diff --git a/Analysis/include/Luau/BuiltinDefinitions.h b/Analysis/include/Luau/BuiltinDefinitions.h index 89a3a929..c582d42b 100644 --- a/Analysis/include/Luau/BuiltinDefinitions.h +++ b/Analysis/include/Luau/BuiltinDefinitions.h @@ -9,7 +9,7 @@ namespace Luau { -static constexpr char kRequireTagName[] = "require"; +inline constexpr char kRequireTagName[] = "require"; struct Frontend; struct GlobalTypes; diff --git a/Analysis/include/Luau/Constraint.h b/Analysis/include/Luau/Constraint.h index 74187556..c48c6ea9 100644 --- a/Analysis/include/Luau/Constraint.h +++ b/Analysis/include/Luau/Constraint.h @@ -276,6 +276,12 @@ struct ReducePackConstraint TypePackId tp; }; +// simplify ty +struct SimplifyConstraint +{ + TypeId ty; +}; + using ConstraintV = Variant< SubtypeConstraint, PackSubtypeConstraint, @@ -294,7 +300,8 @@ using ConstraintV = Variant< ReduceConstraint, ReducePackConstraint, EqualityConstraint, - TableCheckConstraint>; + TableCheckConstraint, + SimplifyConstraint>; struct Constraint { diff --git a/Analysis/include/Luau/ConstraintGenerator.h b/Analysis/include/Luau/ConstraintGenerator.h index 3a896e2d..10b962f6 100644 --- a/Analysis/include/Luau/ConstraintGenerator.h +++ b/Analysis/include/Luau/ConstraintGenerator.h @@ -173,6 +173,8 @@ private: std::vector> DEPRECATED_interiorTypes; std::vector interiorFreeTypes; + std::vector unionsToSimplify; + /** * Fabricates a new free type belonging to a given scope. * @param scope the scope the free type belongs to. @@ -447,6 +449,12 @@ private: // make a union type function of these two types TypeId makeUnion(const ScopePtr& scope, Location location, TypeId lhs, TypeId rhs); + + // Make a union type and add it to `unionsToSimplify`, ensuring that + // later we will attempt to simplify this union in order to keep types + // small. + TypeId makeUnion(std::vector options); + // make an intersect type function of these two types TypeId makeIntersect(const ScopePtr& scope, Location location, TypeId lhs, TypeId rhs); void prepopulateGlobalScopeForFragmentTypecheck(const ScopePtr& globalScope, const ScopePtr& resumeScope, AstStatBlock* program); diff --git a/Analysis/include/Luau/ConstraintSolver.h b/Analysis/include/Luau/ConstraintSolver.h index caf3969c..0804f296 100644 --- a/Analysis/include/Luau/ConstraintSolver.h +++ b/Analysis/include/Luau/ConstraintSolver.h @@ -249,6 +249,8 @@ public: bool tryDispatch(const ReducePackConstraint& c, NotNull constraint, bool force); bool tryDispatch(const EqualityConstraint& c, NotNull constraint); + bool tryDispatch(const SimplifyConstraint& c, NotNull constraint); + // for a, ... in some_table do // also handles __iter metamethod bool tryDispatchIterableTable(TypeId iteratorTy, const IterableConstraint& c, NotNull constraint, bool force); diff --git a/Analysis/include/Luau/Error.h b/Analysis/include/Luau/Error.h index 243daef1..11216668 100644 --- a/Analysis/include/Luau/Error.h +++ b/Analysis/include/Luau/Error.h @@ -462,6 +462,11 @@ struct ReservedIdentifier bool operator==(const ReservedIdentifier& rhs) const; }; +struct UnexpectedArrayLikeTableItem +{ + bool operator==(const UnexpectedArrayLikeTableItem&) const { return true; } +}; + using TypeErrorData = Variant< TypeMismatch, UnknownSymbol, @@ -512,7 +517,8 @@ using TypeErrorData = Variant< UnexpectedTypePackInSubtyping, ExplicitFunctionAnnotationRecommended, UserDefinedTypeFunctionError, - ReservedIdentifier>; + ReservedIdentifier, + UnexpectedArrayLikeTableItem>; struct TypeErrorSummary { diff --git a/Analysis/include/Luau/Metamethods.h b/Analysis/include/Luau/Metamethods.h index 747b7201..af126e7a 100644 --- a/Analysis/include/Luau/Metamethods.h +++ b/Analysis/include/Luau/Metamethods.h @@ -8,7 +8,7 @@ namespace Luau { -static const std::unordered_map kBinaryOpMetamethods{ +inline const std::unordered_map kBinaryOpMetamethods{ {AstExprBinary::Op::CompareEq, "__eq"}, {AstExprBinary::Op::CompareNe, "__eq"}, {AstExprBinary::Op::CompareGe, "__lt"}, @@ -25,7 +25,7 @@ static const std::unordered_map kBinaryOpMetamet {AstExprBinary::Op::Concat, "__concat"}, }; -static const std::unordered_map kUnaryOpMetamethods{ +inline const std::unordered_map kUnaryOpMetamethods{ {AstExprUnary::Op::Minus, "__unm"}, {AstExprUnary::Op::Len, "__len"}, }; diff --git a/Analysis/include/Luau/Subtyping.h b/Analysis/include/Luau/Subtyping.h index aeee0829..fbb0452e 100644 --- a/Analysis/include/Luau/Subtyping.h +++ b/Analysis/include/Luau/Subtyping.h @@ -60,7 +60,7 @@ struct SubtypingReasoningHash }; using SubtypingReasonings = DenseHashSet; -static const SubtypingReasoning kEmptyReasoning = SubtypingReasoning{TypePath::kEmpty, TypePath::kEmpty, SubtypingVariance::Invalid}; +inline const SubtypingReasoning kEmptyReasoning = SubtypingReasoning{TypePath::kEmpty, TypePath::kEmpty, SubtypingVariance::Invalid}; struct SubtypingResult { diff --git a/Analysis/include/Luau/Type.h b/Analysis/include/Luau/Type.h index a110268a..b41bf28b 100644 --- a/Analysis/include/Luau/Type.h +++ b/Analysis/include/Luau/Type.h @@ -1206,7 +1206,7 @@ std::vector filterMap(TypeId type, TypeIdPredicate predicate); // A tag to mark a type which doesn't derive directly from the root type as overriding the return of `typeof`. // Any classes which derive from this type will have typeof return this type. -static constexpr char kTypeofRootTag[] = "typeofRoot"; +inline constexpr char kTypeofRootTag[] = "typeofRoot"; void attachTag(TypeId ty, const std::string& tagName); void attachTag(Property& prop, const std::string& tagName); diff --git a/Analysis/include/Luau/TypeChecker2.h b/Analysis/include/Luau/TypeChecker2.h index ebd2f4bc..4b7901ab 100644 --- a/Analysis/include/Luau/TypeChecker2.h +++ b/Analysis/include/Luau/TypeChecker2.h @@ -193,6 +193,9 @@ private: void explainError(TypeId subTy, TypeId superTy, Location location, const SubtypingResult& result); void explainError(TypePackId subTy, TypePackId superTy, Location location, const SubtypingResult& result); + + bool testPotentialLiteralIsSubtype(AstExpr* expr, TypeId expectedTy); + bool testIsSubtype(TypeId subTy, TypeId superTy, Location location); bool testIsSubtype(TypePackId subTy, TypePackId superTy, Location location); void reportError(TypeError e); diff --git a/Analysis/include/Luau/TypeFunctionRuntime.h b/Analysis/include/Luau/TypeFunctionRuntime.h index 108910c1..047ff176 100644 --- a/Analysis/include/Luau/TypeFunctionRuntime.h +++ b/Analysis/include/Luau/TypeFunctionRuntime.h @@ -216,9 +216,6 @@ struct TypeFunctionExternType std::optional metatable; // metaclass? - // this was mistaken, and we should actually be keeping separate read/write types here. - std::optional parent_DEPRECATED; - std::optional readParent; std::optional writeParent; diff --git a/Analysis/include/Luau/TypePath.h b/Analysis/include/Luau/TypePath.h index d783c662..b161c8a2 100644 --- a/Analysis/include/Luau/TypePath.h +++ b/Analysis/include/Luau/TypePath.h @@ -183,7 +183,7 @@ struct PathHash }; /// The canonical "empty" Path, meaning a Path with no components. -static const Path kEmpty{}; +inline const Path kEmpty{}; struct PathBuilder { diff --git a/Analysis/include/Luau/TypeUtils.h b/Analysis/include/Luau/TypeUtils.h index 04516396..6d57caed 100644 --- a/Analysis/include/Luau/TypeUtils.h +++ b/Analysis/include/Luau/TypeUtils.h @@ -291,4 +291,14 @@ void trackInteriorFreeType(Scope* scope, TypeId ty); void trackInteriorFreeTypePack(Scope* scope, TypePackId tp); +// A fast approximation of subTy <: superTy +bool fastIsSubtype(TypeId subTy, TypeId superTy); + +/** + * @param tables A list of potential table parts of a union + * @param exprType Type of the expression to match + * @return An element of `tables` that best matches `exprType`. + */ +std::optional extractMatchingTableType(std::vector& tables, TypeId exprType, NotNull builtinTypes); + } // namespace Luau diff --git a/Analysis/src/AutocompleteCore.cpp b/Analysis/src/AutocompleteCore.cpp index e862f02e..2c678123 100644 --- a/Analysis/src/AutocompleteCore.cpp +++ b/Analysis/src/AutocompleteCore.cpp @@ -25,7 +25,6 @@ LUAU_FASTINT(LuauTypeInferIterationLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTFLAGVARIABLE(DebugLuauMagicVariableNames) LUAU_FASTFLAGVARIABLE(LuauAutocompleteUsesModuleForTypeCompatibility) -LUAU_FASTFLAGVARIABLE(LuauAutocompleteUnionCopyPreviousSeen) LUAU_FASTFLAGVARIABLE(LuauAutocompleteMissingFollows) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) @@ -490,13 +489,10 @@ static void autocompleteProps( // t1 where t1 = t1 | ExternType // // Then we are on a one way journey to a stack overflow. - if (FFlag::LuauAutocompleteUnionCopyPreviousSeen) + for (auto ty : seen) { - for (auto ty : seen) - { - if (is(ty)) - innerSeen.insert(ty); - } + if (is(ty)) + innerSeen.insert(ty); } if (isNil(*iter)) diff --git a/Analysis/src/BuiltinDefinitions.cpp b/Analysis/src/BuiltinDefinitions.cpp index 19595cce..e6a02677 100644 --- a/Analysis/src/BuiltinDefinitions.cpp +++ b/Analysis/src/BuiltinDefinitions.cpp @@ -30,9 +30,8 @@ */ LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauNonReentrantGeneralization2) +LUAU_FASTFLAG(LuauNonReentrantGeneralization3) LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3) -LUAU_FASTFLAGVARIABLE(LuauUserTypeFunTypecheck) LUAU_FASTFLAGVARIABLE(LuauMagicFreezeCheckBlocked2) LUAU_FASTFLAGVARIABLE(LuauFormatUseLastPosition) @@ -292,8 +291,6 @@ void assignPropDocumentationSymbols(TableType::Props& props, const std::string& static void finalizeGlobalBindings(ScopePtr scope) { - LUAU_ASSERT(FFlag::LuauUserTypeFunTypecheck); - for (const auto& pair : scope->bindings) { persist(pair.second.typeId); @@ -313,8 +310,8 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC TypeArena& arena = globals.globalTypes; NotNull builtinTypes = globals.builtinTypes; - Scope* globalScope = nullptr; // NotNull when removing FFlag::LuauNonReentrantGeneralization2 - if (FFlag::LuauNonReentrantGeneralization2) + Scope* globalScope = nullptr; // NotNull when removing FFlag::LuauNonReentrantGeneralization3 + if (FFlag::LuauNonReentrantGeneralization3) globalScope = globals.globalScope.get(); if (FFlag::LuauSolverV2) @@ -420,23 +417,7 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC // clang-format on } - if (FFlag::LuauUserTypeFunTypecheck) - { - finalizeGlobalBindings(globals.globalScope); - } - else - { - for (const auto& pair : globals.globalScope->bindings) - { - persist(pair.second.typeId); - - if (TableType* ttv = getMutable(pair.second.typeId)) - { - if (!ttv->name) - ttv->name = "typeof(" + toString(pair.first) + ")"; - } - } - } + finalizeGlobalBindings(globals.globalScope); attachMagicFunction(getGlobalBinding(globals, "assert"), std::make_shared()); @@ -500,58 +481,55 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC attachTag(requireTy, kRequireTagName); attachMagicFunction(requireTy, std::make_shared()); - if (FFlag::LuauUserTypeFunTypecheck) + // Global scope cannot be the parent of the type checking environment because it can be changed by the embedder + globals.globalTypeFunctionScope->exportedTypeBindings = globals.globalScope->exportedTypeBindings; + globals.globalTypeFunctionScope->builtinTypeNames = globals.globalScope->builtinTypeNames; + + // Type function runtime also removes a few standard libraries and globals, so we will take only the ones that are defined + static const char* typeFunctionRuntimeBindings[] = { + // Libraries + "math", + "table", + "string", + "bit32", + "utf8", + "buffer", + + // Globals + "assert", + "error", + "print", + "next", + "ipairs", + "pairs", + "select", + "unpack", + "getmetatable", + "setmetatable", + "rawget", + "rawset", + "rawlen", + "rawequal", + "tonumber", + "tostring", + "type", + "typeof", + }; + + for (auto& name : typeFunctionRuntimeBindings) { - // Global scope cannot be the parent of the type checking environment because it can be changed by the embedder - globals.globalTypeFunctionScope->exportedTypeBindings = globals.globalScope->exportedTypeBindings; - globals.globalTypeFunctionScope->builtinTypeNames = globals.globalScope->builtinTypeNames; + AstName astName = globals.globalNames.names->get(name); + LUAU_ASSERT(astName.value); - // Type function runtime also removes a few standard libraries and globals, so we will take only the ones that are defined - static const char* typeFunctionRuntimeBindings[] = { - // Libraries - "math", - "table", - "string", - "bit32", - "utf8", - "buffer", - - // Globals - "assert", - "error", - "print", - "next", - "ipairs", - "pairs", - "select", - "unpack", - "getmetatable", - "setmetatable", - "rawget", - "rawset", - "rawlen", - "rawequal", - "tonumber", - "tostring", - "type", - "typeof", - }; - - for (auto& name : typeFunctionRuntimeBindings) - { - AstName astName = globals.globalNames.names->get(name); - LUAU_ASSERT(astName.value); - - globals.globalTypeFunctionScope->bindings[astName] = globals.globalScope->bindings[astName]; - } - - LoadDefinitionFileResult typeFunctionLoadResult = frontend.loadDefinitionFile( - globals, globals.globalTypeFunctionScope, getTypeFunctionDefinitionSource(), "@luau", /* captureComments */ false, false - ); - LUAU_ASSERT(typeFunctionLoadResult.success); - - finalizeGlobalBindings(globals.globalTypeFunctionScope); + globals.globalTypeFunctionScope->bindings[astName] = globals.globalScope->bindings[astName]; } + + LoadDefinitionFileResult typeFunctionLoadResult = frontend.loadDefinitionFile( + globals, globals.globalTypeFunctionScope, getTypeFunctionDefinitionSource(), "@luau", /* captureComments */ false, false + ); + LUAU_ASSERT(typeFunctionLoadResult.success); + + finalizeGlobalBindings(globals.globalTypeFunctionScope); } static std::vector parseFormatString(NotNull builtinTypes, const char* data, size_t size) diff --git a/Analysis/src/Constraint.cpp b/Analysis/src/Constraint.cpp index 1e59f4f8..b84e476b 100644 --- a/Analysis/src/Constraint.cpp +++ b/Analysis/src/Constraint.cpp @@ -143,10 +143,6 @@ DenseHashSet Constraint::getMaybeMutatedFreeTypes() const rci.traverse(ty); // `UnpackConstraint` should not mutate `sourcePack`. } - else if (auto rpc = get(*this); FFlag::DebugLuauGreedyGeneralization && rpc) - { - rci.traverse(rpc->ty); - } else if (auto rpc = get(*this)) { rci.traverse(rpc->tp); diff --git a/Analysis/src/ConstraintGenerator.cpp b/Analysis/src/ConstraintGenerator.cpp index c3ccf60d..6d3ec1c1 100644 --- a/Analysis/src/ConstraintGenerator.cpp +++ b/Analysis/src/ConstraintGenerator.cpp @@ -33,25 +33,21 @@ LUAU_FASTINT(LuauCheckRecursionLimit) LUAU_FASTFLAG(DebugLuauLogSolverToJson) LUAU_FASTFLAG(DebugLuauMagicTypes) -LUAU_FASTFLAG(LuauNonReentrantGeneralization2) -LUAU_FASTFLAGVARIABLE(LuauPropagateExpectedTypesForCalls) +LUAU_FASTFLAG(LuauNonReentrantGeneralization3) LUAU_FASTFLAG(DebugLuauGreedyGeneralization) -LUAU_FASTFLAGVARIABLE(LuauUngeneralizedTypesForRecursiveFunctions) - -LUAU_FASTFLAG(LuauUserTypeFunTypecheck) LUAU_FASTFLAGVARIABLE(LuauRetainDefinitionAliasLocations) -LUAU_FASTFLAGVARIABLE(LuauCacheInferencePerAstExpr) -LUAU_FASTFLAGVARIABLE(LuauAlwaysResolveAstTypes) LUAU_FASTFLAGVARIABLE(LuauWeakNilRefinementType) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) LUAU_FASTFLAG(LuauGlobalVariableModuleIsolation) - -LUAU_FASTFLAGVARIABLE(LuauNoTypeFunctionsNamedTypeOf) +LUAU_FASTFLAG(LuauNoMoreInjectiveTypeFunctions) LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions) LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType) LUAU_FASTFLAGVARIABLE(LuauAvoidDoubleNegation) +LUAU_FASTFLAGVARIABLE(LuauSimplifyOutOfLine) +LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck) +LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops) namespace Luau { @@ -255,18 +251,15 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block) rootScope->location = block->location; module->astScopes[block] = NotNull{scope.get()}; - if (FFlag::LuauNonReentrantGeneralization2) + if (FFlag::LuauNonReentrantGeneralization3) interiorFreeTypes.emplace_back(); else DEPRECATED_interiorTypes.emplace_back(); - if (FFlag::LuauUserTypeFunTypecheck) - { - // Create module-local scope for the type function environment - ScopePtr localTypeFunctionScope = std::make_shared(typeFunctionScope); - localTypeFunctionScope->location = block->location; - typeFunctionRuntime->rootScope = localTypeFunctionScope; - } + // Create module-local scope for the type function environment + ScopePtr localTypeFunctionScope = std::make_shared(typeFunctionScope); + localTypeFunctionScope->location = block->location; + typeFunctionRuntime->rootScope = localTypeFunctionScope; rootScope->returnType = freshTypePack(scope, Polarity::Positive); TypeId moduleFnTy = arena->addType(FunctionType{TypeLevel{}, builtinTypes->anyTypePack, rootScope->returnType}); @@ -294,7 +287,7 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block) } ); - if (FFlag::LuauNonReentrantGeneralization2) + if (FFlag::LuauNonReentrantGeneralization3) { scope->interiorFreeTypes = std::move(interiorFreeTypes.back().types); scope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks); @@ -313,7 +306,7 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block) } ); - if (FFlag::LuauNonReentrantGeneralization2) + if (FFlag::LuauNonReentrantGeneralization3) interiorFreeTypes.pop_back(); else DEPRECATED_interiorTypes.pop_back(); @@ -338,6 +331,12 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block) LUAU_ASSERT(get(ty)); asMutable(ty)->ty.emplace(domainTy); } + + if (FFlag::LuauSimplifyOutOfLine) + { + for (TypeId ty : unionsToSimplify) + addConstraint(scope, block->location, SimplifyConstraint{ty}); + } } void ConstraintGenerator::visitFragmentRoot(const ScopePtr& resumeScope, AstStatBlock* block) @@ -345,13 +344,13 @@ void ConstraintGenerator::visitFragmentRoot(const ScopePtr& resumeScope, AstStat // We prepopulate global data in the resumeScope to avoid writing data into the old modules scopes prepopulateGlobalScopeForFragmentTypecheck(globalScope, resumeScope, block); // Pre - if (FFlag::LuauNonReentrantGeneralization2) + if (FFlag::LuauNonReentrantGeneralization3) interiorFreeTypes.emplace_back(); else DEPRECATED_interiorTypes.emplace_back(); visitBlockWithoutChildScope(resumeScope, block); // Post - if (FFlag::LuauNonReentrantGeneralization2) + if (FFlag::LuauNonReentrantGeneralization3) interiorFreeTypes.pop_back(); else DEPRECATED_interiorTypes.pop_back(); @@ -381,7 +380,7 @@ void ConstraintGenerator::visitFragmentRoot(const ScopePtr& resumeScope, AstStat TypeId ConstraintGenerator::freshType(const ScopePtr& scope, Polarity polarity) { - if (FFlag::LuauNonReentrantGeneralization2) + if (FFlag::LuauNonReentrantGeneralization3) { auto ft = Luau::freshType(arena, builtinTypes, scope.get(), polarity); interiorFreeTypes.back().types.push_back(ft); @@ -403,7 +402,7 @@ TypePackId ConstraintGenerator::freshTypePack(const ScopePtr& scope, Polarity po { FreeTypePack f{scope.get(), polarity}; TypePackId result = arena->addTypePack(TypePackVar{std::move(f)}); - if (FFlag::LuauNonReentrantGeneralization2) + if (FFlag::LuauNonReentrantGeneralization3) interiorFreeTypes.back().typePacks.push_back(result); return result; } @@ -829,8 +828,7 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc } else if (auto function = stat->as()) { - if (FFlag::LuauUserTypeFunTypecheck) - hasTypeFunction = true; + hasTypeFunction = true; // If a type function w/ same name has already been defined, error for having duplicates if (scope->exportedTypeBindings.count(function->name.value) || scope->privateTypeBindings.count(function->name.value)) @@ -841,11 +839,7 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc continue; } - // Variable becomes unused with the removal of FFlag::LuauUserTypeFunTypecheck - ScopePtr defnScope = FFlag::LuauUserTypeFunTypecheck ? nullptr : childScope(function, scope); - // Create TypeFunctionInstanceType - std::vector typeParams; typeParams.reserve(function->body->args.size); @@ -914,7 +908,7 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc } } - if (FFlag::LuauUserTypeFunTypecheck && hasTypeFunction) + if (hasTypeFunction) typeFunctionEnvScope = std::make_shared(typeFunctionRuntime->rootScope); // Additional pass for user-defined type functions to fill in their environments completely @@ -922,13 +916,10 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc { if (auto function = stat->as()) { - if (FFlag::LuauUserTypeFunTypecheck) - { - // Similar to global pre-population, create a binding for each type function in the scope upfront - TypeId bt = arena->addType(BlockedType{}); - typeFunctionEnvScope->bindings[function->name] = Binding{bt, function->location}; - astTypeFunctionEnvironmentScopes[function] = typeFunctionEnvScope; - } + // Similar to global pre-population, create a binding for each type function in the scope upfront + TypeId bt = arena->addType(BlockedType{}); + typeFunctionEnvScope->bindings[function->name] = Binding{bt, function->location}; + astTypeFunctionEnvironmentScopes[function] = typeFunctionEnvScope; // Find the type function we have already created TypeFunctionInstanceType* mainTypeFun = nullptr; @@ -948,61 +939,32 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc UserDefinedFunctionData& userFuncData = mainTypeFun->userFuncData; size_t level = 0; - if (FFlag::LuauUserTypeFunTypecheck) + auto addToEnvironment = [this](UserDefinedFunctionData& userFuncData, ScopePtr scope, const Name& name, TypeId type, size_t level) { - auto addToEnvironment = [this](UserDefinedFunctionData& userFuncData, ScopePtr scope, const Name& name, TypeId type, size_t level) - { - if (userFuncData.environment.find(name)) - return; + if (userFuncData.environment.find(name)) + return; - if (auto ty = get(type); ty && ty->userFuncData.definition) + if (auto ty = get(type); ty && ty->userFuncData.definition) + { + userFuncData.environment[name] = std::make_pair(ty->userFuncData.definition, level); + + if (auto it = astTypeFunctionEnvironmentScopes.find(ty->userFuncData.definition)) { - userFuncData.environment[name] = std::make_pair(ty->userFuncData.definition, level); - - if (auto it = astTypeFunctionEnvironmentScopes.find(ty->userFuncData.definition)) - { - if (auto existing = (*it)->linearSearchForBinding(name, /* traverseScopeChain */ false)) - scope->bindings[ty->userFuncData.definition->name] = - Binding{existing->typeId, ty->userFuncData.definition->location}; - } + if (auto existing = (*it)->linearSearchForBinding(name, /* traverseScopeChain */ false)) + scope->bindings[ty->userFuncData.definition->name] = Binding{existing->typeId, ty->userFuncData.definition->location}; } - }; - - for (Scope* curr = scope.get(); curr; curr = curr->parent.get()) - { - for (auto& [name, tf] : curr->privateTypeBindings) - addToEnvironment(userFuncData, typeFunctionEnvScope, name, tf.type, level); - - for (auto& [name, tf] : curr->exportedTypeBindings) - addToEnvironment(userFuncData, typeFunctionEnvScope, name, tf.type, level); - - level++; } - } - else + }; + + for (Scope* curr = scope.get(); curr; curr = curr->parent.get()) { - for (Scope* curr = scope.get(); curr; curr = curr->parent.get()) - { - for (auto& [name, tf] : curr->privateTypeBindings) - { - if (userFuncData.environment.find(name)) - continue; + for (auto& [name, tf] : curr->privateTypeBindings) + addToEnvironment(userFuncData, typeFunctionEnvScope, name, tf.type, level); - 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) + addToEnvironment(userFuncData, typeFunctionEnvScope, name, tf.type, 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++; - } + level++; } } } @@ -1289,6 +1251,9 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFor* for_) visit(forScope, for_->body); + if (FFlag::LuauDfgAllowUpdatesInLoops) + scope->inheritAssignments(forScope); + return ControlFlow::None; } @@ -1350,6 +1315,9 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatForIn* forI visit(loopScope, forIn->body); Checkpoint end = checkpoint(this); + if (FFlag::LuauDfgAllowUpdatesInLoops) + scope->inheritAssignments(loopScope); + // This iter constraint must dispatch first. forEachConstraint( start, @@ -1373,6 +1341,9 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatWhile* whil visit(whileScope, while_->body); + if (FFlag::LuauDfgAllowUpdatesInLoops) + scope->inheritAssignments(whileScope); + return ControlFlow::None; } @@ -1384,6 +1355,9 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatRepeat* rep check(repeatScope, repeat->condition); + if (FFlag::LuauDfgAllowUpdatesInLoops) + scope->inheritAssignments(repeatScope); + return ControlFlow::None; } @@ -1483,24 +1457,21 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f DefId def = dfg->getDef(function->name); - if (FFlag::LuauUngeneralizedTypesForRecursiveFunctions) + if (AstExprLocal* localName = function->name->as()) { - if (AstExprLocal* localName = function->name->as()) - { - sig.bodyScope->bindings[localName->local] = Binding{sig.signature, localName->location}; - sig.bodyScope->lvalueTypes[def] = sig.signature; - sig.bodyScope->rvalueRefinements[def] = sig.signature; - } - else if (AstExprGlobal* globalName = function->name->as()) - { - sig.bodyScope->bindings[globalName->name] = Binding{sig.signature, globalName->location}; - sig.bodyScope->lvalueTypes[def] = sig.signature; - sig.bodyScope->rvalueRefinements[def] = sig.signature; - } - else if (AstExprIndexName* indexName = function->name->as()) - { - sig.bodyScope->rvalueRefinements[def] = sig.signature; - } + sig.bodyScope->bindings[localName->local] = Binding{sig.signature, localName->location}; + sig.bodyScope->lvalueTypes[def] = sig.signature; + sig.bodyScope->rvalueRefinements[def] = sig.signature; + } + else if (AstExprGlobal* globalName = function->name->as()) + { + sig.bodyScope->bindings[globalName->name] = Binding{sig.signature, globalName->location}; + sig.bodyScope->lvalueTypes[def] = sig.signature; + sig.bodyScope->rvalueRefinements[def] = sig.signature; + } + else if (AstExprIndexName* indexName = function->name->as()) + { + sig.bodyScope->rvalueRefinements[def] = sig.signature; } checkFunctionBody(sig.bodyScope, function->func); @@ -1782,15 +1753,9 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeAlias* ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunction* function) { - if (!FFlag::LuauUserTypeFunTypecheck) - return ControlFlow::None; - - if (FFlag::LuauNoTypeFunctionsNamedTypeOf) + if (function->name == "typeof") { - if (function->name == "typeof") - { - reportError(function->location, ReservedIdentifier{"typeof"}); - } + reportError(function->location, ReservedIdentifier{"typeof"}); } auto scopePtr = astTypeFunctionEnvironmentScopes.find(function); @@ -1802,7 +1767,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio // Place this function as a child of the non-type function scope scope->children.push_back(NotNull{sig.signatureScope.get()}); - if (FFlag::LuauNonReentrantGeneralization2) + if (FFlag::LuauNonReentrantGeneralization3) interiorFreeTypes.emplace_back(); else DEPRECATED_interiorTypes.push_back(std::vector{}); @@ -1820,7 +1785,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio } ); - if (FFlag::LuauNonReentrantGeneralization2) + if (FFlag::LuauNonReentrantGeneralization3) { sig.signatureScope->interiorFreeTypes = std::move(interiorFreeTypes.back().types); sig.signatureScope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks); @@ -1829,7 +1794,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio sig.signatureScope->interiorFreeTypes = std::move(DEPRECATED_interiorTypes.back()); getMutable(generalizedTy)->setOwner(gc); - if (FFlag::LuauNonReentrantGeneralization2) + if (FFlag::LuauNonReentrantGeneralization3) interiorFreeTypes.pop_back(); else DEPRECATED_interiorTypes.pop_back(); @@ -2229,7 +2194,7 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall* else if (i < exprArgs.size() - 1 || !(arg->is() || arg->is())) { std::optional expectedType = std::nullopt; - if (FFlag::LuauPropagateExpectedTypesForCalls && i < expectedTypesForCall.size()) + if (i < expectedTypesForCall.size()) { expectedType = expectedTypesForCall[i]; } @@ -2240,7 +2205,7 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall* else { std::vector> expectedTypes = {}; - if (FFlag::LuauPropagateExpectedTypesForCalls && i < expectedTypesForCall.size()) + if (i < expectedTypesForCall.size()) { expectedTypes.insert(expectedTypes.end(), expectedTypesForCall.begin() + int(i), expectedTypesForCall.end()); } @@ -2423,7 +2388,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExpr* expr, std:: // a[b] += c // // We only solve _one_ set of constraints for `b`. - if (FFlag::LuauCacheInferencePerAstExpr && inferredExprCache.contains(expr)) + if (inferredExprCache.contains(expr)) return inferredExprCache[expr]; Inference result; @@ -2478,8 +2443,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExpr* expr, std:: result = Inference{freshType(scope)}; } - if (FFlag::LuauCacheInferencePerAstExpr) - inferredExprCache[expr] = result; + inferredExprCache[expr] = result; LUAU_ASSERT(result.ty); module->astTypes[expr] = result.ty; @@ -2494,7 +2458,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantStrin return Inference{arena->addType(SingletonType{StringSingleton{std::string{string->value.data, string->value.size}}})}; TypeId freeTy = nullptr; - if (FFlag::LuauNonReentrantGeneralization2) + if (FFlag::LuauNonReentrantGeneralization3) { freeTy = freshType(scope, Polarity::Positive); FreeType* ft = getMutable(freeTy); @@ -2521,7 +2485,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantBool* return Inference{singletonType}; TypeId freeTy = nullptr; - if (FFlag::LuauNonReentrantGeneralization2) + if (FFlag::LuauNonReentrantGeneralization3) { freeTy = freshType(scope, Polarity::Positive); FreeType* ft = getMutable(freeTy); @@ -2682,7 +2646,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* fun Checkpoint startCheckpoint = checkpoint(this); FunctionSignature sig = checkFunctionSignature(scope, func, expectedType); - if (FFlag::LuauNonReentrantGeneralization2) + if (FFlag::LuauNonReentrantGeneralization3) interiorFreeTypes.emplace_back(); else DEPRECATED_interiorTypes.push_back(std::vector{}); @@ -2700,7 +2664,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* fun } ); - if (FFlag::LuauNonReentrantGeneralization2) + if (FFlag::LuauNonReentrantGeneralization3) { sig.signatureScope->interiorFreeTypes = std::move(interiorFreeTypes.back().types); sig.signatureScope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks); @@ -2840,11 +2804,17 @@ Inference ConstraintGenerator::checkAstExprBinary( } case AstExprBinary::Op::CompareLt: { + if (FFlag::LuauNoMoreInjectiveTypeFunctions) + addConstraint(scope, location, EqualityConstraint{leftType, rightType}); + TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().ltFunc, {leftType, rightType}, {}, scope, location); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::CompareGe: { + if (FFlag::LuauNoMoreInjectiveTypeFunctions) + addConstraint(scope, location, EqualityConstraint{leftType, rightType}); + TypeId resultType = createTypeFunctionInstance( builtinTypeFunctions().ltFunc, {rightType, leftType}, // lua decided that `__ge(a, b)` is instead just `__lt(b, a)` @@ -2856,11 +2826,17 @@ Inference ConstraintGenerator::checkAstExprBinary( } case AstExprBinary::Op::CompareLe: { + if (FFlag::LuauNoMoreInjectiveTypeFunctions) + addConstraint(scope, location, EqualityConstraint{leftType, rightType}); + TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().leFunc, {leftType, rightType}, {}, scope, location); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::CompareGt: { + if (FFlag::LuauNoMoreInjectiveTypeFunctions) + addConstraint(scope, location, EqualityConstraint{leftType, rightType}); + TypeId resultType = createTypeFunctionInstance( builtinTypeFunctions().leFunc, {rightType, leftType}, // lua decided that `__gt(a, b)` is instead just `__le(b, a)` @@ -3192,7 +3168,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr, ttv->definitionLocation = expr->location; ttv->scope = scope.get(); - if (FFlag::LuauNonReentrantGeneralization2) + if (FFlag::LuauNonReentrantGeneralization3) interiorFreeTypes.back().types.push_back(ty); else DEPRECATED_interiorTypes.back().push_back(ty); @@ -3245,18 +3221,47 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr, { LUAU_ASSERT(!indexValueLowerBound.empty()); - TypeId indexKey = indexKeyLowerBound.size() == 1 - ? *indexKeyLowerBound.begin() - : arena->addType(UnionType{std::vector(indexKeyLowerBound.begin(), indexKeyLowerBound.end())}); + if (FFlag::LuauSimplifyOutOfLine) + { + TypeId indexKey = nullptr; + TypeId indexValue = nullptr; - TypeId indexValue = indexValueLowerBound.size() == 1 - ? *indexValueLowerBound.begin() - : arena->addType(UnionType{std::vector(indexValueLowerBound.begin(), indexValueLowerBound.end())}); + if (indexKeyLowerBound.size() == 1) + { + indexKey = *indexKeyLowerBound.begin(); + } + else + { + indexKey = arena->addType(UnionType{std::vector(indexKeyLowerBound.begin(), indexKeyLowerBound.end())}); + unionsToSimplify.push_back(indexKey); + } - ttv->indexer = TableIndexer{indexKey, indexValue}; + if (indexValueLowerBound.size() == 1) + { + indexValue = *indexValueLowerBound.begin(); + } + else + { + indexValue = arena->addType(UnionType{std::vector(indexValueLowerBound.begin(), indexValueLowerBound.end())}); + unionsToSimplify.push_back(indexValue); + } + + ttv->indexer = TableIndexer{indexKey, indexValue}; + } + else + { + TypeId indexKey = indexKeyLowerBound.size() == 1 + ? *indexKeyLowerBound.begin() + : arena->addType(UnionType{std::vector(indexKeyLowerBound.begin(), indexKeyLowerBound.end())}); + + TypeId indexValue = indexValueLowerBound.size() == 1 + ? *indexValueLowerBound.begin() + : arena->addType(UnionType{std::vector(indexValueLowerBound.begin(), indexValueLowerBound.end())}); + ttv->indexer = TableIndexer{indexKey, indexValue}; + } } - if (expectedType) + if (expectedType && !FFlag::LuauTableLiteralSubtypeSpecificCheck) { addConstraint( scope, @@ -3769,15 +3774,10 @@ TypeId ConstraintGenerator::resolveType_(const ScopePtr& scope, AstType* ty, boo } else if (ty->is()) { - if (FFlag::LuauAlwaysResolveAstTypes) - result = builtinTypes->nilType; - else - return builtinTypes->nilType; + result = builtinTypes->nilType; } else if (auto unionAnnotation = ty->as()) { - if (FFlag::LuauAlwaysResolveAstTypes) - { if (unionAnnotation->types.size == 1) result = resolveType_(scope, unionAnnotation->types.data[0], inTypeArguments); else @@ -3790,24 +3790,9 @@ TypeId ConstraintGenerator::resolveType_(const ScopePtr& scope, AstType* ty, boo result = arena->addType(UnionType{parts}); } - } - else - { - if (unionAnnotation->types.size == 1) - return resolveType_(scope, unionAnnotation->types.data[0], inTypeArguments); - std::vector parts; - for (AstType* part : unionAnnotation->types) - { - parts.push_back(resolveType(scope, part, inTypeArguments)); - } - - result = arena->addType(UnionType{parts}); - } } else if (auto intersectionAnnotation = ty->as()) { - if (FFlag::LuauAlwaysResolveAstTypes) - { if (intersectionAnnotation->types.size == 1) result = resolveType_(scope, intersectionAnnotation->types.data[0], inTypeArguments); else @@ -3820,19 +3805,6 @@ TypeId ConstraintGenerator::resolveType_(const ScopePtr& scope, AstType* ty, boo result = arena->addType(IntersectionType{parts}); } - } - else - { - if (intersectionAnnotation->types.size == 1) - return resolveType_(scope, intersectionAnnotation->types.data[0], inTypeArguments); - std::vector parts; - for (AstType* part : intersectionAnnotation->types) - { - parts.push_back(resolveType(scope, part, inTypeArguments)); - } - - result = arena->addType(IntersectionType{parts}); - } } else if (auto typeGroupAnnotation = ty->as()) { @@ -4036,9 +4008,26 @@ TypeId ConstraintGenerator::makeUnion(const ScopePtr& scope, Location location, if (get(follow(rhs))) return lhs; - TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().unionFunc, {lhs, rhs}, {}, scope, location); + if (FFlag::LuauSimplifyOutOfLine) + { + TypeId result = simplifyUnion(scope, location, lhs, rhs); + if (is(follow(result))) + unionsToSimplify.push_back(result); + return result; + } + else + { + TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().unionFunc, {lhs, rhs}, {}, scope, location); + return resultType; + } +} - return resultType; +TypeId ConstraintGenerator::makeUnion(std::vector options) +{ + LUAU_ASSERT(FFlag::LuauSimplifyOutOfLine); + TypeId result = arena->addType(UnionType{std::move(options)}); + unionsToSimplify.push_back(result); + return result; } TypeId ConstraintGenerator::makeIntersect(const ScopePtr& scope, Location location, TypeId lhs, TypeId rhs) @@ -4183,12 +4172,9 @@ void ConstraintGenerator::prepopulateGlobalScopeForFragmentTypecheck(const Scope } - if (FFlag::LuauUserTypeFunTypecheck) - { - // Handle type function globals as well, without preparing a module scope since they have a separate environment - GlobalPrepopulator tfgp{NotNull{typeFunctionRuntime->rootScope.get()}, arena, dfg}; - program->visit(&tfgp); - } + // Handle type function globals as well, without preparing a module scope since they have a separate environment + GlobalPrepopulator tfgp{NotNull{typeFunctionRuntime->rootScope.get()}, arena, dfg}; + program->visit(&tfgp); } void ConstraintGenerator::prepopulateGlobalScope(const ScopePtr& globalScope, AstStatBlock* program) @@ -4200,12 +4186,9 @@ void ConstraintGenerator::prepopulateGlobalScope(const ScopePtr& globalScope, As program->visit(&gp); - if (FFlag::LuauUserTypeFunTypecheck) - { - // Handle type function globals as well, without preparing a module scope since they have a separate environment - GlobalPrepopulator tfgp{NotNull{typeFunctionRuntime->rootScope.get()}, arena, dfg}; - program->visit(&tfgp); - } + // Handle type function globals as well, without preparing a module scope since they have a separate environment + GlobalPrepopulator tfgp{NotNull{typeFunctionRuntime->rootScope.get()}, arena, dfg}; + program->visit(&tfgp); } bool ConstraintGenerator::recordPropertyAssignment(TypeId ty) @@ -4265,9 +4248,16 @@ void ConstraintGenerator::fillInInferredBindings(const ScopePtr& globalScope, As scope->bindings[symbol] = Binding{tys.front(), location}; else { - TypeId ty = createTypeFunctionInstance(builtinTypeFunctions().unionFunc, std::move(tys), {}, globalScope, location); - - scope->bindings[symbol] = Binding{ty, location}; + if (FFlag::LuauSimplifyOutOfLine) + { + TypeId ty = makeUnion(std::move(tys)); + scope->bindings[symbol] = Binding{ty, location}; + } + else + { + TypeId ty = createTypeFunctionInstance(builtinTypeFunctions().unionFunc, std::move(tys), {}, globalScope, location); + scope->bindings[symbol] = Binding{ty, location}; + } } } } @@ -4305,7 +4295,12 @@ std::vector> ConstraintGenerator::getExpectedCallTypesForF else if (result.size() == 1) el = result[0]; else - el = module->internalTypes.addType(UnionType{std::move(result)}); + { + if (FFlag::LuauSimplifyOutOfLine) + el = makeUnion(std::move(result)); + else + el = module->internalTypes.addType(UnionType{std::move(result)}); + } } } }; diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index 63e2178d..c7c3e437 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -35,11 +35,14 @@ LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification) LUAU_FASTFLAGVARIABLE(LuauHasPropProperBlock) LUAU_FASTFLAGVARIABLE(DebugLuauGreedyGeneralization) LUAU_FASTFLAG(LuauDeprecatedAttribute) -LUAU_FASTFLAG(LuauNonReentrantGeneralization2) -LUAU_FASTFLAG(LuauNewTypeFunReductionChecks2) +LUAU_FASTFLAG(LuauNonReentrantGeneralization3) LUAU_FASTFLAGVARIABLE(LuauTrackInferredFunctionTypeFromCall) LUAU_FASTFLAGVARIABLE(LuauAddCallConstraintForIterableFunctions) LUAU_FASTFLAGVARIABLE(LuauGuardAgainstMalformedTypeAliasExpansion) +LUAU_FASTFLAGVARIABLE(LuauInsertErrorTypesIntoIndexerResult) +LUAU_FASTFLAGVARIABLE(LuauClipVariadicAnysFromArgsToGenericFuncs2) +LUAU_FASTFLAG(LuauSubtypeGenericsAndNegations) +LUAU_FASTFLAG(LuauNoMoreInjectiveTypeFunctions) namespace Luau { @@ -686,6 +689,13 @@ void ConstraintSolver::initFreeTypeTracking() block(dep, c); } } + + // Also check flag integrity while we're here + if (FFlag::DebugLuauGreedyGeneralization) + { + LUAU_ASSERT(FFlag::LuauSubtypeGenericsAndNegations); + LUAU_ASSERT(FFlag::LuauNoMoreInjectiveTypeFunctions); + } } void ConstraintSolver::generalizeOneType(TypeId ty) @@ -729,7 +739,7 @@ void ConstraintSolver::bind(NotNull constraint, TypeId ty, Typ constraint, ty, constraint->scope, builtinTypes->neverType, builtinTypes->unknownType, Polarity::Mixed ); // FIXME? Is this the right polarity? - if (FFlag::LuauNonReentrantGeneralization2) + if (FFlag::LuauNonReentrantGeneralization3) trackInteriorFreeType(constraint->scope, ty); return; @@ -819,6 +829,8 @@ bool ConstraintSolver::tryDispatch(NotNull constraint, bool fo success = tryDispatch(*rpc, constraint, force); else if (auto eqc = get(*constraint)) success = tryDispatch(*eqc, constraint); + else if (auto sc = get(*constraint)) + success = tryDispatch(*sc, constraint); else LUAU_ASSERT(false); @@ -888,7 +900,7 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNullscope->interiorFreeTypes) // NOLINT(bugprone-unchecked-optional-access) { - if (FFlag::LuauNonReentrantGeneralization2) + if (FFlag::LuauNonReentrantGeneralization3) { ty = follow(ty); if (auto freeTy = get(ty)) @@ -910,7 +922,7 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNullscope->interiorFreeTypePacks) { @@ -1519,7 +1531,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNullscope, freeTy); @@ -1540,6 +1552,51 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull(asMutable(c.result), result); + + if (FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2) + { + FunctionType* inferredFuncTy = getMutable(inferredTy); + LUAU_ASSERT(inferredFuncTy); + + // Strip variadic anys from the argTypes of any functionType arguments + const auto [argsHead, argsTail] = flatten(inferredFuncTy->argTypes); + TypePack clippedArgs = {{}, argsTail}; + bool clippedAny = false; + + for (TypeId t : argsHead) + { + const FunctionType* f = get(follow(t)); + if (!f || !f->argTypes) + { + clippedArgs.head.push_back(t); + continue; + } + + const TypePack* argTp = get(follow(f->argTypes)); + if (!argTp || !argTp->tail) + { + clippedArgs.head.push_back(t); + continue; + } + + if (const VariadicTypePack* argTpTail = get(follow(argTp->tail)); + argTpTail && argTpTail->hidden && argTpTail->ty == builtinTypes->anyType) + { + const TypePackId anyLessArgTp = arena->addTypePack(TypePack{argTp->head}); + // Mint a new FunctionType in case the original came from another module + const TypeId newFuncTypeId = arena->addType(FunctionType{anyLessArgTp, f->retTypes}); + FunctionType* newFunc = getMutable(newFuncTypeId); + newFunc->argNames = f->argNames; + clippedArgs.head.push_back(newFuncTypeId); + clippedAny = true; + } + else + clippedArgs.head.push_back(t); + } + + if (clippedAny) + inferredFuncTy->argTypes = arena->addTypePack(std::move(clippedArgs)); + } } for (const auto& [expanded, additions] : u2.expandedFreeTypes) @@ -1683,12 +1740,32 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull(actualArgTy); // Generic types are skipped over entirely, for now. if (containsGenerics.hasGeneric(expectedArgTy)) + { + if (!FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2 || !lambdaTy || !lambdaTy->argTypes) + continue; + + const TypePack* argTp = get(follow(lambdaTy->argTypes)); + if (!argTp || !argTp->tail) + continue; + + if (const VariadicTypePack* argTpTail = get(follow(argTp->tail)); + argTpTail && argTpTail->hidden && argTpTail->ty == builtinTypes->anyType) + { + // Strip variadic any + const TypePackId anyLessArgTp = arena->addTypePack(TypePack{argTp->head}); + const TypeId newFuncTypeId = arena->addType(FunctionType{anyLessArgTp, lambdaTy->retTypes}); + FunctionType* newFunc = getMutable(newFuncTypeId); + newFunc->argNames = lambdaTy->argNames; + (*c.astTypes)[expr] = newFuncTypeId; + } + continue; + } const FunctionType* expectedLambdaTy = get(expectedArgTy); - const FunctionType* lambdaTy = get(actualArgTy); const AstExprFunction* lambdaExpr = expr->as(); if (expectedLambdaTy && lambdaTy && lambdaExpr) @@ -1899,7 +1976,7 @@ bool ConstraintSolver::tryDispatchHasIndexer( FreeType freeResult{tt->scope, builtinTypes->neverType, builtinTypes->unknownType, Polarity::Mixed}; emplace(constraint, resultType, freeResult); - if (FFlag::LuauNonReentrantGeneralization2) + if (FFlag::LuauNonReentrantGeneralization3) trackInteriorFreeType(constraint->scope, resultType); tt->indexer = TableIndexer{indexType, resultType}; @@ -1980,8 +2057,9 @@ bool ConstraintSolver::tryDispatchHasIndexer( continue; r = follow(r); - if (!get(r)) + if (FFlag::LuauInsertErrorTypesIntoIndexerResult || !get(r)) results.insert(r); + } if (0 == results.size()) @@ -2443,18 +2521,15 @@ bool ConstraintSolver::tryDispatch(const ReduceConstraint& c, NotNulllocation); - if (FFlag::LuauNewTypeFunReductionChecks2) - { - for (TypeId ity : result.irreducibleTypes) - uninhabitedTypeFunctions.insert(ity); - } + for (TypeId ity : result.irreducibleTypes) + uninhabitedTypeFunctions.insert(ity); bool reductionFinished = result.blockedTypes.empty() && result.blockedPacks.empty(); ty = follow(ty); // If we couldn't reduce this type function, stick it in the set! - if (get(ty) && (!FFlag::LuauNewTypeFunReductionChecks2 || !result.irreducibleTypes.find(ty))) + if (get(ty) && !result.irreducibleTypes.find(ty)) typeFunctionsToFinalize[ty] = constraint; if (force || reductionFinished) @@ -2531,6 +2606,83 @@ bool ConstraintSolver::tryDispatch(const EqualityConstraint& c, NotNull recordedTys{nullptr}; + DenseHashSet blockedTys{nullptr}; + + FindAllUnionMembers() + : TypeOnceVisitor(/* skipBoundTypes */ true) + { + } + + bool visit(TypeId ty) override + { + recordedTys.insert(ty); + return false; + } + + bool visit(TypeId ty, const BlockedType&) override + { + blockedTys.insert(ty); + return false; + } + bool visit(TypeId ty, const PendingExpansionType&) override + { + blockedTys.insert(ty); + return false; + } + bool visit(TypeId ty, const FreeType&) override + { + blockedTys.insert(ty); + return false; + } + bool visit(TypeId ty, const TypeFunctionInstanceType&) override + { + blockedTys.insert(ty); + return false; + } + + bool visit(TypeId, const UnionType&) override + { + return true; + } +}; + +bool ConstraintSolver::tryDispatch(const SimplifyConstraint& c, NotNull constraint) +{ + TypeId target = follow(c.ty); + + if (target->persistent || target->owningArena != arena || !is(target)) + { + // If our target ends up being: + // - A persistent union like `false?` + // - A union from another arena + // - Something other than a union type + // Then it's either harmful or useless to fire this constraint, so we exit early. + return true; + } + + FindAllUnionMembers finder; + finder.traverse(target); + if (!finder.blockedTys.empty()) + { + for (TypeId ty : finder.blockedTys) + block(ty, constraint); + return false; + } + TypeId result = builtinTypes->neverType; + for (TypeId ty : finder.recordedTys) + { + ty = follow(ty); + if (ty == target) + continue; + result = simplifyUnion(constraint->scope, constraint->location, result, ty); + } + emplaceType(asMutable(target), result); + return true; +} + bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const IterableConstraint& c, NotNull constraint, bool force) { iteratorTy = follow(iteratorTy); diff --git a/Analysis/src/DataFlowGraph.cpp b/Analysis/src/DataFlowGraph.cpp index 0b419fa4..51476821 100644 --- a/Analysis/src/DataFlowGraph.cpp +++ b/Analysis/src/DataFlowGraph.cpp @@ -14,11 +14,13 @@ LUAU_FASTFLAG(DebugLuauFreezeArena) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAGVARIABLE(LuauPreprocessTypestatedArgument) -LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackTrueReset) LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackNotNull) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) LUAU_FASTFLAGVARIABLE(LuauDoNotAddUpvalueTypesToLocalType) LUAU_FASTFLAGVARIABLE(LuauDfgIfBlocksShouldRespectControlFlow) +LUAU_FASTFLAGVARIABLE(LuauDfgMatchCGScopes) +LUAU_FASTFLAGVARIABLE(LuauDfgAllowUpdatesInLoops) + namespace Luau { @@ -40,18 +42,11 @@ struct PushScope ~PushScope() { - if (FFlag::LuauDfgScopeStackTrueReset) - { - // If somehow this stack has _shrunk_ to be smaller than we expect, - // something very strange has happened. - LUAU_ASSERT(stack.size() > previousSize); - while (stack.size() > previousSize) - stack.pop_back(); - } - else - { + // If somehow this stack has _shrunk_ to be smaller than we expect, + // something very strange has happened. + LUAU_ASSERT(stack.size() > previousSize); + while (stack.size() > previousSize) stack.pop_back(); - } } }; @@ -157,6 +152,9 @@ void DfgScope::inherit(const DfgScope* childScope) bool DfgScope::canUpdateDefinition(Symbol symbol) const { + if (FFlag::LuauDfgAllowUpdatesInLoops) + return true; + for (const DfgScope* current = this; current; current = current->parent) { if (current->bindings.find(symbol)) @@ -170,6 +168,9 @@ bool DfgScope::canUpdateDefinition(Symbol symbol) const bool DfgScope::canUpdateDefinition(DefId def, const std::string& key) const { + if (FFlag::LuauDfgAllowUpdatesInLoops) + return true; + for (const DfgScope* current = this; current; current = current->parent) { if (auto props = current->props.find(def)) @@ -533,40 +534,96 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatIf* i) ControlFlow DataFlowGraphBuilder::visit(AstStatWhile* w) { - // TODO(controlflow): entry point has a back edge from exit point + // FIXME: This is unsound, as it does not consider the _second_ loop + // iteration. Consider something like: + // + // local function f(_: number) end + // local x = 42 + // while math.random () > 0.5 do + // f(x) + // x = "" + // end + // + // While the first iteration is fine, the second iteration would + // allow a string to flow into a position that expects. DfgScope* whileScope = makeChildScope(DfgScope::Loop); + if (FFlag::LuauDfgAllowUpdatesInLoops) { - PushScope ps{scopeStack, whileScope}; - visitExpr(w->condition); - visit(w->body); + + ControlFlow cf; + { + PushScope ps{scopeStack, whileScope}; + visitExpr(w->condition); + cf = visit(w->body); + } + + auto scope = currentScope(); + // If the inner loop unconditioanlly returns or throws we shouldn't + // consume any type state from the loop body. + if (!matches(cf, ControlFlow::Returns | ControlFlow::Throws)) + join(scope, scope, whileScope); + + return ControlFlow::None; } - - if (FFlag::LuauDfgScopeStackNotNull) - currentScope()->inherit(whileScope); else - currentScope_DEPRECATED()->inherit(whileScope); + { + { + PushScope ps{scopeStack, whileScope}; + visitExpr(w->condition); + visit(w->body); + } - return ControlFlow::None; + if (FFlag::LuauDfgScopeStackNotNull) + currentScope()->inherit(whileScope); + else + currentScope_DEPRECATED()->inherit(whileScope); + + return ControlFlow::None; + } } ControlFlow DataFlowGraphBuilder::visit(AstStatRepeat* r) { - // TODO(controlflow): entry point has a back edge from exit point + // See comment in visit(AstStatWhile*): this is unsound as it + // does not consider the _second_ loop iteration. DfgScope* repeatScope = makeChildScope(DfgScope::Loop); + if (FFlag::LuauDfgAllowUpdatesInLoops) { - PushScope ps{scopeStack, repeatScope}; - visitBlockWithoutChildScope(r->body); - visitExpr(r->condition); - } + ControlFlow cf; - if (FFlag::LuauDfgScopeStackNotNull) + { + PushScope ps{scopeStack, repeatScope}; + cf = visitBlockWithoutChildScope(r->body); + visitExpr(r->condition); + } + + // Ultimately: the options for a repeat-until loop are more + // straightforward. currentScope()->inherit(repeatScope); - else - currentScope_DEPRECATED()->inherit(repeatScope); - return ControlFlow::None; + // `repeat` loops will unconditionally fire: if the internal control + // flow is unconditionally a break or continue, then we have linear + // control flow, but if it's throws or returns, then we need to + // return _that_ to the parent. + return matches(cf, ControlFlow::Breaks | ControlFlow::Continues) ? ControlFlow::None : cf; + } + else + { + { + PushScope ps{scopeStack, repeatScope}; + visitBlockWithoutChildScope(r->body); + visitExpr(r->condition); + } + + if (FFlag::LuauDfgScopeStackNotNull) + currentScope()->inherit(repeatScope); + else + currentScope_DEPRECATED()->inherit(repeatScope); + + return ControlFlow::None; + } } ControlFlow DataFlowGraphBuilder::visit(AstStatBreak* b) @@ -635,6 +692,8 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatLocal* l) ControlFlow DataFlowGraphBuilder::visit(AstStatFor* f) { + // See comment in visit(AstStatWhile*): this is unsound as it + // does not consider the _second_ loop iteration. DfgScope* forScope = makeChildScope(DfgScope::Loop); visitExpr(f->from); @@ -642,66 +701,135 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatFor* f) if (f->step) visitExpr(f->step); + if (FFlag::LuauDfgAllowUpdatesInLoops) { - PushScope ps{scopeStack, forScope}; - if (f->var->annotation) - visitType(f->var->annotation); + ControlFlow cf; + { + PushScope ps{scopeStack, forScope}; - DefId def = defArena->freshCell(f->var, f->var->location); - graph.localDefs[f->var] = def; - if (FFlag::LuauDfgScopeStackNotNull) + if (f->var->annotation) + visitType(f->var->annotation); + + DefId def = defArena->freshCell(f->var, f->var->location); + graph.localDefs[f->var] = def; currentScope()->bindings[f->var] = def; - else - currentScope_DEPRECATED()->bindings[f->var] = def; - captures[f->var].allVersions.push_back(def); + captures[f->var].allVersions.push_back(def); - // TODO(controlflow): entry point has a back edge from exit point - visit(f->body); + cf = visit(f->body); + } + + auto scope = currentScope(); + // If the inner loop unconditioanlly returns or throws we shouldn't + // consume any type state from the loop body. + if (!matches(cf, ControlFlow::Returns | ControlFlow::Throws)) + join(scope, scope, forScope); + + return ControlFlow::None; } - - if (FFlag::LuauDfgScopeStackNotNull) - currentScope()->inherit(forScope); else - currentScope_DEPRECATED()->inherit(forScope); + { + { + PushScope ps{scopeStack, forScope}; - return ControlFlow::None; + if (f->var->annotation) + visitType(f->var->annotation); + + DefId def = defArena->freshCell(f->var, f->var->location); + graph.localDefs[f->var] = def; + if (FFlag::LuauDfgScopeStackNotNull) + currentScope()->bindings[f->var] = def; + else + currentScope_DEPRECATED()->bindings[f->var] = def; + captures[f->var].allVersions.push_back(def); + + // TODO(controlflow): entry point has a back edge from exit point + visit(f->body); + } + + if (FFlag::LuauDfgScopeStackNotNull) + currentScope()->inherit(forScope); + else + currentScope_DEPRECATED()->inherit(forScope); + + return ControlFlow::None; + } } ControlFlow DataFlowGraphBuilder::visit(AstStatForIn* f) { DfgScope* forScope = makeChildScope(DfgScope::Loop); + if (FFlag::LuauDfgAllowUpdatesInLoops) { - PushScope ps{scopeStack, forScope}; - for (AstLocal* local : f->vars) + ControlFlow cf; { - if (local->annotation) - visitType(local->annotation); + PushScope ps{scopeStack, forScope}; - DefId def = defArena->freshCell(local, local->location); - graph.localDefs[local] = def; - if (FFlag::LuauDfgScopeStackNotNull) - currentScope()->bindings[local] = def; - else - currentScope_DEPRECATED()->bindings[local] = def; - captures[local].allVersions.push_back(def); + for (AstLocal* local : f->vars) + { + if (local->annotation) + visitType(local->annotation); + + DefId def = defArena->freshCell(local, local->location); + graph.localDefs[local] = def; + if (FFlag::LuauDfgScopeStackNotNull) + currentScope()->bindings[local] = def; + else + currentScope_DEPRECATED()->bindings[local] = def; + captures[local].allVersions.push_back(def); + } + + // TODO(controlflow): entry point has a back edge from exit point + // We're gonna need a `visitExprList` and `visitVariadicExpr` (function calls and `...`) + for (AstExpr* e : f->values) + visitExpr(e); + + cf = visit(f->body); } - // TODO(controlflow): entry point has a back edge from exit point - // We're gonna need a `visitExprList` and `visitVariadicExpr` (function calls and `...`) - for (AstExpr* e : f->values) - visitExpr(e); + auto scope = currentScope(); + // If the inner loop unconditioanlly returns or throws we shouldn't + // consume any type state from the loop body. + if (!matches(cf, ControlFlow::Returns | ControlFlow::Throws)) + join(scope, scope, forScope); - visit(f->body); + return ControlFlow::None; } - if (FFlag::LuauDfgScopeStackNotNull) - currentScope()->inherit(forScope); else - currentScope_DEPRECATED()->inherit(forScope); + { + { + PushScope ps{scopeStack, forScope}; - return ControlFlow::None; + for (AstLocal* local : f->vars) + { + if (local->annotation) + visitType(local->annotation); + + DefId def = defArena->freshCell(local, local->location); + graph.localDefs[local] = def; + if (FFlag::LuauDfgScopeStackNotNull) + currentScope()->bindings[local] = def; + else + currentScope_DEPRECATED()->bindings[local] = def; + captures[local].allVersions.push_back(def); + } + + // TODO(controlflow): entry point has a back edge from exit point + // We're gonna need a `visitExprList` and `visitVariadicExpr` (function calls and `...`) + for (AstExpr* e : f->values) + visitExpr(e); + + visit(f->body); + } + if (FFlag::LuauDfgScopeStackNotNull) + currentScope()->inherit(forScope); + else + currentScope_DEPRECATED()->inherit(forScope); + + return ControlFlow::None; + } } ControlFlow DataFlowGraphBuilder::visit(AstStatAssign* a) @@ -1112,9 +1240,18 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprUnary* u) DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprBinary* b) { visitExpr(b->left); - visitExpr(b->right); + if (FFlag::LuauDfgMatchCGScopes) + { + PushScope _{scopeStack, makeChildScope()}; + visitExpr(b->right); + return {defArena->freshCell(Symbol{}, b->location), nullptr}; + } + else + { + visitExpr(b->right); + return {defArena->freshCell(Symbol{}, b->location), nullptr}; + } - return {defArena->freshCell(Symbol{}, b->location), nullptr}; } DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprTypeAssertion* t) @@ -1127,9 +1264,29 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprTypeAssertion* t) DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprIfElse* i) { - visitExpr(i->condition); - visitExpr(i->trueExpr); - visitExpr(i->falseExpr); + if (FFlag::LuauDfgMatchCGScopes) + { + // In the constraint generator, the condition, consequence, and + // alternative all have distinct scopes. + { + PushScope _{scopeStack, makeChildScope()}; + visitExpr(i->condition); + } + { + PushScope _{scopeStack, makeChildScope()}; + visitExpr(i->trueExpr); + } + { + PushScope _{scopeStack, makeChildScope()}; + visitExpr(i->falseExpr); + } + } + else + { + visitExpr(i->condition); + visitExpr(i->trueExpr); + visitExpr(i->falseExpr); + } return {defArena->freshCell(Symbol{}, i->location), nullptr}; } diff --git a/Analysis/src/EmbeddedBuiltinDefinitions.cpp b/Analysis/src/EmbeddedBuiltinDefinitions.cpp index 65b7ef00..52e36d82 100644 --- a/Analysis/src/EmbeddedBuiltinDefinitions.cpp +++ b/Analysis/src/EmbeddedBuiltinDefinitions.cpp @@ -7,7 +7,7 @@ LUAU_FASTFLAG(LuauTypeFunOptional) namespace Luau { -static const std::string kBuiltinDefinitionBaseSrc = R"BUILTIN_SRC( +static constexpr const char* kBuiltinDefinitionBaseSrc = R"BUILTIN_SRC( @checked declare function require(target: any): any @@ -60,7 +60,7 @@ declare function unpack(tab: {V}, i: number?, j: number?): ...V )BUILTIN_SRC"; -static const std::string kBuiltinDefinitionBit32Src = R"BUILTIN_SRC( +static constexpr const char* kBuiltinDefinitionBit32Src = R"BUILTIN_SRC( declare bit32: { band: @checked (...number) -> number, @@ -82,7 +82,7 @@ declare bit32: { )BUILTIN_SRC"; -static const std::string kBuiltinDefinitionMathSrc = R"BUILTIN_SRC( +static constexpr const char* kBuiltinDefinitionMathSrc = R"BUILTIN_SRC( declare math: { frexp: @checked (n: number) -> (number, number), @@ -133,7 +133,7 @@ declare math: { )BUILTIN_SRC"; -static const std::string kBuiltinDefinitionOsSrc = R"BUILTIN_SRC( +static constexpr const char* kBuiltinDefinitionOsSrc = R"BUILTIN_SRC( type DateTypeArg = { year: number, @@ -166,7 +166,7 @@ declare os: { )BUILTIN_SRC"; -static const std::string kBuiltinDefinitionCoroutineSrc = R"BUILTIN_SRC( +static constexpr const char* kBuiltinDefinitionCoroutineSrc = R"BUILTIN_SRC( declare coroutine: { create: (f: (A...) -> R...) -> thread, @@ -181,7 +181,7 @@ declare coroutine: { )BUILTIN_SRC"; -static const std::string kBuiltinDefinitionTableSrc = R"BUILTIN_SRC( +static constexpr const char* kBuiltinDefinitionTableSrc = R"BUILTIN_SRC( declare table: { concat: (t: {V}, sep: string?, i: number?, j: number?) -> string, @@ -207,7 +207,7 @@ declare table: { )BUILTIN_SRC"; -static const std::string kBuiltinDefinitionDebugSrc = R"BUILTIN_SRC( +static constexpr const char* kBuiltinDefinitionDebugSrc = R"BUILTIN_SRC( declare debug: { info: ((thread: thread, level: number, options: string) -> ...any) & ((level: number, options: string) -> ...any) & ((func: (A...) -> R1..., options: string) -> ...any), @@ -216,7 +216,7 @@ declare debug: { )BUILTIN_SRC"; -static const std::string kBuiltinDefinitionUtf8Src = R"BUILTIN_SRC( +static constexpr const char* kBuiltinDefinitionUtf8Src = R"BUILTIN_SRC( declare utf8: { char: @checked (...number) -> string, @@ -229,7 +229,7 @@ declare utf8: { )BUILTIN_SRC"; -static const std::string kBuiltinDefinitionBufferSrc = R"BUILTIN_SRC( +static constexpr const char* kBuiltinDefinitionBufferSrc = R"BUILTIN_SRC( --- Buffer API declare buffer: { create: @checked (size: number) -> buffer, @@ -262,7 +262,7 @@ declare buffer: { )BUILTIN_SRC"; -static const std::string kBuiltinDefinitionVectorSrc = (FFlag::LuauDeclareExternType) +static const char* const kBuiltinDefinitionVectorSrc = (FFlag::LuauDeclareExternType) ? R"BUILTIN_SRC( -- While vector would have been better represented as a built-in primitive type, type solver extern type handling covers most of the properties @@ -340,7 +340,7 @@ std::string getBuiltinDefinitionSource() } // TODO: split into separate tagged unions when the new solver can appropriately handle that. -static const std::string kBuiltinDefinitionTypeMethodSrc = R"BUILTIN_SRC( +static constexpr const char* kBuiltinDefinitionTypeMethodSrc = R"BUILTIN_SRC( export type type = { tag: "nil" | "unknown" | "never" | "any" | "boolean" | "number" | "string" | "buffer" | "thread" | @@ -393,7 +393,7 @@ export type type = { )BUILTIN_SRC"; -static const std::string kBuiltinDefinitionTypesLibSrc = R"BUILTIN_SRC( +static constexpr const char* kBuiltinDefinitionTypesLibSrc = R"BUILTIN_SRC( declare types: { unknown: type, @@ -416,7 +416,7 @@ declare types: { } )BUILTIN_SRC"; -static const std::string kBuiltinDefinitionTypesLibWithOptionalSrc = R"BUILTIN_SRC( +static constexpr const char* kBuiltinDefinitionTypesLibWithOptionalSrc = R"BUILTIN_SRC( declare types: { unknown: type, diff --git a/Analysis/src/Error.cpp b/Analysis/src/Error.cpp index 7fcbcd81..6a18370a 100644 --- a/Analysis/src/Error.cpp +++ b/Analysis/src/Error.cpp @@ -839,6 +839,11 @@ struct ErrorConverter return result; } + + std::string operator()(const UnexpectedArrayLikeTableItem&) const + { + return "Unexpected array-like table item: the indexer key type of this table is not `number`."; + } }; struct InvalidNameChecker @@ -1432,6 +1437,9 @@ void copyError(T& e, TypeArena& destArena, CloneState& cloneState) for (auto& ty : e.cause) ty = clone(ty); } + else if constexpr (std::is_same_v) + { + } else if constexpr (std::is_same_v) { } diff --git a/Analysis/src/FragmentAutocomplete.cpp b/Analysis/src/FragmentAutocomplete.cpp index 9a03d89c..3b606964 100644 --- a/Analysis/src/FragmentAutocomplete.cpp +++ b/Analysis/src/FragmentAutocomplete.cpp @@ -33,8 +33,6 @@ LUAU_FASTFLAGVARIABLE(LuauBetterCursorInCommentDetection) LUAU_FASTFLAGVARIABLE(LuauAllFreeTypesHaveScopes) LUAU_FASTFLAGVARIABLE(LuauPersistConstraintGenerationScopes) LUAU_FASTFLAGVARIABLE(LuauCloneTypeAliasBindings) -LUAU_FASTFLAG(LuauUserTypeFunTypecheck) -LUAU_FASTFLAGVARIABLE(LuauFragmentNoTypeFunEval) LUAU_FASTFLAGVARIABLE(LuauBetterScopeSelection) LUAU_FASTFLAGVARIABLE(LuauBlockDiffFragmentSelection) LUAU_FASTFLAGVARIABLE(LuauFragmentAcMemoryLeak) @@ -833,7 +831,7 @@ FragmentAutocompleteAncestryResult findAncestryForFragmentParse_DEPRECATED(AstSt } } } - else if (auto typeFun = stat->as(); typeFun && FFlag::LuauUserTypeFunTypecheck) + else if (auto typeFun = stat->as()) { if (typeFun->location.contains(cursorPos)) { @@ -1107,8 +1105,7 @@ FragmentTypeCheckResult typecheckFragment_( /// User defined type functions runtime TypeFunctionRuntime typeFunctionRuntime(iceHandler, NotNull{&limits}); - if (FFlag::LuauFragmentNoTypeFunEval || FFlag::LuauUserTypeFunTypecheck) - typeFunctionRuntime.allowEvaluation = false; + typeFunctionRuntime.allowEvaluation = false; /// Create a DataFlowGraph just for the surrounding context DataFlowGraph dfg = DataFlowGraphBuilder::build(root, NotNull{&incrementalModule->defArena}, NotNull{&incrementalModule->keyArena}, iceHandler); @@ -1141,13 +1138,10 @@ FragmentTypeCheckResult typecheckFragment_( freshChildOfNearestScope->interiorFreeTypePacks.emplace(); cg.rootScope = freshChildOfNearestScope.get(); - if (FFlag::LuauUserTypeFunTypecheck) - { - // Create module-local scope for the type function environment - ScopePtr localTypeFunctionScope = std::make_shared(cg.typeFunctionScope); - localTypeFunctionScope->location = root->location; - cg.typeFunctionRuntime->rootScope = localTypeFunctionScope; - } + // Create module-local scope for the type function environment + ScopePtr localTypeFunctionScope = std::make_shared(cg.typeFunctionScope); + localTypeFunctionScope->location = root->location; + cg.typeFunctionRuntime->rootScope = localTypeFunctionScope; reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneAndSquashScopeStart); cloneTypesFromFragment( diff --git a/Analysis/src/Frontend.cpp b/Analysis/src/Frontend.cpp index c31b2d55..285403e8 100644 --- a/Analysis/src/Frontend.cpp +++ b/Analysis/src/Frontend.cpp @@ -39,7 +39,6 @@ LUAU_FASTINT(LuauTarjanChildLimit) LUAU_FASTFLAG(LuauInferInNoCheckMode) LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3) LUAU_FASTFLAG(LuauSolverV2) -LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauRethrowKnownExceptions, false) LUAU_FASTFLAG(DebugLuauGreedyGeneralization) LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson) LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile) @@ -47,8 +46,6 @@ LUAU_FASTFLAGVARIABLE(DebugLuauForbidInternalTypes) LUAU_FASTFLAGVARIABLE(DebugLuauForceStrictMode) LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode) -LUAU_FASTFLAG(LuauTypeFunResultInAutocomplete) - namespace Luau { @@ -1098,27 +1095,13 @@ void Frontend::performQueueItemTask(std::shared_ptr state, { BuildQueueItem& item = state->buildQueueItems[itemPos]; - if (DFFlag::LuauRethrowKnownExceptions) + try { - try - { - checkBuildQueueItem(item); - } - catch (const Luau::InternalCompilerError&) - { - item.exception = std::current_exception(); - } + checkBuildQueueItem(item); } - else + catch (const Luau::InternalCompilerError&) { - try - { - checkBuildQueueItem(item); - } - catch (...) - { - item.exception = std::current_exception(); - } + item.exception = std::current_exception(); } { @@ -1402,7 +1385,7 @@ ModulePtr check( SimplifierPtr simplifier = newSimplifier(NotNull{&result->internalTypes}, builtinTypes); TypeFunctionRuntime typeFunctionRuntime{iceHandler, NotNull{&limits}}; - typeFunctionRuntime.allowEvaluation = FFlag::LuauTypeFunResultInAutocomplete || sourceModule.parseErrors.empty(); + typeFunctionRuntime.allowEvaluation = true; ConstraintGenerator cg{ result, diff --git a/Analysis/src/Generalization.cpp b/Analysis/src/Generalization.cpp index a977b033..41da29cf 100644 --- a/Analysis/src/Generalization.cpp +++ b/Analysis/src/Generalization.cpp @@ -16,7 +16,7 @@ LUAU_FASTFLAG(DebugLuauGreedyGeneralization) -LUAU_FASTFLAGVARIABLE(LuauNonReentrantGeneralization2) +LUAU_FASTFLAGVARIABLE(LuauNonReentrantGeneralization3) namespace Luau { @@ -469,7 +469,7 @@ struct FreeTypeSearcher : TypeVisitor bool visit(TypeId ty, const FreeType& ft) override { - if (FFlag::LuauNonReentrantGeneralization2) + if (FFlag::LuauNonReentrantGeneralization3) { if (!subsumes(scope, ft.scope)) return true; @@ -520,7 +520,7 @@ struct FreeTypeSearcher : TypeVisitor if ((tt.state == TableState::Free || tt.state == TableState::Unsealed) && subsumes(scope, tt.scope)) { - if (FFlag::LuauNonReentrantGeneralization2) + if (FFlag::LuauNonReentrantGeneralization3) unsealedTables.insert(ty); else { @@ -559,7 +559,7 @@ struct FreeTypeSearcher : TypeVisitor if (tt.indexer) { - if (FFlag::LuauNonReentrantGeneralization2) + if (FFlag::LuauNonReentrantGeneralization3) { // {[K]: V} is equivalent to three functions: get, set, and iterate // @@ -617,7 +617,7 @@ struct FreeTypeSearcher : TypeVisitor if (!subsumes(scope, ftp.scope)) return true; - if (FFlag::LuauNonReentrantGeneralization2) + if (FFlag::LuauNonReentrantGeneralization3) { GeneralizationParams& params = typePacks[tp]; ++params.useCount; @@ -1159,7 +1159,7 @@ struct RemoveType : Substitution // NOLINT for (TypeId ty : ut) { - if (ty != needle) + if (ty != needle && !is(ty)) newParts.insert(ty); } @@ -1180,7 +1180,7 @@ struct RemoveType : Substitution // NOLINT for (TypeId ty : it) { - if (ty != needle) + if (ty != needle && !is(ty)) newParts.insert(ty); } @@ -1301,7 +1301,23 @@ GeneralizationResult generalizeType( if (follow(ub) != freeTy) emplaceType(asMutable(freeTy), ub); else if (!isWithinFunction || params.useCount == 1) - emplaceType(asMutable(freeTy), builtinTypes->unknownType); + { + // If we have some free type: + // + // A <: 'b < C + // + // We can approximately generalize this to the intersection of it's + // bounds, taking care to avoid constructing a degenerate + // union or intersection by clipping the free type from the upper + // and lower bounds, then also cleaning the resulting intersection. + std::optional removedLb = removeType(arena, builtinTypes, ft->lowerBound, freeTy); + if (!removedLb) + return {std::nullopt, false, true}; + std::optional cleanedTy = removeType(arena, builtinTypes, arena->addType(IntersectionType{{*removedLb, ub}}), freeTy); + if (!cleanedTy) + return {std::nullopt, false, true}; + emplaceType(asMutable(freeTy), *cleanedTy); + } else { // if the upper bound is the type in question, we don't actually have an upper bound. @@ -1374,7 +1390,7 @@ std::optional generalize( FreeTypeSearcher fts{scope, cachedTypes}; fts.traverse(ty); - if (FFlag::LuauNonReentrantGeneralization2) + if (FFlag::LuauNonReentrantGeneralization3) { FunctionType* functionTy = getMutable(ty); auto pushGeneric = [&](TypeId t) diff --git a/Analysis/src/InferPolarity.cpp b/Analysis/src/InferPolarity.cpp index 2ce8f508..6319a214 100644 --- a/Analysis/src/InferPolarity.cpp +++ b/Analysis/src/InferPolarity.cpp @@ -5,7 +5,7 @@ #include "Luau/Scope.h" #include "Luau/VisitType.h" -LUAU_FASTFLAG(LuauNonReentrantGeneralization2) +LUAU_FASTFLAG(LuauNonReentrantGeneralization3) namespace Luau { @@ -133,7 +133,7 @@ struct InferPolarity : TypeVisitor template static void inferGenericPolarities_(NotNull arena, NotNull scope, TID ty) { - if (!FFlag::LuauNonReentrantGeneralization2) + if (!FFlag::LuauNonReentrantGeneralization3) return; InferPolarity infer{arena, scope}; diff --git a/Analysis/src/IostreamHelpers.cpp b/Analysis/src/IostreamHelpers.cpp index f78e2143..591b1366 100644 --- a/Analysis/src/IostreamHelpers.cpp +++ b/Analysis/src/IostreamHelpers.cpp @@ -248,6 +248,8 @@ static void errorToString(std::ostream& stream, const T& err) stream << " } } "; } + else if constexpr (std::is_same_v) + stream << "UnexpectedArrayLikeTableItem {}"; else static_assert(always_false_v, "Non-exhaustive type switch"); } diff --git a/Analysis/src/Linter.cpp b/Analysis/src/Linter.cpp index 3ac099ca..3bfa2f9e 100644 --- a/Analysis/src/Linter.cpp +++ b/Analysis/src/Linter.cpp @@ -3240,7 +3240,7 @@ static void fillBuiltinGlobals(LintContext& context, const AstNameTable& names, } } -static const char* fuzzyMatch(std::string_view str, const char** array, size_t size) +static const char* fuzzyMatch(std::string_view str, const char* const* array, size_t size) { if (FInt::LuauSuggestionDistance == 0) return nullptr; diff --git a/Analysis/src/NonStrictTypeChecker.cpp b/Analysis/src/NonStrictTypeChecker.cpp index fd2c1d0f..336d0290 100644 --- a/Analysis/src/NonStrictTypeChecker.cpp +++ b/Analysis/src/NonStrictTypeChecker.cpp @@ -22,8 +22,6 @@ LUAU_FASTFLAG(DebugLuauMagicTypes) -LUAU_FASTFLAGVARIABLE(LuauNonStrictVisitorImprovements) -LUAU_FASTFLAGVARIABLE(LuauNewNonStrictWarnOnUnknownGlobals) LUAU_FASTFLAGVARIABLE(LuauNewNonStrictVisitTypes2) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) @@ -368,26 +366,16 @@ struct NonStrictTypeChecker NonStrictContext visit(AstStatWhile* whileStatement) { - if (FFlag::LuauNonStrictVisitorImprovements) - { - NonStrictContext condition = visit(whileStatement->condition, ValueContext::RValue); - NonStrictContext body = visit(whileStatement->body); - return NonStrictContext::disjunction(builtinTypes, arena, condition, body); - } - else - return {}; + NonStrictContext condition = visit(whileStatement->condition, ValueContext::RValue); + NonStrictContext body = visit(whileStatement->body); + return NonStrictContext::disjunction(builtinTypes, arena, condition, body); } NonStrictContext visit(AstStatRepeat* repeatStatement) { - if (FFlag::LuauNonStrictVisitorImprovements) - { - NonStrictContext body = visit(repeatStatement->body); - NonStrictContext condition = visit(repeatStatement->condition, ValueContext::RValue); - return NonStrictContext::disjunction(builtinTypes, arena, body, condition); - } - else - return {}; + NonStrictContext body = visit(repeatStatement->body); + NonStrictContext condition = visit(repeatStatement->condition, ValueContext::RValue); + return NonStrictContext::disjunction(builtinTypes, arena, body, condition); } NonStrictContext visit(AstStatBreak* breakStatement) @@ -402,13 +390,10 @@ struct NonStrictTypeChecker NonStrictContext visit(AstStatReturn* returnStatement) { - if (FFlag::LuauNonStrictVisitorImprovements) - { - // TODO: this is believing existing code, but i'm not sure if this makes sense - // for how the contexts are handled - for (AstExpr* expr : returnStatement->list) - visit(expr, ValueContext::RValue); - } + // TODO: this is believing existing code, but i'm not sure if this makes sense + // for how the contexts are handled + for (AstExpr* expr : returnStatement->list) + visit(expr, ValueContext::RValue); return {}; } @@ -430,21 +415,14 @@ struct NonStrictTypeChecker if (FFlag::LuauNewNonStrictVisitTypes2) visit(forStatement->var->annotation); - if (FFlag::LuauNonStrictVisitorImprovements) - { - // TODO: throwing out context based on same principle as existing code? - if (forStatement->from) - visit(forStatement->from, ValueContext::RValue); - if (forStatement->to) - visit(forStatement->to, ValueContext::RValue); - if (forStatement->step) - visit(forStatement->step, ValueContext::RValue); - return visit(forStatement->body); - } - else - { - return {}; - } + // TODO: throwing out context based on same principle as existing code? + if (forStatement->from) + visit(forStatement->from, ValueContext::RValue); + if (forStatement->to) + visit(forStatement->to, ValueContext::RValue); + if (forStatement->step) + visit(forStatement->step, ValueContext::RValue); + return visit(forStatement->body); } NonStrictContext visit(AstStatForIn* forInStatement) @@ -455,38 +433,25 @@ struct NonStrictTypeChecker visit(var->annotation); } - if (FFlag::LuauNonStrictVisitorImprovements) - { - for (AstExpr* rhs : forInStatement->values) - visit(rhs, ValueContext::RValue); - return visit(forInStatement->body); - } - else - { - return {}; - } + for (AstExpr* rhs : forInStatement->values) + visit(rhs, ValueContext::RValue); + return visit(forInStatement->body); } NonStrictContext visit(AstStatAssign* assign) { - if (FFlag::LuauNonStrictVisitorImprovements) - { - for (AstExpr* lhs : assign->vars) - visit(lhs, ValueContext::LValue); - for (AstExpr* rhs : assign->values) - visit(rhs, ValueContext::RValue); - } + for (AstExpr* lhs : assign->vars) + visit(lhs, ValueContext::LValue); + for (AstExpr* rhs : assign->values) + visit(rhs, ValueContext::RValue); return {}; } NonStrictContext visit(AstStatCompoundAssign* compoundAssign) { - if (FFlag::LuauNonStrictVisitorImprovements) - { - visit(compoundAssign->var, ValueContext::LValue); - visit(compoundAssign->value, ValueContext::RValue); - } + visit(compoundAssign->var, ValueContext::LValue); + visit(compoundAssign->value, ValueContext::RValue); return {}; } @@ -556,13 +521,10 @@ struct NonStrictTypeChecker NonStrictContext visit(AstStatError* error) { - if (FFlag::LuauNonStrictVisitorImprovements) - { - for (AstStat* stat : error->statements) - visit(stat); - for (AstExpr* expr : error->expressions) - visit(expr, ValueContext::RValue); - } + for (AstStat* stat : error->statements) + visit(stat); + for (AstExpr* expr : error->expressions) + visit(expr, ValueContext::RValue); return {}; } @@ -617,10 +579,7 @@ struct NonStrictTypeChecker NonStrictContext visit(AstExprGroup* group, ValueContext context) { - if (FFlag::LuauNonStrictVisitorImprovements) - return visit(group->expr, context); - else - return {}; + return visit(group->expr, context); } NonStrictContext visit(AstExprConstantNil* expr) @@ -650,17 +609,14 @@ struct NonStrictTypeChecker NonStrictContext visit(AstExprGlobal* global, ValueContext context) { - if (FFlag::LuauNewNonStrictWarnOnUnknownGlobals) - { - // We don't file unknown symbols for LValues. - if (context == ValueContext::LValue) - return {}; + // We don't file unknown symbols for LValues. + if (context == ValueContext::LValue) + return {}; - NotNull scope = stack.back(); - if (!scope->lookup(global->name)) - { - reportError(UnknownSymbol{global->name.value, UnknownSymbol::Binding}, global->location); - } + NotNull scope = stack.back(); + if (!scope->lookup(global->name)) + { + reportError(UnknownSymbol{global->name.value, UnknownSymbol::Binding}, global->location); } return {}; @@ -783,24 +739,17 @@ struct NonStrictTypeChecker NonStrictContext visit(AstExprIndexName* indexName, ValueContext context) { - if (FFlag::LuauNonStrictVisitorImprovements) - return visit(indexName->expr, context); - else - return {}; + return visit(indexName->expr, context); } NonStrictContext visit(AstExprIndexExpr* indexExpr, ValueContext context) { - if (FFlag::LuauNonStrictVisitorImprovements) - { - NonStrictContext expr = visit(indexExpr->expr, context); - NonStrictContext index = visit(indexExpr->index, ValueContext::RValue); - return NonStrictContext::disjunction(builtinTypes, arena, expr, index); - } - else - return {}; + NonStrictContext expr = visit(indexExpr->expr, context); + NonStrictContext index = visit(indexExpr->index, ValueContext::RValue); + return NonStrictContext::disjunction(builtinTypes, arena, expr, index); } + NonStrictContext visit(AstExprFunction* exprFn) { // TODO: should a function being used as an expression generate a context without the arguments? @@ -840,14 +789,11 @@ struct NonStrictTypeChecker NonStrictContext visit(AstExprTable* table) { - if (FFlag::LuauNonStrictVisitorImprovements) + for (auto [_, key, value] : table->items) { - for (auto [_, key, value] : table->items) - { - if (key) - visit(key, ValueContext::RValue); - visit(value, ValueContext::RValue); - } + if (key) + visit(key, ValueContext::RValue); + visit(value, ValueContext::RValue); } return {}; @@ -855,22 +801,14 @@ struct NonStrictTypeChecker NonStrictContext visit(AstExprUnary* unary) { - if (FFlag::LuauNonStrictVisitorImprovements) - return visit(unary->expr, ValueContext::RValue); - else - return {}; + return visit(unary->expr, ValueContext::RValue); } NonStrictContext visit(AstExprBinary* binary) { - if (FFlag::LuauNonStrictVisitorImprovements) - { - NonStrictContext lhs = visit(binary->left, ValueContext::RValue); - NonStrictContext rhs = visit(binary->right, ValueContext::RValue); - return NonStrictContext::disjunction(builtinTypes, arena, lhs, rhs); - } - else - return {}; + NonStrictContext lhs = visit(binary->left, ValueContext::RValue); + NonStrictContext rhs = visit(binary->right, ValueContext::RValue); + return NonStrictContext::disjunction(builtinTypes, arena, lhs, rhs); } NonStrictContext visit(AstExprTypeAssertion* typeAssertion) @@ -878,10 +816,7 @@ struct NonStrictTypeChecker if (FFlag::LuauNewNonStrictVisitTypes2) visit(typeAssertion->annotation); - if (FFlag::LuauNonStrictVisitorImprovements) - return visit(typeAssertion->expr, ValueContext::RValue); - else - return {}; + return visit(typeAssertion->expr, ValueContext::RValue); } NonStrictContext visit(AstExprIfElse* ifElse) @@ -894,22 +829,16 @@ struct NonStrictTypeChecker NonStrictContext visit(AstExprInterpString* interpString) { - if (FFlag::LuauNonStrictVisitorImprovements) - { - for (AstExpr* expr : interpString->expressions) - visit(expr, ValueContext::RValue); - } + for (AstExpr* expr : interpString->expressions) + visit(expr, ValueContext::RValue); return {}; } NonStrictContext visit(AstExprError* error) { - if (FFlag::LuauNonStrictVisitorImprovements) - { - for (AstExpr* expr : error->expressions) - visit(expr, ValueContext::RValue); - } + for (AstExpr* expr : error->expressions) + visit(expr, ValueContext::RValue); return {}; } diff --git a/Analysis/src/Normalize.cpp b/Analysis/src/Normalize.cpp index cadc9244..6597f62e 100644 --- a/Analysis/src/Normalize.cpp +++ b/Analysis/src/Normalize.cpp @@ -21,8 +21,6 @@ LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000) LUAU_FASTINTVARIABLE(LuauNormalizeIntersectionLimit, 200) LUAU_FASTINTVARIABLE(LuauNormalizeUnionLimit, 100) LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAGVARIABLE(LuauFixInfiniteRecursionInNormalization) -LUAU_FASTFLAGVARIABLE(LuauNormalizationCatchMetatableCycles) namespace Luau { @@ -2606,60 +2604,22 @@ std::optional Normalizer::intersectionOfTables(TypeId here, TypeId there { if (tprop.readTy.has_value()) { - if (FFlag::LuauFixInfiniteRecursionInNormalization) - { - TypeId ty = simplifyIntersection(builtinTypes, NotNull{arena}, *hprop.readTy, *tprop.readTy).result; + TypeId ty = simplifyIntersection(builtinTypes, NotNull{arena}, *hprop.readTy, *tprop.readTy).result; - // If any property is going to get mapped to `never`, we can just call the entire table `never`. - // Since this check is syntactic, we may sometimes miss simplifying tables with complex uninhabited properties. - // Prior versions of this code attempted to do this semantically using the normalization machinery, but this - // mistakenly causes infinite loops when giving more complex recursive table types. As it stands, this approach - // will continue to scale as simplification is improved, but we may wish to reintroduce the semantic approach - // once we have revisited the usage of seen sets systematically (and possibly with some additional guarding to recognize - // when types are infinitely-recursive with non-pointer identical instances of them, or some guard to prevent that - // construction altogether). See also: `gh1632_no_infinite_recursion_in_normalization` - if (get(ty)) - return {builtinTypes->neverType}; + // If any property is going to get mapped to `never`, we can just call the entire table `never`. + // Since this check is syntactic, we may sometimes miss simplifying tables with complex uninhabited properties. + // Prior versions of this code attempted to do this semantically using the normalization machinery, but this + // mistakenly causes infinite loops when giving more complex recursive table types. As it stands, this approach + // will continue to scale as simplification is improved, but we may wish to reintroduce the semantic approach + // once we have revisited the usage of seen sets systematically (and possibly with some additional guarding to recognize + // when types are infinitely-recursive with non-pointer identical instances of them, or some guard to prevent that + // construction altogether). See also: `gh1632_no_infinite_recursion_in_normalization` + if (get(ty)) + return {builtinTypes->neverType}; - prop.readTy = ty; - hereSubThere &= (ty == hprop.readTy); - thereSubHere &= (ty == tprop.readTy); - } - else - { - // if the intersection of the read types of a property is uninhabited, the whole table is `never`. - // We've seen these table prop elements before and we're about to ask if their intersection - // is inhabited - - auto pair1 = std::pair{*hprop.readTy, *tprop.readTy}; - auto pair2 = std::pair{*tprop.readTy, *hprop.readTy}; - if (seenTablePropPairs.contains(pair1) || seenTablePropPairs.contains(pair2)) - { - seenTablePropPairs.erase(pair1); - seenTablePropPairs.erase(pair2); - return {builtinTypes->neverType}; - } - else - { - seenTablePropPairs.insert(pair1); - seenTablePropPairs.insert(pair2); - } - - // FIXME(ariel): this is being added in a flag removal, so not changing the semantics here, but worth noting that this - // fresh `seenSet` is definitely a bug. we already have `seenSet` from the parameter that _should_ have been used here. - Set seenSet{nullptr}; - NormalizationResult res = isIntersectionInhabited(*hprop.readTy, *tprop.readTy, seenTablePropPairs, seenSet); - - seenTablePropPairs.erase(pair1); - seenTablePropPairs.erase(pair2); - if (NormalizationResult::True != res) - return {builtinTypes->neverType}; - - TypeId ty = simplifyIntersection(builtinTypes, NotNull{arena}, *hprop.readTy, *tprop.readTy).result; - prop.readTy = ty; - hereSubThere &= (ty == hprop.readTy); - thereSubHere &= (ty == tprop.readTy); - } + prop.readTy = ty; + hereSubThere &= (ty == hprop.readTy); + thereSubHere &= (ty == tprop.readTy); } else { @@ -3352,21 +3312,6 @@ NormalizationResult Normalizer::intersectNormalWithTy( return NormalizationResult::True; } -void makeTableShared_DEPRECATED(TypeId ty) -{ - ty = follow(ty); - if (auto tableTy = getMutable(ty)) - { - for (auto& [_, prop] : tableTy->props) - prop.makeShared(); - } - else if (auto metatableTy = get(ty)) - { - makeTableShared_DEPRECATED(metatableTy->metatable); - makeTableShared_DEPRECATED(metatableTy->table); - } -} - void makeTableShared(TypeId ty, DenseHashSet& seen) { ty = follow(ty); @@ -3490,10 +3435,7 @@ TypeId Normalizer::typeFromNormal(const NormalizedType& norm) result.reserve(result.size() + norm.tables.size()); for (auto table : norm.tables) { - if (FFlag::LuauNormalizationCatchMetatableCycles) - makeTableShared(table); - else - makeTableShared_DEPRECATED(table); + makeTableShared(table); result.push_back(table); } } diff --git a/Analysis/src/OverloadResolution.cpp b/Analysis/src/OverloadResolution.cpp index 342aee95..0443ba6c 100644 --- a/Analysis/src/OverloadResolution.cpp +++ b/Analysis/src/OverloadResolution.cpp @@ -11,6 +11,7 @@ #include "Luau/Unifier2.h" LUAU_FASTFLAGVARIABLE(LuauArityMismatchOnUndersaturatedUnknownArguments) +LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2) namespace Luau { @@ -287,6 +288,25 @@ std::pair OverloadResolver::checkOverload_ return {Analysis::Ok, {}}; } + + if (FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2) + { + if (reason.subPath == TypePath::Path{{TypePath::PackField::Arguments, TypePath::PackField::Tail}} && reason.superPath == justArguments) + { + // We have an arity mismatch if the argument tail is a generic type pack + if (auto fnArgs = get(fn->argTypes)) + { + // TODO: Determine whether arguments have incorrect type, incorrect count, or both (CLI-152070) + if (get(fnArgs->tail)) + { + auto [minParams, optMaxParams] = getParameterExtents(TxnLog::empty(), fn->argTypes); + TypeError error{fnExpr->location, CountMismatch{minParams, optMaxParams, args->head.size(), CountMismatch::Arg}}; + + return {Analysis::ArityMismatch, {error}}; + } + } + } + } } ErrorVec errors; diff --git a/Analysis/src/Substitution.cpp b/Analysis/src/Substitution.cpp index 27a889fc..2b9cbe53 100644 --- a/Analysis/src/Substitution.cpp +++ b/Analysis/src/Substitution.cpp @@ -10,7 +10,6 @@ LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTINTVARIABLE(LuauTarjanPreallocationSize, 256) -LUAU_FASTFLAG(LuauSyntheticErrors) namespace Luau { @@ -57,25 +56,17 @@ static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log) } else if constexpr (std::is_same_v) { - if (FFlag::LuauSyntheticErrors) - { - LUAU_ASSERT(ty->persistent || a.synthetic); + LUAU_ASSERT(ty->persistent || a.synthetic); - if (ty->persistent) - return ty; - - // While this code intentionally works (and clones) even if `a.synthetic` is `std::nullopt`, - // we still assert above because we consider it a bug to have a non-persistent error type - // without any associated metadata. We should always use the persistent version in such cases. - ErrorType clone = ErrorType{}; - clone.synthetic = a.synthetic; - return dest.addType(clone); - } - else - { - LUAU_ASSERT(ty->persistent); + if (ty->persistent) return ty; - } + + // While this code intentionally works (and clones) even if `a.synthetic` is `std::nullopt`, + // we still assert above because we consider it a bug to have a non-persistent error type + // without any associated metadata. We should always use the persistent version in such cases. + ErrorType clone = ErrorType{}; + clone.synthetic = a.synthetic; + return dest.addType(clone); } else if constexpr (std::is_same_v) { diff --git a/Analysis/src/Subtyping.cpp b/Analysis/src/Subtyping.cpp index 53b1028e..45d1b7fe 100644 --- a/Analysis/src/Subtyping.cpp +++ b/Analysis/src/Subtyping.cpp @@ -17,12 +17,10 @@ #include "Luau/TypePath.h" #include "Luau/TypeUtils.h" -#include - LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity) LUAU_FASTINTVARIABLE(LuauSubtypingReasoningLimit, 100) -LUAU_FASTFLAGVARIABLE(LuauSubtypingEnableReasoningLimit) LUAU_FASTFLAGVARIABLE(LuauSubtypeGenericsAndNegations) +LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2) namespace Luau { @@ -101,7 +99,7 @@ static SubtypingReasonings mergeReasonings(const SubtypingReasonings& a, const S result.insert(r); } - if (FFlag::LuauSubtypingEnableReasoningLimit && result.size() >= size_t(FInt::LuauSubtypingReasoningLimit)) + if (result.size() >= size_t(FInt::LuauSubtypingReasoningLimit)) return result; } @@ -120,7 +118,7 @@ static SubtypingReasonings mergeReasonings(const SubtypingReasonings& a, const S result.insert(r); } - if (FFlag::LuauSubtypingEnableReasoningLimit && result.size() >= size_t(FInt::LuauSubtypingReasoningLimit)) + if (result.size() >= size_t(FInt::LuauSubtypingReasoningLimit)) return result; } @@ -815,7 +813,9 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId // (X) -> () <: (T) -> () // Possible optimization: If headSize == 0 then we can just use subTp as-is. - std::vector headSlice(begin(superHead), begin(superHead) + headSize); + std::vector headSlice = FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2 + ? std::vector(begin(superHead) + headSize, end(superHead)) + : std::vector(begin(superHead), begin(superHead) + headSize); TypePackId superTailPack = arena->addTypePack(std::move(headSlice), superTail); if (TypePackId* other = env.getMappedPackBounds(*subTail)) @@ -870,12 +870,17 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId // (X...) -> () <: (T) -> () // Possible optimization: If headSize == 0 then we can just use subTp as-is. - std::vector headSlice(begin(subHead), begin(subHead) + headSize); + std::vector headSlice = FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2 + ? std::vector(begin(subHead) + headSize, end(subHead)) + : std::vector(begin(subHead), begin(subHead) + headSize); TypePackId subTailPack = arena->addTypePack(std::move(headSlice), subTail); if (TypePackId* other = env.getMappedPackBounds(*superTail)) // TODO: TypePath can't express "slice of a pack + its tail". - results.push_back(isContravariantWith(env, subTailPack, *other, scope).withSuperComponent(TypePath::PackField::Tail)); + if (FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2) + results.push_back(isCovariantWith(env, subTailPack, *other, scope).withSuperComponent(TypePath::PackField::Tail)); + else + results.push_back(isContravariantWith(env, subTailPack, *other, scope).withSuperComponent(TypePath::PackField::Tail)); else env.mappedGenericPacks.try_insert(*superTail, subTailPack); diff --git a/Analysis/src/TableLiteralInference.cpp b/Analysis/src/TableLiteralInference.cpp index 90340bcc..bf7d3096 100644 --- a/Analysis/src/TableLiteralInference.cpp +++ b/Analysis/src/TableLiteralInference.cpp @@ -14,17 +14,11 @@ #include "Luau/Unifier2.h" LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceElideAssert) +LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck) namespace Luau { -// A fast approximation of subTy <: superTy -static bool fastIsSubtype(TypeId subTy, TypeId superTy) -{ - Relation r = relate(superTy, subTy); - return r == Relation::Coincident || r == Relation::Superset; -} - static bool isRecord(const AstExprTable::Item& item) { if (item.kind == AstExprTable::Item::Record) @@ -35,80 +29,6 @@ static bool isRecord(const AstExprTable::Item& item) return false; } -static std::optional extractMatchingTableType(std::vector& tables, TypeId exprType, NotNull builtinTypes) -{ - if (tables.empty()) - return std::nullopt; - - const TableType* exprTable = get(follow(exprType)); - if (!exprTable) - return std::nullopt; - - size_t tableCount = 0; - std::optional firstTable; - - for (TypeId ty : tables) - { - ty = follow(ty); - if (auto tt = get(ty)) - { - // If the expected table has a key whose type is a string or boolean - // singleton and the corresponding exprType property does not match, - // then skip this table. - - if (!firstTable) - firstTable = ty; - ++tableCount; - - for (const auto& [name, expectedProp] : tt->props) - { - if (!expectedProp.readTy) - continue; - - const TypeId expectedType = follow(*expectedProp.readTy); - - auto st = get(expectedType); - if (!st) - continue; - - auto it = exprTable->props.find(name); - if (it == exprTable->props.end()) - continue; - - const auto& [_name, exprProp] = *it; - - if (!exprProp.readTy) - continue; - - const TypeId propType = follow(*exprProp.readTy); - - const FreeType* ft = get(propType); - - if (ft && get(ft->lowerBound)) - { - if (fastIsSubtype(builtinTypes->booleanType, ft->upperBound) && fastIsSubtype(expectedType, builtinTypes->booleanType)) - { - return ty; - } - - if (fastIsSubtype(builtinTypes->stringType, ft->upperBound) && fastIsSubtype(expectedType, ft->lowerBound)) - { - return ty; - } - } - } - } - } - - if (tableCount == 1) - { - LUAU_ASSERT(firstTable); - return firstTable; - } - - return std::nullopt; -} - TypeId matchLiteralType( NotNull> astTypes, NotNull> astExpectedTypes, @@ -257,7 +177,6 @@ TypeId matchLiteralType( if (tt) { TypeId res = matchLiteralType(astTypes, astExpectedTypes, builtinTypes, arena, unifier, subtyping, *tt, exprType, expr, toBlock); - parts.push_back(res); return arena->addType(UnionType{std::move(parts)}); } diff --git a/Analysis/src/ToString.cpp b/Analysis/src/ToString.cpp index 525b2144..6d192896 100644 --- a/Analysis/src/ToString.cpp +++ b/Analysis/src/ToString.cpp @@ -22,7 +22,6 @@ LUAU_FASTFLAGVARIABLE(LuauEnableDenseTableAlias) LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAGVARIABLE(LuauSyntheticErrors) LUAU_FASTFLAGVARIABLE(LuauStringPartLengthLimit) /* @@ -1092,7 +1091,7 @@ struct TypeStringifier { state.result.error = true; - if (FFlag::LuauSyntheticErrors && tv.synthetic) + if (tv.synthetic) { state.emit("*error-type<"); stringify(*tv.synthetic); @@ -1278,7 +1277,7 @@ struct TypePackStringifier { state.result.error = true; - if (FFlag::LuauSyntheticErrors && error.synthetic) + if (error.synthetic) { state.emit("*"); stringify(*error.synthetic); @@ -1965,6 +1964,8 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts) return "equality: " + tos(c.resultType) + " ~ " + tos(c.assignmentType); else if constexpr (std::is_same_v) return "table_check " + tos(c.expectedType) + " :> " + tos(c.exprType); + else if constexpr (std::is_same_v) + return "simplify " + tos(c.ty); else static_assert(always_false_v, "Non-exhaustive constraint switch"); }; diff --git a/Analysis/src/Transpiler.cpp b/Analysis/src/Transpiler.cpp index 9ff31fff..d9489c9d 100644 --- a/Analysis/src/Transpiler.cpp +++ b/Analysis/src/Transpiler.cpp @@ -10,8 +10,6 @@ #include #include -LUAU_FASTFLAG(LuauStoreCSTData2) -LUAU_FASTFLAG(LuauFixFunctionWithAttributesStartLocation) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) LUAU_FASTFLAG(LuauStoreLocalAnnotationColonPositions) @@ -166,17 +164,7 @@ struct StringWriter : Writer void symbol(std::string_view s) override { - if (FFlag::LuauStoreCSTData2) - { - write(s); - } - else - { - if (isDigit(lastChar) && s[0] == '.') - space(); - - write(s); - } + write(s); } void literal(std::string_view s) override @@ -256,7 +244,7 @@ public: first = !first; else { - if (FFlag::LuauStoreCSTData2 && commaPosition) + if (commaPosition) { writer.advance(*commaPosition); commaPosition++; @@ -308,1024 +296,6 @@ private: size_t idx = 0; }; -struct Printer_DEPRECATED -{ - explicit Printer_DEPRECATED(Writer& writer) - : writer(writer) - { - } - - bool writeTypes = false; - Writer& writer; - - void visualize(const AstLocal& local) - { - advance(local.location.begin); - - writer.identifier(local.name.value); - if (writeTypes && local.annotation) - { - writer.symbol(":"); - visualizeTypeAnnotation(*local.annotation); - } - } - - void visualizeTypePackAnnotation(const AstTypePack& annotation, bool forVarArg, bool unconditionallyParenthesize = true) - { - advance(annotation.location.begin); - if (const AstTypePackVariadic* variadicTp = annotation.as()) - { - if (!forVarArg) - writer.symbol("..."); - - visualizeTypeAnnotation(*variadicTp->variadicType); - } - else if (const AstTypePackGeneric* genericTp = annotation.as()) - { - writer.symbol(genericTp->genericName.value); - writer.symbol("..."); - } - else if (const AstTypePackExplicit* explicitTp = annotation.as()) - { - LUAU_ASSERT(!forVarArg); - visualizeTypeList(explicitTp->typeList, unconditionallyParenthesize); - } - else - { - LUAU_ASSERT(!"Unknown TypePackAnnotation kind"); - } - } - - void visualizeTypeList(const AstTypeList& list, bool unconditionallyParenthesize) - { - size_t typeCount = list.types.size + (list.tailType != nullptr ? 1 : 0); - if (typeCount == 0) - { - writer.symbol("("); - writer.symbol(")"); - } - else if (typeCount == 1) - { - bool shouldParenthesize = unconditionallyParenthesize && (list.types.size == 0 || !list.types.data[0]->is()); - if (shouldParenthesize) - writer.symbol("("); - - // Only variadic tail - if (list.types.size == 0) - { - visualizeTypePackAnnotation(*list.tailType, false); - } - else - { - visualizeTypeAnnotation(*list.types.data[0]); - } - - if (shouldParenthesize) - writer.symbol(")"); - } - else - { - writer.symbol("("); - - bool first = true; - for (const auto& el : list.types) - { - if (first) - first = false; - else - writer.symbol(","); - - visualizeTypeAnnotation(*el); - } - - if (list.tailType) - { - writer.symbol(","); - visualizeTypePackAnnotation(*list.tailType, false); - } - - writer.symbol(")"); - } - } - - bool isIntegerish(double d) - { - if (d <= std::numeric_limits::max() && d >= std::numeric_limits::min()) - return double(int(d)) == d && !(d == 0.0 && signbit(d)); - else - return false; - } - - void visualize(AstExpr& expr) - { - advance(expr.location.begin); - - if (const auto& a = expr.as()) - { - writer.symbol("("); - visualize(*a->expr); - writer.symbol(")"); - } - else if (expr.is()) - { - writer.keyword("nil"); - } - else if (const auto& a = expr.as()) - { - if (a->value) - writer.keyword("true"); - else - writer.keyword("false"); - } - else if (const auto& a = expr.as()) - { - if (isinf(a->value)) - { - if (a->value > 0) - writer.literal("1e500"); - else - writer.literal("-1e500"); - } - else if (isnan(a->value)) - writer.literal("0/0"); - else - { - if (isIntegerish(a->value)) - writer.literal(std::to_string(int(a->value))); - else - { - char buffer[100]; - size_t len = snprintf(buffer, sizeof(buffer), "%.17g", a->value); - writer.literal(std::string_view{buffer, len}); - } - } - } - else if (const auto& a = expr.as()) - { - writer.string(std::string_view(a->value.data, a->value.size)); - } - else if (const auto& a = expr.as()) - { - writer.identifier(a->local->name.value); - } - else if (const auto& a = expr.as()) - { - writer.identifier(a->name.value); - } - else if (expr.is()) - { - writer.symbol("..."); - } - else if (const auto& a = expr.as()) - { - visualize(*a->func); - writer.symbol("("); - - bool first = true; - for (const auto& arg : a->args) - { - if (first) - first = false; - else - writer.symbol(","); - - visualize(*arg); - } - - writer.symbol(")"); - } - else if (const auto& a = expr.as()) - { - visualize(*a->expr); - writer.symbol(std::string(1, a->op)); - writer.write(a->index.value); - } - else if (const auto& a = expr.as()) - { - visualize(*a->expr); - writer.symbol("["); - visualize(*a->index); - writer.symbol("]"); - } - else if (const auto& a = expr.as()) - { - writer.keyword("function"); - visualizeFunctionBody(*a); - } - else if (const auto& a = expr.as()) - { - writer.symbol("{"); - - bool first = true; - - for (const auto& item : a->items) - { - if (first) - first = false; - else - writer.symbol(","); - - switch (item.kind) - { - case AstExprTable::Item::List: - break; - - case AstExprTable::Item::Record: - { - const auto& value = item.key->as()->value; - advance(item.key->location.begin); - writer.identifier(std::string_view(value.data, value.size)); - writer.maybeSpace(item.value->location.begin, 1); - writer.symbol("="); - } - break; - - case AstExprTable::Item::General: - { - writer.symbol("["); - visualize(*item.key); - writer.symbol("]"); - writer.maybeSpace(item.value->location.begin, 1); - writer.symbol("="); - } - break; - - default: - LUAU_ASSERT(!"Unknown table item kind"); - } - - advance(item.value->location.begin); - visualize(*item.value); - } - - // Decrement endPos column so that we advance to before the closing `}` brace before writing, rather than after it - Position endPos = expr.location.end; - if (endPos.column > 0) - --endPos.column; - - advance(endPos); - - writer.symbol("}"); - advance(expr.location.end); - } - else if (const auto& a = expr.as()) - { - switch (a->op) - { - case AstExprUnary::Not: - writer.keyword("not"); - break; - case AstExprUnary::Minus: - writer.symbol("-"); - break; - case AstExprUnary::Len: - writer.symbol("#"); - break; - } - visualize(*a->expr); - } - else if (const auto& a = expr.as()) - { - visualize(*a->left); - - switch (a->op) - { - case AstExprBinary::Add: - case AstExprBinary::Sub: - case AstExprBinary::Mul: - case AstExprBinary::Div: - case AstExprBinary::FloorDiv: - case AstExprBinary::Mod: - case AstExprBinary::Pow: - case AstExprBinary::CompareLt: - case AstExprBinary::CompareGt: - writer.maybeSpace(a->right->location.begin, 2); - writer.symbol(toString(a->op)); - break; - case AstExprBinary::Concat: - case AstExprBinary::CompareNe: - case AstExprBinary::CompareEq: - case AstExprBinary::CompareLe: - case AstExprBinary::CompareGe: - case AstExprBinary::Or: - writer.maybeSpace(a->right->location.begin, 3); - writer.keyword(toString(a->op)); - break; - case AstExprBinary::And: - writer.maybeSpace(a->right->location.begin, 4); - writer.keyword(toString(a->op)); - break; - default: - LUAU_ASSERT(!"Unknown Op"); - } - - visualize(*a->right); - } - else if (const auto& a = expr.as()) - { - visualize(*a->expr); - - if (writeTypes) - { - writer.maybeSpace(a->annotation->location.begin, 2); - writer.symbol("::"); - visualizeTypeAnnotation(*a->annotation); - } - } - else if (const auto& a = expr.as()) - { - writer.keyword("if"); - visualize(*a->condition); - writer.keyword("then"); - visualize(*a->trueExpr); - writer.keyword("else"); - visualize(*a->falseExpr); - } - else if (const auto& a = expr.as()) - { - writer.symbol("`"); - - size_t index = 0; - - for (const auto& string : a->strings) - { - writer.write(escape(std::string_view(string.data, string.size), /* escapeForInterpString = */ true)); - - if (index < a->expressions.size) - { - writer.symbol("{"); - visualize(*a->expressions.data[index]); - writer.symbol("}"); - } - - index++; - } - - writer.symbol("`"); - } - else if (const auto& a = expr.as()) - { - writer.symbol("(error-expr"); - - for (size_t i = 0; i < a->expressions.size; i++) - { - writer.symbol(i == 0 ? ": " : ", "); - visualize(*a->expressions.data[i]); - } - - writer.symbol(")"); - } - else - { - LUAU_ASSERT(!"Unknown AstExpr"); - } - } - - void writeEnd(const Location& loc) - { - Position endPos = loc.end; - if (endPos.column >= 3) - endPos.column -= 3; - advance(endPos); - writer.keyword("end"); - } - - void advance(const Position& newPos) - { - writer.advance(newPos); - } - - void visualize(AstStat& program) - { - advance(program.location.begin); - - if (const auto& block = program.as()) - { - writer.keyword("do"); - for (const auto& s : block->body) - visualize(*s); - writeEnd(program.location); - } - else if (const auto& a = program.as()) - { - writer.keyword("if"); - visualizeElseIf(*a); - } - else if (const auto& a = program.as()) - { - writer.keyword("while"); - visualize(*a->condition); - writer.keyword("do"); - visualizeBlock(*a->body); - writeEnd(program.location); - } - else if (const auto& a = program.as()) - { - writer.keyword("repeat"); - visualizeBlock(*a->body); - if (a->condition->location.begin.column > 5) - writer.advance(Position{a->condition->location.begin.line, a->condition->location.begin.column - 6}); - writer.keyword("until"); - visualize(*a->condition); - } - else if (program.is()) - writer.keyword("break"); - else if (program.is()) - writer.keyword("continue"); - else if (const auto& a = program.as()) - { - writer.keyword("return"); - - bool first = true; - for (const auto& expr : a->list) - { - if (first) - first = false; - else - writer.symbol(","); - visualize(*expr); - } - } - else if (const auto& a = program.as()) - { - visualize(*a->expr); - } - else if (const auto& a = program.as()) - { - writer.keyword("local"); - - bool first = true; - for (const auto& local : a->vars) - { - if (first) - first = false; - else - writer.write(","); - - visualize(*local); - } - - first = true; - for (const auto& value : a->values) - { - if (first) - { - first = false; - writer.maybeSpace(value->location.begin, 2); - writer.symbol("="); - } - else - writer.symbol(","); - - visualize(*value); - } - } - else if (const auto& a = program.as()) - { - writer.keyword("for"); - - visualize(*a->var); - writer.symbol("="); - visualize(*a->from); - writer.symbol(","); - visualize(*a->to); - if (a->step) - { - writer.symbol(","); - visualize(*a->step); - } - writer.keyword("do"); - visualizeBlock(*a->body); - - writeEnd(program.location); - } - else if (const auto& a = program.as()) - { - writer.keyword("for"); - - bool first = true; - for (const auto& var : a->vars) - { - if (first) - first = false; - else - writer.symbol(","); - - visualize(*var); - } - - writer.keyword("in"); - - first = true; - for (const auto& val : a->values) - { - if (first) - first = false; - else - writer.symbol(","); - - visualize(*val); - } - - writer.keyword("do"); - - visualizeBlock(*a->body); - - writeEnd(program.location); - } - else if (const auto& a = program.as()) - { - bool first = true; - for (const auto& var : a->vars) - { - if (first) - first = false; - else - writer.symbol(","); - visualize(*var); - } - - first = true; - for (const auto& value : a->values) - { - if (first) - { - writer.maybeSpace(value->location.begin, 1); - writer.symbol("="); - first = false; - } - else - writer.symbol(","); - - visualize(*value); - } - } - else if (const auto& a = program.as()) - { - visualize(*a->var); - - switch (a->op) - { - case AstExprBinary::Add: - writer.maybeSpace(a->value->location.begin, 2); - writer.symbol("+="); - break; - case AstExprBinary::Sub: - writer.maybeSpace(a->value->location.begin, 2); - writer.symbol("-="); - break; - case AstExprBinary::Mul: - writer.maybeSpace(a->value->location.begin, 2); - writer.symbol("*="); - break; - case AstExprBinary::Div: - writer.maybeSpace(a->value->location.begin, 2); - writer.symbol("/="); - break; - case AstExprBinary::FloorDiv: - writer.maybeSpace(a->value->location.begin, 2); - writer.symbol("//="); - break; - case AstExprBinary::Mod: - writer.maybeSpace(a->value->location.begin, 2); - writer.symbol("%="); - break; - case AstExprBinary::Pow: - writer.maybeSpace(a->value->location.begin, 2); - writer.symbol("^="); - break; - case AstExprBinary::Concat: - writer.maybeSpace(a->value->location.begin, 3); - writer.symbol("..="); - break; - default: - LUAU_ASSERT(!"Unexpected compound assignment op"); - } - - visualize(*a->value); - } - else if (const auto& a = program.as()) - { - writer.keyword("function"); - visualize(*a->name); - visualizeFunctionBody(*a->func); - } - else if (const auto& a = program.as()) - { - writer.keyword("local function"); - advance(a->name->location.begin); - writer.identifier(a->name->name.value); - visualizeFunctionBody(*a->func); - } - else if (const auto& a = program.as()) - { - if (writeTypes) - { - if (a->exported) - writer.keyword("export"); - - writer.keyword("type"); - writer.identifier(a->name.value); - if (a->generics.size > 0 || a->genericPacks.size > 0) - { - writer.symbol("<"); - CommaSeparatorInserter comma(writer); - - for (auto o : a->generics) - { - comma(); - - writer.advance(o->location.begin); - writer.identifier(o->name.value); - - if (o->defaultValue) - { - writer.maybeSpace(o->defaultValue->location.begin, 2); - writer.symbol("="); - visualizeTypeAnnotation(*o->defaultValue); - } - } - - for (auto o : a->genericPacks) - { - comma(); - - writer.advance(o->location.begin); - writer.identifier(o->name.value); - writer.symbol("..."); - - if (o->defaultValue) - { - writer.maybeSpace(o->defaultValue->location.begin, 2); - writer.symbol("="); - visualizeTypePackAnnotation(*o->defaultValue, false); - } - } - - writer.symbol(">"); - } - writer.maybeSpace(a->type->location.begin, 2); - writer.symbol("="); - visualizeTypeAnnotation(*a->type); - } - } - else if (const auto& t = program.as()) - { - if (writeTypes) - { - writer.keyword("type function"); - writer.identifier(t->name.value); - visualizeFunctionBody(*t->body); - } - } - else if (const auto& a = program.as()) - { - writer.symbol("(error-stat"); - - for (size_t i = 0; i < a->expressions.size; i++) - { - writer.symbol(i == 0 ? ": " : ", "); - visualize(*a->expressions.data[i]); - } - - for (size_t i = 0; i < a->statements.size; i++) - { - writer.symbol(i == 0 && a->expressions.size == 0 ? ": " : ", "); - visualize(*a->statements.data[i]); - } - - writer.symbol(")"); - } - else - { - LUAU_ASSERT(!"Unknown AstStat"); - } - - if (program.hasSemicolon) - writer.symbol(";"); - } - - void visualizeFunctionBody(AstExprFunction& func) - { - if (func.generics.size > 0 || func.genericPacks.size > 0) - { - CommaSeparatorInserter comma(writer); - writer.symbol("<"); - for (const auto& o : func.generics) - { - comma(); - - writer.advance(o->location.begin); - writer.identifier(o->name.value); - } - for (const auto& o : func.genericPacks) - { - comma(); - - writer.advance(o->location.begin); - writer.identifier(o->name.value); - writer.symbol("..."); - } - writer.symbol(">"); - } - - writer.symbol("("); - CommaSeparatorInserter comma(writer); - - for (size_t i = 0; i < func.args.size; ++i) - { - AstLocal* local = func.args.data[i]; - - comma(); - - advance(local->location.begin); - writer.identifier(local->name.value); - if (writeTypes && local->annotation) - { - writer.symbol(":"); - visualizeTypeAnnotation(*local->annotation); - } - } - - if (func.vararg) - { - comma(); - advance(func.varargLocation.begin); - writer.symbol("..."); - - if (func.varargAnnotation) - { - writer.symbol(":"); - visualizeTypePackAnnotation(*func.varargAnnotation, true); - } - } - - writer.symbol(")"); - - if (writeTypes && (FFlag::LuauStoreReturnTypesAsPackOnAst ? func.returnAnnotation != nullptr : func.returnAnnotation_DEPRECATED.has_value())) - { - writer.symbol(":"); - writer.space(); - - if (FFlag::LuauStoreReturnTypesAsPackOnAst) - visualizeTypePackAnnotation(*func.returnAnnotation, false, false); - else - visualizeTypeList(*func.returnAnnotation_DEPRECATED, false); - } - - visualizeBlock(*func.body); - writeEnd(func.location); - } - - void visualizeBlock(AstStatBlock& block) - { - for (const auto& s : block.body) - visualize(*s); - writer.advance(block.location.end); - } - - void visualizeBlock(AstStat& stat) - { - if (AstStatBlock* block = stat.as()) - visualizeBlock(*block); - else - LUAU_ASSERT(!"visualizeBlock was expecting an AstStatBlock"); - } - - void visualizeElseIf(AstStatIf& elseif) - { - visualize(*elseif.condition); - writer.keyword("then"); - visualizeBlock(*elseif.thenbody); - - if (elseif.elsebody == nullptr) - { - writeEnd(elseif.location); - } - else if (auto elseifelseif = elseif.elsebody->as()) - { - writer.keyword("elseif"); - visualizeElseIf(*elseifelseif); - } - else - { - writer.keyword("else"); - - visualizeBlock(*elseif.elsebody); - writeEnd(elseif.location); - } - } - - void visualizeTypeAnnotation(const AstType& typeAnnotation) - { - advance(typeAnnotation.location.begin); - if (const auto& a = typeAnnotation.as()) - { - if (a->prefix) - { - writer.write(a->prefix->value); - writer.symbol("."); - } - - writer.write(a->name.value); - if (a->parameters.size > 0 || a->hasParameterList) - { - CommaSeparatorInserter comma(writer); - writer.symbol("<"); - for (auto o : a->parameters) - { - comma(); - - if (o.type) - visualizeTypeAnnotation(*o.type); - else - visualizeTypePackAnnotation(*o.typePack, false); - } - - writer.symbol(">"); - } - } - else if (const auto& a = typeAnnotation.as()) - { - if (a->generics.size > 0 || a->genericPacks.size > 0) - { - CommaSeparatorInserter comma(writer); - writer.symbol("<"); - for (const auto& o : a->generics) - { - comma(); - - writer.advance(o->location.begin); - writer.identifier(o->name.value); - } - for (const auto& o : a->genericPacks) - { - comma(); - - writer.advance(o->location.begin); - writer.identifier(o->name.value); - writer.symbol("..."); - } - writer.symbol(">"); - } - - { - visualizeTypeList(a->argTypes, true); - } - - writer.symbol("->"); - if (FFlag::LuauStoreReturnTypesAsPackOnAst) - visualizeTypePackAnnotation(*a->returnTypes, false); - else - visualizeTypeList(a->returnTypes_DEPRECATED, true); - } - else if (const auto& a = typeAnnotation.as()) - { - AstTypeReference* indexType = a->indexer ? a->indexer->indexType->as() : nullptr; - - if (a->props.size == 0 && indexType && indexType->name == "number") - { - writer.symbol("{"); - visualizeTypeAnnotation(*a->indexer->resultType); - writer.symbol("}"); - } - else - { - CommaSeparatorInserter comma(writer); - - writer.symbol("{"); - - for (std::size_t i = 0; i < a->props.size; ++i) - { - comma(); - advance(a->props.data[i].location.begin); - writer.identifier(a->props.data[i].name.value); - if (a->props.data[i].type) - { - writer.symbol(":"); - visualizeTypeAnnotation(*a->props.data[i].type); - } - } - if (a->indexer) - { - comma(); - writer.symbol("["); - visualizeTypeAnnotation(*a->indexer->indexType); - writer.symbol("]"); - writer.symbol(":"); - visualizeTypeAnnotation(*a->indexer->resultType); - } - writer.symbol("}"); - } - } - else if (auto a = typeAnnotation.as()) - { - writer.keyword("typeof"); - writer.symbol("("); - visualize(*a->expr); - writer.symbol(")"); - } - else if (const auto& a = typeAnnotation.as()) - { - if (a->types.size == 2) - { - AstType* l = a->types.data[0]; - AstType* r = a->types.data[1]; - - auto lta = l->as(); - if (lta && lta->name == "nil" && !r->is()) - std::swap(l, r); - - // it's still possible that we had a (T | U) or (T | nil) and not (nil | T) - auto rta = r->as(); - if (rta && rta->name == "nil") - { - bool wrap = l->as() || l->as(); - - if (wrap) - writer.symbol("("); - - visualizeTypeAnnotation(*l); - - if (wrap) - writer.symbol(")"); - - writer.symbol("?"); - return; - } - } - - for (size_t i = 0; i < a->types.size; ++i) - { - if (a->types.data[i]->is()) - { - writer.symbol("?"); - continue; - } - - if (i > 0) - { - writer.maybeSpace(a->types.data[i]->location.begin, 2); - writer.symbol("|"); - } - - bool wrap = a->types.data[i]->as() || a->types.data[i]->as(); - - if (wrap) - writer.symbol("("); - - visualizeTypeAnnotation(*a->types.data[i]); - - if (wrap) - writer.symbol(")"); - } - } - else if (const auto& a = typeAnnotation.as()) - { - for (size_t i = 0; i < a->types.size; ++i) - { - if (i > 0) - { - writer.maybeSpace(a->types.data[i]->location.begin, 2); - writer.symbol("&"); - } - - bool wrap = a->types.data[i]->as() || a->types.data[i]->as(); - - if (wrap) - writer.symbol("("); - - visualizeTypeAnnotation(*a->types.data[i]); - - if (wrap) - writer.symbol(")"); - } - } - else if (const auto& a = typeAnnotation.as()) - { - writer.symbol("("); - visualizeTypeAnnotation(*a->type); - writer.symbol(")"); - } - else if (const auto& a = typeAnnotation.as()) - { - writer.keyword(a->value ? "true" : "false"); - } - else if (const auto& a = typeAnnotation.as()) - { - writer.string(std::string_view(a->value.data, a->value.size)); - } - else if (typeAnnotation.is()) - { - writer.symbol("%error-type%"); - } - else - { - LUAU_ASSERT(!"Unknown AstType"); - } - } -}; - struct Printer { explicit Printer(Writer& writer, CstNodeMap cstNodeMap) @@ -1490,8 +460,7 @@ struct Printer void visualize(AstExpr& expr) { - if (!expr.is() || FFlag::LuauFixFunctionWithAttributesStartLocation) - advance(expr.location.begin); + advance(expr.location.begin); if (const auto& a = expr.as()) { @@ -1627,15 +596,8 @@ struct Printer { for (const auto& attribute : a->attributes) visualizeAttribute(*attribute); - if (FFlag::LuauFixFunctionWithAttributesStartLocation) - { - if (const auto cstNode = lookupCstNode(a)) - advance(cstNode->functionKeywordPosition); - } - else - { - advance(a->location.begin); - } + if (const auto cstNode = lookupCstNode(a)) + advance(cstNode->functionKeywordPosition); writer.keyword("function"); visualizeFunctionBody(*a); } @@ -1887,8 +849,7 @@ struct Printer void visualize(AstStat& program) { - if ((!program.is() && !program.is()) || FFlag::LuauFixFunctionWithAttributesStartLocation) - advance(program.location.begin); + advance(program.location.begin); if (const auto& block = program.as()) { @@ -2143,15 +1104,8 @@ struct Printer { for (const auto& attribute : a->func->attributes) visualizeAttribute(*attribute); - if (FFlag::LuauFixFunctionWithAttributesStartLocation) - { - if (const auto cstNode = lookupCstNode(a)) - advance(cstNode->functionKeywordPosition); - } - else - { - advance(a->location.begin); - } + if (const auto cstNode = lookupCstNode(a)) + advance(cstNode->functionKeywordPosition); writer.keyword("function"); visualize(*a->name); visualizeFunctionBody(*a->func); @@ -2162,15 +1116,9 @@ struct Printer visualizeAttribute(*attribute); const auto cstNode = lookupCstNode(a); - if (FFlag::LuauFixFunctionWithAttributesStartLocation) - { - if (cstNode) - advance(cstNode->localKeywordPosition); - } - else - { - advance(a->location.begin); - } + + if (cstNode) + advance(cstNode->localKeywordPosition); writer.keyword("local"); @@ -2314,8 +1262,7 @@ struct Printer if (program.hasSemicolon) { - if (FFlag::LuauStoreCSTData2) - advanceBefore(program.location.end, 1); + advanceBefore(program.location.end, 1); writer.symbol(";"); } } @@ -2902,30 +1849,15 @@ std::string toString(AstNode* node) StringWriter writer; writer.pos = node->location.begin; - if (FFlag::LuauStoreCSTData2) - { - Printer printer(writer, CstNodeMap{nullptr}); - printer.writeTypes = true; + Printer printer(writer, CstNodeMap{nullptr}); + printer.writeTypes = true; - if (auto statNode = node->asStat()) - printer.visualize(*statNode); - else if (auto exprNode = node->asExpr()) - printer.visualize(*exprNode); - else if (auto typeNode = node->asType()) - printer.visualizeTypeAnnotation(*typeNode); - } - else - { - Printer_DEPRECATED printer(writer); - printer.writeTypes = true; - - if (auto statNode = node->asStat()) - printer.visualize(*statNode); - else if (auto exprNode = node->asExpr()) - printer.visualize(*exprNode); - else if (auto typeNode = node->asType()) - printer.visualizeTypeAnnotation(*typeNode); - } + if (auto statNode = node->asStat()) + printer.visualize(*statNode); + else if (auto exprNode = node->asExpr()) + printer.visualize(*exprNode); + else if (auto typeNode = node->asType()) + printer.visualizeTypeAnnotation(*typeNode); return writer.str(); } @@ -2938,38 +1870,21 @@ void dump(AstNode* node) std::string transpile(AstStatBlock& block, const CstNodeMap& cstNodeMap) { StringWriter writer; - if (FFlag::LuauStoreCSTData2) - { - Printer(writer, cstNodeMap).visualizeBlock(block); - } - else - { - Printer_DEPRECATED(writer).visualizeBlock(block); - } + Printer(writer, cstNodeMap).visualizeBlock(block); return writer.str(); } std::string transpileWithTypes(AstStatBlock& block, const CstNodeMap& cstNodeMap) { StringWriter writer; - if (FFlag::LuauStoreCSTData2) - { - Printer printer(writer, cstNodeMap); - printer.writeTypes = true; - printer.visualizeBlock(block); - } - else - { - Printer_DEPRECATED printer(writer); - printer.writeTypes = true; - printer.visualizeBlock(block); - } + Printer printer(writer, cstNodeMap); + printer.writeTypes = true; + printer.visualizeBlock(block); return writer.str(); } std::string transpileWithTypes(AstStatBlock& block) { - // TODO: remove this interface? return transpileWithTypes(block, CstNodeMap{nullptr}); } diff --git a/Analysis/src/TypeAttach.cpp b/Analysis/src/TypeAttach.cpp index 176bdb17..bc605be1 100644 --- a/Analysis/src/TypeAttach.cpp +++ b/Analysis/src/TypeAttach.cpp @@ -13,7 +13,6 @@ #include -LUAU_FASTFLAG(LuauStoreCSTData2) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) static char* allocateString(Luau::Allocator& allocator, std::string_view contents) @@ -308,8 +307,7 @@ public: std::optional* arg = &argNames.data[i++]; if (el) - new (arg) - std::optional(AstArgumentName(AstName(el->name.c_str()), FFlag::LuauStoreCSTData2 ? Location() : el->location)); + new (arg) std::optional(AstArgumentName(AstName(el->name.c_str()), Location())); else new (arg) std::optional(); } diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index d132a411..0dfed541 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -30,11 +30,12 @@ LUAU_FASTFLAG(DebugLuauMagicTypes) -LUAU_FASTFLAG(LuauUserTypeFunTypecheck) LUAU_FASTFLAGVARIABLE(LuauTypeCheckerAcceptNumberConcats) LUAU_FASTFLAGVARIABLE(LuauTypeCheckerStricterIndexCheck) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) LUAU_FASTFLAGVARIABLE(LuauReportSubtypingErrors) +LUAU_FASTFLAGVARIABLE(LuauSkipMalformedTypeAliases) +LUAU_FASTFLAGVARIABLE(LuauTableLiteralSubtypeSpecificCheck) namespace Luau { @@ -710,14 +711,77 @@ void TypeChecker2::visit(AstStatReturn* ret) { Scope* scope = findInnermostScope(ret->location); TypePackId expectedRetType = scope->returnType; + if (FFlag::LuauTableLiteralSubtypeSpecificCheck) + { + if (ret->list.size == 0) + { + testIsSubtype(builtinTypes->emptyTypePack, expectedRetType, ret->location); + return; + } - TypeArena* arena = &module->internalTypes; - TypePackId actualRetType = reconstructPack(ret->list, *arena); + auto [head, _] = extendTypePack(module->internalTypes, builtinTypes, expectedRetType, ret->list.size); + bool isSubtype = true; + std::vector actualHead; + std::optional actualTail; + for (size_t idx = 0; idx < ret->list.size - 1; idx++) + { + if (idx < head.size()) + { + isSubtype &= testPotentialLiteralIsSubtype(ret->list.data[idx], head[idx]); + actualHead.push_back(head[idx]); + } + else + { + actualHead.push_back(lookupType(ret->list.data[idx])); + } + } - testIsSubtype(actualRetType, expectedRetType, ret->location); + // This stanza is deconstructing what constraint generation does to + // return statements. If we have some statement like: + // + // return E0, E1, E2, ... , EN + // + // All expressions *except* the last will be types, and the last can + // potentially be a pack. However, if the last expression is a function + // call or varargs (`...`), then we _could_ have a pack in the final + // position. Additionally, if we have an argument overflow, then we can't + // do anything interesting with subtyping. + // + // _If_ the last argument is not a function call or varargs and we have + // at least an argument underflow, then we grab the last type out of + // the type pack head and use that to check the subtype of + auto lastExpr = ret->list.data[ret->list.size - 1]; + if (head.size() < ret->list.size || lastExpr->is() || lastExpr->is()) + { + actualTail = lookupPack(lastExpr); + } + else + { + auto lastType = head[ret->list.size - 1]; + isSubtype &= testPotentialLiteralIsSubtype(lastExpr, lastType); + actualHead.push_back(lastType); + } + + // After all that, we still fire a pack subtype test to determine + // whether we have a well-formed return statement. We only fire + // this if all the previous subtype tests have succeeded, lest + // we double error. + if (isSubtype) + { + auto reconstructedRetType = module->internalTypes.addTypePack(TypePack{std::move(actualHead), std::move(actualTail)}); + testIsSubtype(reconstructedRetType, expectedRetType, ret->location); + } + } + else + { + TypeArena* arena = &module->internalTypes; + TypePackId actualRetType = reconstructPack(ret->list, *arena); + testIsSubtype(actualRetType, expectedRetType, ret->location); + } for (AstExpr* expr : ret->list) visit(expr, ValueContext::RValue); + } void TypeChecker2::visit(AstStatExpr* expr) @@ -745,7 +809,12 @@ void TypeChecker2::visit(AstStatLocal* local) TypeId annotationType = lookupAnnotation(var->annotation); TypeId valueType = value ? lookupType(value) : nullptr; if (valueType) - testIsSubtype(valueType, annotationType, value->location); + { + if (FFlag::LuauTableLiteralSubtypeSpecificCheck) + testPotentialLiteralIsSubtype(value, annotationType); + else + testIsSubtype(valueType, annotationType, value->location); + } visit(var->annotation); } @@ -1154,14 +1223,26 @@ void TypeChecker2::visit(AstStatAssign* assign) continue; } - bool ok = testIsSubtype(rhsType, lhsType, rhs->location); - - // If rhsType bindingType = getBindingType(lhs); - if (bindingType) - testIsSubtype(rhsType, *bindingType, rhs->location); + // If rhsType bindingType = getBindingType(lhs)) + testPotentialLiteralIsSubtype(rhs, *bindingType); + } + } + else + { + bool ok = testIsSubtype(rhsType, lhsType, rhs->location); + + // If rhsType bindingType = getBindingType(lhs); + if (bindingType) + testIsSubtype(rhsType, *bindingType, rhs->location); + } } } } @@ -1200,14 +1281,19 @@ void TypeChecker2::visit(const AstTypeList* typeList) void TypeChecker2::visit(AstStatTypeAlias* stat) { + // We will not visit type aliases that do not have an associated scope, + // this means that (probably) this was a duplicate type alias or a + // type alias with an illegal name (like `typeof`). + if (FFlag::LuauSkipMalformedTypeAliases && !module->astScopes.contains(stat)) + return; + visitGenerics(stat->generics, stat->genericPacks); visit(stat->type); } void TypeChecker2::visit(AstStatTypeFunction* stat) { - if (FFlag::LuauUserTypeFunTypecheck) - visit(stat->body); + visit(stat->body); } void TypeChecker2::visit(AstTypeList types) @@ -2857,6 +2943,130 @@ void TypeChecker2::explainError(TypePackId subTy, TypePackId superTy, Location l reportError(TypePackMismatch{superTy, subTy, reasonings.toString()}, location); } +namespace +{ +bool isRecord(const AstExprTable::Item& item) +{ + return item.kind == AstExprTable::Item::Record || (item.kind == AstExprTable::Item::General && item.key->is()); +} +} + +bool TypeChecker2::testPotentialLiteralIsSubtype(AstExpr* expr, TypeId expectedType) +{ + auto exprType = follow(lookupType(expr)); + expectedType = follow(expectedType); + + auto exprTable = expr->as(); + auto exprTableType = get(exprType); + auto expectedTableType = get(expectedType); + + // If we don't have a table or the type of the expression isn't a + // table, then do a normal subtype test. + if (!exprTableType || !exprTable) + return testIsSubtype(exprType, expectedType, expr->location); + + // At this point we *know* that the expression is a table and has a specific + // table type, but if there isn't an expected table type we should do something + // slightly different. + if (!expectedTableType) + { + if (auto utv = get(expectedType)) + { + std::vector parts{begin(utv), end(utv)}; + std::optional tt = extractMatchingTableType(parts, exprType, builtinTypes); + if (tt) + return testPotentialLiteralIsSubtype(expr, *tt); + } + + return testIsSubtype(exprType, expectedType, expr->location); + } + + Set > missingKeys{{}}; + for (const auto& [name, prop] : expectedTableType->props) + { + LUAU_ASSERT(!prop.isWriteOnly()); + auto readTy = *prop.readTy; + if (!isOptional(readTy)) + missingKeys.insert(name); + } + + bool isArrayLike = false; + if (expectedTableType->indexer) + { + NotNull scope{findInnermostScope(expr->location)}; + auto result = subtyping->isSubtype(expectedTableType->indexer->indexType, builtinTypes->numberType, scope); + isArrayLike = result.isSubtype; + } + + bool isSubtype = true; + + for (const auto& item : exprTable->items) + { + if (isRecord(item)) + { + const AstArray& s = item.key->as()->value; + std::string keyStr{s.data, s.data + s.size}; + + missingKeys.erase(keyStr); + auto expectedIt = expectedTableType->props.find(keyStr); + if (expectedIt == expectedTableType->props.end()) + { + if (expectedTableType->indexer) + { + module->astExpectedTypes[item.key] = expectedTableType->indexer->indexType; + module->astExpectedTypes[item.value] = expectedTableType->indexer->indexResultType; + auto inferredKeyType = module->internalTypes.addType(SingletonType{StringSingleton{keyStr}}); + isSubtype &= testIsSubtype(inferredKeyType, expectedTableType->indexer->indexType, item.key->location); + isSubtype &= testPotentialLiteralIsSubtype(item.value, expectedTableType->indexer->indexResultType); + } + // If there's not an indexer, then by width subtyping we can just do nothing :) + } + else + { + // TODO: What do we do for write only props? + LUAU_ASSERT(expectedIt->second.readTy); + // Some property is in the expected type: we can test against the specific type. + module->astExpectedTypes[item.value] = *expectedIt->second.readTy; + isSubtype &= testPotentialLiteralIsSubtype(item.value, *expectedIt->second.readTy); + } + } + else if (item.kind == AstExprTable::Item::List) + { + if (!isArrayLike) + { + isSubtype = false; + reportError(UnexpectedArrayLikeTableItem{}, item.value->location); + } + // if the indexer index type is not exactly `number`. + if (expectedTableType->indexer) + { + module->astExpectedTypes[item.value] = expectedTableType->indexer->indexResultType; + isSubtype &= testPotentialLiteralIsSubtype(item.value, expectedTableType->indexer->indexResultType); + } + } + else if (item.kind == AstExprTable::Item::General && expectedTableType->indexer) + { + module->astExpectedTypes[item.key] = expectedTableType->indexer->indexType; + module->astExpectedTypes[item.value] = expectedTableType->indexer->indexResultType; + isSubtype &= testPotentialLiteralIsSubtype(item.key, expectedTableType->indexer->indexType); + isSubtype &= testPotentialLiteralIsSubtype(item.value, expectedTableType->indexer->indexResultType); + } + } + + if (!missingKeys.empty()) + { + std::vector temp; + temp.reserve(missingKeys.size()); + for (const auto& key : missingKeys) + if (key) + temp.push_back(*key); + reportError(MissingProperties{expectedType, exprType, std::move(temp)}, expr->location); + return false; + } + + return isSubtype; +} + bool TypeChecker2::testIsSubtype(TypeId subTy, TypeId superTy, Location location) { NotNull scope{findInnermostScope(location)}; diff --git a/Analysis/src/TypeFunction.cpp b/Analysis/src/TypeFunction.cpp index 14a1831a..a55a22f7 100644 --- a/Analysis/src/TypeFunction.cpp +++ b/Analysis/src/TypeFunction.cpp @@ -47,24 +47,14 @@ LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyApplicationCartesianProductLimit, 5'0 LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyUseGuesserDepth, -1); LUAU_FASTFLAG(DebugLuauEqSatSimplification) -LUAU_FASTFLAG(LuauTypeFunResultInAutocomplete) -LUAU_FASTFLAG(LuauNonReentrantGeneralization2) +LUAU_FASTFLAG(LuauNonReentrantGeneralization3) LUAU_FASTFLAG(DebugLuauGreedyGeneralization) LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies) -LUAU_FASTFLAGVARIABLE(LuauMetatableTypeFunctions) -LUAU_FASTFLAGVARIABLE(LuauIndexTypeFunctionImprovements) -LUAU_FASTFLAGVARIABLE(LuauIndexTypeFunctionFunctionMetamethods) -LUAU_FASTFLAGVARIABLE(LuauIntersectNotNil) -LUAU_FASTFLAGVARIABLE(LuauMetatablesHaveLength) -LUAU_FASTFLAGVARIABLE(LuauIndexAnyIsAny) -LUAU_FASTFLAGVARIABLE(LuauFixCyclicIndexInIndexer) -LUAU_FASTFLAGVARIABLE(LuauSimplyRefineNotNil) -LUAU_FASTFLAGVARIABLE(LuauIndexDeferPendingIndexee) -LUAU_FASTFLAGVARIABLE(LuauNewTypeFunReductionChecks2) LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect) LUAU_FASTFLAGVARIABLE(LuauNarrowIntersectionNevers) LUAU_FASTFLAGVARIABLE(LuauRefineWaitForBlockedTypesInTarget) +LUAU_FASTFLAGVARIABLE(LuauNoMoreInjectiveTypeFunctions) LUAU_FASTFLAGVARIABLE(LuauNotAllBinaryTypeFunsHaveDefaults) namespace Luau @@ -72,61 +62,6 @@ namespace Luau using TypeOrTypePackIdSet = DenseHashSet; -struct InstanceCollector_DEPRECATED : TypeOnceVisitor -{ - VecDeque tys; - VecDeque tps; - TypeOrTypePackIdSet shouldGuess{nullptr}; - std::vector cyclicInstance; - - bool visit(TypeId ty, const TypeFunctionInstanceType&) override - { - // TypeOnceVisitor performs a depth-first traversal in the absence of - // cycles. This means that by pushing to the front of the queue, we will - // try to reduce deeper instances first if we start with the first thing - // in the queue. Consider Add, number>, number>: - // we want to reduce the innermost Add instantiation - // first. - - if (DFInt::LuauTypeFamilyUseGuesserDepth >= 0 && typeFunctionDepth > DFInt::LuauTypeFamilyUseGuesserDepth) - shouldGuess.insert(ty); - - tys.push_front(ty); - - return true; - } - - void cycle(TypeId ty) override - { - /// Detected cyclic type pack - TypeId t = follow(ty); - if (get(t)) - cyclicInstance.push_back(t); - } - - bool visit(TypeId ty, const ExternType&) override - { - return false; - } - - bool visit(TypePackId tp, const TypeFunctionInstanceTypePack&) override - { - // TypeOnceVisitor performs a depth-first traversal in the absence of - // cycles. This means that by pushing to the front of the queue, we will - // try to reduce deeper instances first if we start with the first thing - // in the queue. Consider Add, number>, number>: - // we want to reduce the innermost Add instantiation - // first. - - if (DFInt::LuauTypeFamilyUseGuesserDepth >= 0 && typeFunctionDepth > DFInt::LuauTypeFamilyUseGuesserDepth) - shouldGuess.insert(tp); - - tps.push_front(tp); - - return true; - } -}; - struct InstanceCollector : TypeOnceVisitor { DenseHashSet recordedTys{nullptr}; @@ -546,7 +481,7 @@ struct TypeFunctionReducer if (const TypeFunctionInstanceType* tfit = get(subject)) { - if (FFlag::LuauNewTypeFunReductionChecks2 && tfit->function->name == "user") + if (tfit->function->name == "user") { UnscopedGenericFinder finder; finder.traverse(subject); @@ -674,114 +609,56 @@ static FunctionGraphReductionResult reduceFunctionsInternal( FunctionGraphReductionResult reduceTypeFunctions(TypeId entrypoint, Location location, TypeFunctionContext ctx, bool force) { - if (FFlag::LuauNewTypeFunReductionChecks2) + InstanceCollector collector; + + try { - InstanceCollector collector; - - try - { - collector.traverse(entrypoint); - } - catch (RecursionLimitException&) - { - return FunctionGraphReductionResult{}; - } - - if (collector.tys.empty() && collector.tps.empty()) - return {}; - - return reduceFunctionsInternal( - std::move(collector.tys), - std::move(collector.tps), - std::move(collector.shouldGuess), - std::move(collector.cyclicInstance), - location, - ctx, - force - ); + collector.traverse(entrypoint); } - else + catch (RecursionLimitException&) { - InstanceCollector_DEPRECATED collector; - - try - { - collector.traverse(entrypoint); - } - catch (RecursionLimitException&) - { - return FunctionGraphReductionResult{}; - } - - if (collector.tys.empty() && collector.tps.empty()) - return {}; - - return reduceFunctionsInternal( - std::move(collector.tys), - std::move(collector.tps), - std::move(collector.shouldGuess), - std::move(collector.cyclicInstance), - location, - ctx, - force - ); + return FunctionGraphReductionResult{}; } + + if (collector.tys.empty() && collector.tps.empty()) + return {}; + + return reduceFunctionsInternal( + std::move(collector.tys), + std::move(collector.tps), + std::move(collector.shouldGuess), + std::move(collector.cyclicInstance), + location, + ctx, + force + ); } FunctionGraphReductionResult reduceTypeFunctions(TypePackId entrypoint, Location location, TypeFunctionContext ctx, bool force) { - if (FFlag::LuauNewTypeFunReductionChecks2) + InstanceCollector collector; + + try { - InstanceCollector collector; - - try - { - collector.traverse(entrypoint); - } - catch (RecursionLimitException&) - { - return FunctionGraphReductionResult{}; - } - - if (collector.tys.empty() && collector.tps.empty()) - return {}; - - return reduceFunctionsInternal( - std::move(collector.tys), - std::move(collector.tps), - std::move(collector.shouldGuess), - std::move(collector.cyclicInstance), - location, - ctx, - force - ); + collector.traverse(entrypoint); } - else + catch (RecursionLimitException&) { - InstanceCollector_DEPRECATED collector; - - try - { - collector.traverse(entrypoint); - } - catch (RecursionLimitException&) - { - return FunctionGraphReductionResult{}; - } - - if (collector.tys.empty() && collector.tps.empty()) - return {}; - - return reduceFunctionsInternal( - std::move(collector.tys), - std::move(collector.tps), - std::move(collector.shouldGuess), - std::move(collector.cyclicInstance), - location, - ctx, - force - ); + return FunctionGraphReductionResult{}; } + + if (collector.tys.empty() && collector.tps.empty()) + return {}; + + return reduceFunctionsInternal( + std::move(collector.tys), + std::move(collector.tps), + std::move(collector.shouldGuess), + std::move(collector.cyclicInstance), + location, + ctx, + force + ); } bool isPending(TypeId ty, ConstraintSolver* solver) @@ -934,36 +811,22 @@ TypeFunctionReductionResult userDefinedTypeFunction( } // If type functions cannot be evaluated because of errors in the code, we do not generate any additional ones - if (!ctx->typeFunctionRuntime->allowEvaluation || (FFlag::LuauTypeFunResultInAutocomplete && typeFunction->userFuncData.definition->hasErrors)) + if (!ctx->typeFunctionRuntime->allowEvaluation || typeFunction->userFuncData.definition->hasErrors) return {ctx->builtins->errorRecoveryType(), Reduction::MaybeOk, {}, {}}; - if (FFlag::LuauNewTypeFunReductionChecks2) - { - FindUserTypeFunctionBlockers check{ctx}; + FindUserTypeFunctionBlockers check{ctx}; - for (auto typeParam : typeParams) - check.traverse(follow(typeParam)); + for (auto typeParam : typeParams) + check.traverse(follow(typeParam)); - if (!check.blockingTypes.empty()) - return {std::nullopt, Reduction::MaybeOk, check.blockingTypes, {}}; - } - else - { - for (auto typeParam : typeParams) - { - TypeId ty = follow(typeParam); - - // block if we need to - if (isPending(ty, ctx->solver)) - return {std::nullopt, Reduction::MaybeOk, {ty}, {}}; - } - } + if (!check.blockingTypes.empty()) + return {std::nullopt, Reduction::MaybeOk, check.blockingTypes, {}}; // Ensure that whole type function environment is registered for (auto& [name, definition] : typeFunction->userFuncData.environment) { // Cannot evaluate if a potential dependency couldn't be parsed - if (FFlag::LuauTypeFunResultInAutocomplete && definition.first->hasErrors) + if (definition.first->hasErrors) return {ctx->builtins->errorRecoveryType(), Reduction::MaybeOk, {}, {}}; if (std::optional error = ctx->typeFunctionRuntime->registerFunction(definition.first)) @@ -1180,12 +1043,9 @@ TypeFunctionReductionResult lenTypeFunction( std::optional mmType = findMetatableEntry(ctx->builtins, dummy, operandTy, "__len", Location{}); if (!mmType) { - if (FFlag::LuauMetatablesHaveLength) - { - // If we have a metatable type with no __len, this means we still have a table with default length function - if (get(normalizedOperand)) - return {ctx->builtins->numberType, Reduction::MaybeOk, {}, {}}; - } + // If we have a metatable type with no __len, this means we still have a table with default length function + if (get(normalizedOperand)) + return {ctx->builtins->numberType, Reduction::MaybeOk, {}, {}}; return {std::nullopt, Reduction::Erroneous, {}, {}}; } @@ -1241,7 +1101,7 @@ TypeFunctionReductionResult unmTypeFunction( if (isPending(operandTy, ctx->solver)) return {std::nullopt, Reduction::MaybeOk, {operandTy}, {}}; - if (FFlag::LuauNonReentrantGeneralization2) + if (FFlag::LuauNonReentrantGeneralization3) operandTy = follow(operandTy); std::shared_ptr normTy = ctx->normalizer->normalize(operandTy); @@ -1322,7 +1182,7 @@ std::optional TypeFunctionRuntime::registerFunction(AstStatTypeFunc return std::nullopt; // Do not evaluate type functions with parse errors inside - if (FFlag::LuauTypeFunResultInAutocomplete && function->hasErrors) + if (function->hasErrors) return std::nullopt; prepareState(); @@ -1918,12 +1778,12 @@ static TypeFunctionReductionResult comparisonTypeFunction( emplaceType(asMutable(lhsTy), ctx->builtins->numberType); else if (rhsFree && isNumber(lhsTy)) emplaceType(asMutable(rhsTy), ctx->builtins->numberType); - else if (lhsFree && ctx->normalizer->isInhabited(rhsTy) != NormalizationResult::False) + else if (!FFlag::LuauNoMoreInjectiveTypeFunctions && lhsFree && ctx->normalizer->isInhabited(rhsTy) != NormalizationResult::False) { auto c1 = ctx->pushConstraint(EqualityConstraint{lhsTy, rhsTy}); const_cast(ctx->constraint)->dependencies.emplace_back(c1); } - else if (rhsFree && ctx->normalizer->isInhabited(lhsTy) != NormalizationResult::False) + else if (!FFlag::LuauNoMoreInjectiveTypeFunctions && rhsFree && ctx->normalizer->isInhabited(lhsTy) != NormalizationResult::False) { auto c1 = ctx->pushConstraint(EqualityConstraint{rhsTy, lhsTy}); const_cast(ctx->constraint)->dependencies.emplace_back(c1); @@ -2325,15 +2185,12 @@ TypeFunctionReductionResult refineTypeFunction( if (!crt.found) return {target, {}}; - if (FFlag::LuauSimplyRefineNotNil) + if (auto negation = get(discriminant)) { - if (auto negation = get(discriminant)) + if (auto primitive = get(follow(negation->ty)); primitive && primitive->type == PrimitiveType::NilType) { - if (auto primitive = get(follow(negation->ty)); primitive && primitive->type == PrimitiveType::NilType) - { - SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, target, discriminant); - return {result.result, {}}; - } + SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, target, discriminant); + return {result.result, {}}; } } @@ -2620,17 +2477,9 @@ TypeFunctionReductionResult intersectTypeFunction( } } - if (FFlag::LuauIntersectNotNil) + for (TypeId blockedType : result.blockedTypes) { - for (TypeId blockedType : result.blockedTypes) - { - if (!get(blockedType)) - return {std::nullopt, Reduction::MaybeOk, {result.blockedTypes.begin(), result.blockedTypes.end()}, {}}; - } - } - else - { - if (!result.blockedTypes.empty()) + if (!get(blockedType)) return {std::nullopt, Reduction::MaybeOk, {result.blockedTypes.begin(), result.blockedTypes.end()}, {}}; } @@ -2858,7 +2707,7 @@ TypeFunctionReductionResult keyofFunctionImpl( std::vector singletons; singletons.reserve(keys.size()); - for (std::string key : keys) + for (const std::string& key : keys) singletons.push_back(ctx->arena->addType(SingletonType{StringSingleton{key}})); // If there's only one entry, we don't need a UnionType. @@ -2927,10 +2776,7 @@ bool searchPropsAndIndexer( { for (TypeId option : propUnionTy->options) { - if (FFlag::LuauIndexTypeFunctionImprovements) - result.insert(follow(option)); - else - result.insert(option); + result.insert(follow(option)); } } else // property is a singular type or intersection type -> we can simply append @@ -2943,16 +2789,13 @@ bool searchPropsAndIndexer( // index into tbl's indexer if (tblIndexer) { - TypeId indexType = FFlag::LuauFixCyclicIndexInIndexer ? follow(tblIndexer->indexType) : tblIndexer->indexType; + TypeId indexType = follow(tblIndexer->indexType); - if (FFlag::LuauFixCyclicIndexInIndexer) + if (auto tfit = get(indexType)) { - if (auto tfit = get(indexType)) - { - // if we have an index function here, it means we're in a cycle, so let's see if it's well-founded if we tie the knot - if (tfit->function.get() == &builtinTypeFunctions().indexFunc) - indexType = follow(tblIndexer->indexResultType); - } + // if we have an index function here, it means we're in a cycle, so let's see if it's well-founded if we tie the knot + if (tfit->function.get() == &builtinTypeFunctions().indexFunc) + indexType = follow(tblIndexer->indexResultType); } if (isSubtype(ty, indexType, ctx->scope, ctx->builtins, ctx->simplifier, *ctx->ice)) @@ -2964,10 +2807,7 @@ bool searchPropsAndIndexer( { for (TypeId option : idxResUnionTy->options) { - if (FFlag::LuauIndexTypeFunctionImprovements) - result.insert(follow(option)); - else - result.insert(option); + result.insert(follow(option)); } } else // indexResultType is a singular type or intersection type -> we can simply append @@ -2980,46 +2820,6 @@ bool searchPropsAndIndexer( return false; } -/* Handles recursion / metamethods of tables and extern types - `isRaw` parameter indicates whether or not we should follow __index metamethods - returns false if property of `ty` could not be found */ -bool tblIndexInto_DEPRECATED(TypeId indexer, TypeId indexee, DenseHashSet& result, NotNull ctx, bool isRaw) -{ - indexer = follow(indexer); - indexee = follow(indexee); - - // we have a table type to try indexing - if (auto tableTy = get(indexee)) - { - return searchPropsAndIndexer(indexer, tableTy->props, tableTy->indexer, result, ctx); - } - - // we have a metatable type to try indexing - if (auto metatableTy = get(indexee)) - { - if (auto tableTy = get(metatableTy->table)) - { - - // try finding all properties within the current scope of the table - if (searchPropsAndIndexer(indexer, tableTy->props, tableTy->indexer, result, ctx)) - return true; - } - - // if the code reached here, it means we weren't able to find all properties -> look into __index metamethod - if (!isRaw) - { - // findMetatableEntry demands the ability to emit errors, so we must give it - // the necessary state to do that, even if we intend to just eat the errors. - ErrorVec dummy; - std::optional mmType = findMetatableEntry(ctx->builtins, dummy, indexee, "__index", Location{}); - if (mmType) - return tblIndexInto_DEPRECATED(indexer, *mmType, result, ctx, isRaw); - } - } - - return false; -} - bool tblIndexInto( TypeId indexer, TypeId indexee, @@ -3036,50 +2836,47 @@ bool tblIndexInto( return false; seenSet.insert(indexee); - if (FFlag::LuauIndexTypeFunctionFunctionMetamethods) + if (auto unionTy = get(indexee)) { - if (auto unionTy = get(indexee)) + bool res = true; + for (auto component : unionTy) { - bool res = true; - for (auto component : unionTy) - { - // if the component is in the seen set and isn't the indexee itself, - // we can skip it cause it means we encountered it in an earlier component in the union. - if (seenSet.contains(component) && component != indexee) - continue; + // if the component is in the seen set and isn't the indexee itself, + // we can skip it cause it means we encountered it in an earlier component in the union. + if (seenSet.contains(component) && component != indexee) + continue; - res = res && tblIndexInto(indexer, component, result, seenSet, ctx, isRaw); - } - return res; + res = res && tblIndexInto(indexer, component, result, seenSet, ctx, isRaw); } + return res; + } - if (get(indexee)) - { - TypePackId argPack = ctx->arena->addTypePack({indexer}); - SolveResult solveResult = solveFunctionCall( - ctx->arena, - ctx->builtins, - ctx->simplifier, - ctx->normalizer, - ctx->typeFunctionRuntime, - ctx->ice, - ctx->limits, - ctx->scope, - ctx->scope->location, - indexee, - argPack - ); + if (get(indexee)) + { + TypePackId argPack = ctx->arena->addTypePack({indexer}); + SolveResult solveResult = solveFunctionCall( + ctx->arena, + ctx->builtins, + ctx->simplifier, + ctx->normalizer, + ctx->typeFunctionRuntime, + ctx->ice, + ctx->limits, + ctx->scope, + ctx->scope->location, + indexee, + argPack + ); - if (!solveResult.typePackId.has_value()) - return false; + if (!solveResult.typePackId.has_value()) + return false; - TypePack extracted = extendTypePack(*ctx->arena, ctx->builtins, *solveResult.typePackId, 1); - if (extracted.head.empty()) - return false; + TypePack extracted = extendTypePack(*ctx->arena, ctx->builtins, *solveResult.typePackId, 1); + if (extracted.head.empty()) + return false; - result.insert(follow(extracted.head.front())); - return true; - } + result.insert(follow(extracted.head.front())); + return true; } // we have a table type to try indexing @@ -3116,15 +2913,8 @@ bool tblIndexInto( bool tblIndexInto(TypeId indexer, TypeId indexee, DenseHashSet& result, NotNull ctx, bool isRaw) { - if (FFlag::LuauIndexTypeFunctionImprovements) - { - DenseHashSet seenSet{{}}; - return tblIndexInto(indexer, indexee, result, seenSet, ctx, isRaw); - } - else - { - return tblIndexInto_DEPRECATED(indexer, indexee, result, ctx, isRaw); - } + DenseHashSet seenSet{{}}; + return tblIndexInto(indexer, indexee, result, seenSet, ctx, isRaw); } /* Vocabulary note: indexee refers to the type that contains the properties, @@ -3139,7 +2929,7 @@ TypeFunctionReductionResult indexFunctionImpl( { TypeId indexeeTy = follow(typeParams.at(0)); - if (FFlag::LuauIndexDeferPendingIndexee && isPending(indexeeTy, ctx->solver)) + if (isPending(indexeeTy, ctx->solver)) return {std::nullopt, Reduction::MaybeOk, {indexeeTy}, {}}; std::shared_ptr indexeeNormTy = ctx->normalizer->normalize(indexeeTy); @@ -3148,12 +2938,9 @@ TypeFunctionReductionResult indexFunctionImpl( if (!indexeeNormTy) return {std::nullopt, Reduction::MaybeOk, {}, {}}; - if (FFlag::LuauIndexAnyIsAny) - { - // if the indexee is `any`, then indexing also gives us `any`. - if (indexeeNormTy->shouldSuppressErrors()) - return {ctx->builtins->anyType, Reduction::MaybeOk, {}, {}}; - } + // if the indexee is `any`, then indexing also gives us `any`. + if (indexeeNormTy->shouldSuppressErrors()) + return {ctx->builtins->anyType, Reduction::MaybeOk, {}, {}}; // if we don't have either just tables or just extern types, we've got nothing to index into if (indexeeNormTy->hasTables() == indexeeNormTy->hasExternTypes()) @@ -3254,20 +3041,6 @@ TypeFunctionReductionResult indexFunctionImpl( } } - if (!FFlag::LuauIndexTypeFunctionImprovements) - { - // Call `follow()` on each element to resolve all Bound types before returning - std::transform( - properties.begin(), - properties.end(), - properties.begin(), - [](TypeId ty) - { - return follow(ty); - } - ); - } - // If the type being reduced to is a single type, no need to union if (properties.size() == 1) return {*properties.begin(), Reduction::MaybeOk, {}, {}}; @@ -3544,7 +3317,6 @@ TypeFunctionReductionResult weakoptionalTypeFunc( return {targetTy, Reduction::MaybeOk, {}, {}}; } - BuiltinTypeFunctions::BuiltinTypeFunctions() : userFunc{"user", userDefinedTypeFunction} , notFunc{"not", notTypeFunction} @@ -3640,14 +3412,11 @@ void BuiltinTypeFunctions::addToScope(NotNull arena, NotNull s scope->exportedTypeBindings[rawgetFunc.name] = mkBinaryTypeFunctionWithDefault(&rawgetFunc); } - if (FFlag::LuauMetatableTypeFunctions) - { - if (FFlag::LuauNotAllBinaryTypeFunsHaveDefaults) - scope->exportedTypeBindings[setmetatableFunc.name] = mkBinaryTypeFunction(&setmetatableFunc); - else - scope->exportedTypeBindings[setmetatableFunc.name] = mkBinaryTypeFunctionWithDefault(&setmetatableFunc); - scope->exportedTypeBindings[getmetatableFunc.name] = mkUnaryTypeFunction(&getmetatableFunc); - } + if (FFlag::LuauNotAllBinaryTypeFunsHaveDefaults) + scope->exportedTypeBindings[setmetatableFunc.name] = mkBinaryTypeFunction(&setmetatableFunc); + else + scope->exportedTypeBindings[setmetatableFunc.name] = mkBinaryTypeFunctionWithDefault(&setmetatableFunc); + scope->exportedTypeBindings[getmetatableFunc.name] = mkUnaryTypeFunction(&getmetatableFunc); } const BuiltinTypeFunctions& builtinTypeFunctions() diff --git a/Analysis/src/TypeFunctionRuntime.cpp b/Analysis/src/TypeFunctionRuntime.cpp index 65aec6d5..f60870c3 100644 --- a/Analysis/src/TypeFunctionRuntime.cpp +++ b/Analysis/src/TypeFunctionRuntime.cpp @@ -14,7 +14,6 @@ #include LUAU_DYNAMIC_FASTINT(LuauTypeFunctionSerdeIterationLimit) -LUAU_FASTFLAGVARIABLE(LuauTypeFunReadWriteParents) LUAU_FASTFLAGVARIABLE(LuauTypeFunOptional) namespace Luau @@ -1138,28 +1137,6 @@ static int getFunctionGenerics(lua_State* L) return 1; } -// Luau: `self:parent() -> type` -// Returns the parent of a class type -static int getClassParent_DEPRECATED(lua_State* L) -{ - int argumentCount = lua_gettop(L); - if (argumentCount != 1) - luaL_error(L, "type.parent: expected 1 arguments, but got %d", argumentCount); - - TypeFunctionTypeId self = getTypeUserData(L, 1); - auto tfct = get(self); - if (!tfct) - luaL_error(L, "type.parent: expected self to be a class, but got %s instead", getTag(L, self).c_str()); - - // If the parent does not exist, we should return nil - if (!tfct->parent_DEPRECATED) - lua_pushnil(L); - else - allocTypeUserData(L, (*tfct->parent_DEPRECATED)->type); - - return 1; -} - // Luau: `self:readparent() -> type` // Returns the read type of the class' parent static int getReadParent(lua_State* L) @@ -1628,7 +1605,8 @@ void registerTypeUserData(lua_State* L) {"components", getComponents}, // Extern type methods - {FFlag::LuauTypeFunReadWriteParents ? "readparent" : "parent", FFlag::LuauTypeFunReadWriteParents ? getReadParent : getClassParent_DEPRECATED}, + {"readparent", getReadParent}, + {"writeparent", getWriteParent}, // Function type methods (cont.) {"setgenerics", setFunctionGenerics}, @@ -1638,9 +1616,6 @@ void registerTypeUserData(lua_State* L) {"name", getGenericName}, {"ispack", getGenericIsPack}, - // move this under extern type methods when removing FFlagLuauTypeFunReadWriteParents - {FFlag::LuauTypeFunReadWriteParents ? "writeparent" : nullptr, FFlag::LuauTypeFunReadWriteParents ? getWriteParent : nullptr}, - {nullptr, nullptr} }; diff --git a/Analysis/src/TypeFunctionRuntimeBuilder.cpp b/Analysis/src/TypeFunctionRuntimeBuilder.cpp index a0694984..e917e598 100644 --- a/Analysis/src/TypeFunctionRuntimeBuilder.cpp +++ b/Analysis/src/TypeFunctionRuntimeBuilder.cpp @@ -19,7 +19,6 @@ // used to control the recursion limit of any operations done by user-defined type functions // currently, controls serialization, deserialization, and `type.copy` LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFunctionSerdeIterationLimit, 100'000); -LUAU_FASTFLAG(LuauTypeFunReadWriteParents) namespace Luau { @@ -211,7 +210,7 @@ private: // Since there aren't any new class types being created in type functions, we will deserialize by using a direct reference to the original // class target = typeFunctionRuntime->typeArena.allocate( - TypeFunctionExternType{{}, std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt, ty} + TypeFunctionExternType{{}, std::nullopt, std::nullopt, std::nullopt, std::nullopt, ty} ); } else if (auto g = get(ty)) @@ -436,16 +435,9 @@ private: { TypeFunctionTypeId parent = shallowSerialize(*c1->parent); - if (FFlag::LuauTypeFunReadWriteParents) - { - // we don't yet have read/write parents in the type inference engine. - c2->readParent = parent; - c2->writeParent = parent; - } - else - { - c2->parent_DEPRECATED = parent; - } + // we don't yet have read/write parents in the type inference engine. + c2->readParent = parent; + c2->writeParent = parent; } } diff --git a/Analysis/src/TypeInfer.cpp b/Analysis/src/TypeInfer.cpp index 9e9e93d7..e17ea4b6 100644 --- a/Analysis/src/TypeInfer.cpp +++ b/Analysis/src/TypeInfer.cpp @@ -35,7 +35,6 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations) -LUAU_FASTFLAGVARIABLE(LuauStatForInFix) LUAU_FASTFLAGVARIABLE(LuauReduceCheckBinaryExprStackPressure) namespace Luau @@ -1319,23 +1318,15 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatForIn& forin) // and check them against the parameter types of the iterator function. auto [types, tail] = flatten(callRetPack); - if (FFlag::LuauStatForInFix) - { - if (!types.empty()) - { - std::vector argTypes = std::vector(types.begin() + 1, types.end()); - argPack = addTypePack(TypePackVar{TypePack{std::move(argTypes), tail}}); - } - else - { - argPack = addTypePack(TypePack{}); - } - } - else + if (!types.empty()) { std::vector argTypes = std::vector(types.begin() + 1, types.end()); argPack = addTypePack(TypePackVar{TypePack{std::move(argTypes), tail}}); } + else + { + argPack = addTypePack(TypePack{}); + } } else { diff --git a/Analysis/src/TypePack.cpp b/Analysis/src/TypePack.cpp index 5cd59dbe..6244343f 100644 --- a/Analysis/src/TypePack.cpp +++ b/Analysis/src/TypePack.cpp @@ -6,8 +6,6 @@ #include -LUAU_FASTFLAGVARIABLE(LuauTypePackDetectCycles) - namespace Luau { @@ -149,7 +147,7 @@ TypePackIterator& TypePackIterator::operator++() currentTypePack = tp->tail ? log->follow(*tp->tail) : nullptr; tp = currentTypePack ? log->getMutable(currentTypePack) : nullptr; - if (FFlag::LuauTypePackDetectCycles && tp) + if (tp) { // Step twice on each iteration to detect cycles tailCycleCheck = tp->tail ? log->follow(*tp->tail) : nullptr; diff --git a/Analysis/src/TypeUtils.cpp b/Analysis/src/TypeUtils.cpp index 949efde3..f9c165f8 100644 --- a/Analysis/src/TypeUtils.cpp +++ b/Analysis/src/TypeUtils.cpp @@ -4,15 +4,17 @@ #include "Luau/Common.h" #include "Luau/Normalize.h" #include "Luau/Scope.h" +#include "Luau/Simplify.h" #include "Luau/ToString.h" #include "Luau/Type.h" #include "Luau/TypeInfer.h" #include -LUAU_FASTFLAG(LuauSolverV2); -LUAU_FASTFLAG(LuauNonReentrantGeneralization2) +LUAU_FASTFLAG(LuauSolverV2) +LUAU_FASTFLAG(LuauNonReentrantGeneralization3) LUAU_FASTFLAG(LuauDisableNewSolverAssertsInMixedMode) +LUAU_FASTFLAGVARIABLE(LuauErrorSuppressionTypeFunctionArgs) namespace Luau { @@ -305,7 +307,7 @@ TypePack extendTypePack( TypePack newPack; newPack.tail = arena.freshTypePack(ftp->scope, ftp->polarity); - if (FFlag::LuauNonReentrantGeneralization2) + if (FFlag::LuauNonReentrantGeneralization3) trackInteriorFreeTypePack(ftp->scope, *newPack.tail); if (FFlag::LuauSolverV2) @@ -434,6 +436,25 @@ TypeId stripNil(NotNull builtinTypes, TypeArena& arena, TypeId ty) ErrorSuppression shouldSuppressErrors(NotNull normalizer, TypeId ty) { + if (FFlag::LuauErrorSuppressionTypeFunctionArgs) + { + if (auto tfit = get(follow(ty))) + { + for (auto ty : tfit->typeArguments) + { + std::shared_ptr normType = normalizer->normalize(ty); + + if (!normType) + return ErrorSuppression::NormalizationFailed; + + if (normType->shouldSuppressErrors()) + return ErrorSuppression::Suppress; + } + + return ErrorSuppression::DoNotSuppress; + } + } + std::shared_ptr normType = normalizer->normalize(ty); if (!normType) @@ -570,7 +591,7 @@ void trackInteriorFreeType(Scope* scope, TypeId ty) void trackInteriorFreeTypePack(Scope* scope, TypePackId tp) { LUAU_ASSERT(tp); - if (!FFlag::LuauNonReentrantGeneralization2) + if (!FFlag::LuauNonReentrantGeneralization3) return; for (; scope; scope = scope->parent.get()) @@ -587,4 +608,84 @@ void trackInteriorFreeTypePack(Scope* scope, TypePackId tp) LUAU_ASSERT(!"No scopes in parent chain had a present `interiorFreeTypePacks` member."); } +bool fastIsSubtype(TypeId subTy, TypeId superTy) +{ + Relation r = relate(superTy, subTy); + return r == Relation::Coincident || r == Relation::Superset; +} + +std::optional extractMatchingTableType(std::vector& tables, TypeId exprType, NotNull builtinTypes) +{ + if (tables.empty()) + return std::nullopt; + + const TableType* exprTable = get(follow(exprType)); + if (!exprTable) + return std::nullopt; + + size_t tableCount = 0; + std::optional firstTable; + + for (TypeId ty : tables) + { + ty = follow(ty); + if (auto tt = get(ty)) + { + // If the expected table has a key whose type is a string or boolean + // singleton and the corresponding exprType property does not match, + // then skip this table. + + if (!firstTable) + firstTable = ty; + ++tableCount; + + for (const auto& [name, expectedProp] : tt->props) + { + if (!expectedProp.readTy) + continue; + + const TypeId expectedType = follow(*expectedProp.readTy); + + auto st = get(expectedType); + if (!st) + continue; + + auto it = exprTable->props.find(name); + if (it == exprTable->props.end()) + continue; + + const auto& [_name, exprProp] = *it; + + if (!exprProp.readTy) + continue; + + const TypeId propType = follow(*exprProp.readTy); + + const FreeType* ft = get(propType); + + if (ft && get(ft->lowerBound)) + { + if (fastIsSubtype(builtinTypes->booleanType, ft->upperBound) && fastIsSubtype(expectedType, builtinTypes->booleanType)) + { + return ty; + } + + if (fastIsSubtype(builtinTypes->stringType, ft->upperBound) && fastIsSubtype(expectedType, ft->lowerBound)) + { + return ty; + } + } + } + } + } + + if (tableCount == 1) + { + LUAU_ASSERT(firstTable); + return firstTable; + } + + return std::nullopt; +} + } // namespace Luau diff --git a/Analysis/src/Unifier2.cpp b/Analysis/src/Unifier2.cpp index 983e4be2..7a51d8be 100644 --- a/Analysis/src/Unifier2.cpp +++ b/Analysis/src/Unifier2.cpp @@ -18,7 +18,7 @@ #include LUAU_FASTINT(LuauTypeInferRecursionLimit) -LUAU_FASTFLAG(LuauNonReentrantGeneralization2) +LUAU_FASTFLAG(LuauNonReentrantGeneralization3) LUAU_FASTFLAG(DebugLuauGreedyGeneralization) namespace Luau @@ -329,7 +329,7 @@ bool Unifier2::unify(TypeId subTy, const FunctionType* superFn) for (TypePackId genericPack : subFn->genericPacks) { - if (FFlag::LuauNonReentrantGeneralization2) + if (FFlag::LuauNonReentrantGeneralization3) { if (FFlag::DebugLuauGreedyGeneralization) genericPack = follow(genericPack); @@ -454,6 +454,12 @@ bool Unifier2::unify(TableType* subTable, const TableType* superTable) { result &= unify(subTable->indexer->indexType, superTable->indexer->indexType); result &= unify(subTable->indexer->indexResultType, superTable->indexer->indexResultType); + if (FFlag::LuauNonReentrantGeneralization3) + { + // FIXME: We can probably do something more efficient here. + result &= unify(superTable->indexer->indexType, subTable->indexer->indexType); + result &= unify(superTable->indexer->indexResultType, subTable->indexer->indexResultType); + } } if (!subTable->indexer && subTable->state == TableState::Unsealed && superTable->indexer) diff --git a/Ast/include/Luau/ParseResult.h b/Ast/include/Luau/ParseResult.h index 7803dc55..3b9993d6 100644 --- a/Ast/include/Luau/ParseResult.h +++ b/Ast/include/Luau/ParseResult.h @@ -84,6 +84,6 @@ struct ParseExprResult CstNodeMap cstNodeMap{nullptr}; }; -static constexpr const char* kParseNameError = "%error-id%"; +inline constexpr const char* kParseNameError = "%error-id%"; } // namespace Luau diff --git a/Ast/include/Luau/Parser.h b/Ast/include/Luau/Parser.h index 52dd76ea..e7629606 100644 --- a/Ast/include/Luau/Parser.h +++ b/Ast/include/Luau/Parser.h @@ -245,8 +245,6 @@ private: }; TableIndexerResult parseTableIndexer(AstTableAccess access, std::optional accessLocation, Lexeme begin); - // Remove with FFlagLuauStoreCSTData2 - AstTableIndexer* parseTableIndexer_DEPRECATED(AstTableAccess access, std::optional accessLocation, Lexeme begin); AstTypeOrPack parseFunctionType(bool allowPack, const AstArray& attributes); AstType* parseFunctionTypeTail( diff --git a/Ast/src/Parser.cpp b/Ast/src/Parser.cpp index 521a187b..51053f7a 100644 --- a/Ast/src/Parser.cpp +++ b/Ast/src/Parser.cpp @@ -18,12 +18,9 @@ LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100) // flag so that we don't break production games by reverting syntax changes. // See docs/SyntaxChanges.md for an explanation. LUAU_FASTFLAGVARIABLE(LuauSolverV2) -LUAU_FASTFLAGVARIABLE(LuauStoreCSTData2) LUAU_FASTFLAGVARIABLE(LuauDeclareExternType) LUAU_FASTFLAGVARIABLE(LuauParseStringIndexer) -LUAU_FASTFLAGVARIABLE(LuauTypeFunResultInAutocomplete) LUAU_FASTFLAGVARIABLE(LuauStoreReturnTypesAsPackOnAst) -LUAU_FASTFLAGVARIABLE(LuauFixFunctionWithAttributesStartLocation) LUAU_FASTFLAGVARIABLE(LuauStoreLocalAnnotationColonPositions) LUAU_DYNAMIC_FASTFLAGVARIABLE(DebugLuauReportReturnTypeVariadicWithTypeSuffix, false) @@ -525,17 +522,10 @@ AstStat* Parser::parseRepeat() restoreLocals(localsBegin); - if (FFlag::LuauStoreCSTData2) - { - AstStatRepeat* node = allocator.alloc(Location(start, cond->location), cond, body, hasUntil); - if (options.storeCstData) - cstNodeMap[node] = allocator.alloc(untilPosition); - return node; - } - else - { - return allocator.alloc(Location(start, cond->location), cond, body, hasUntil); - } + AstStatRepeat* node = allocator.alloc(Location(start, cond->location), cond, body, hasUntil); + if (options.storeCstData) + cstNodeMap[node] = allocator.alloc(untilPosition); + return node; } // do block end @@ -555,7 +545,7 @@ AstStat* Parser::parseDo() if (body->hasEnd) body->location.end = endLocation.end; - if (FFlag::LuauStoreCSTData2 && options.storeCstData) + if (options.storeCstData) cstNodeMap[body] = allocator.alloc(endLocation.begin); return body; @@ -638,18 +628,11 @@ AstStat* Parser::parseFor() bool hasEnd = expectMatchEndAndConsume(Lexeme::ReservedEnd, matchDo); body->hasEnd = hasEnd; - if (FFlag::LuauStoreCSTData2) - { - AstStatFor* node = allocator.alloc(Location(start, end), var, from, to, step, body, hasDo, matchDo.location); - if (options.storeCstData) - cstNodeMap[node] = allocator.alloc(varname.colonPosition, equalsPosition, endCommaPosition, stepCommaPosition); + AstStatFor* node = allocator.alloc(Location(start, end), var, from, to, step, body, hasDo, matchDo.location); + if (options.storeCstData) + cstNodeMap[node] = allocator.alloc(varname.colonPosition, equalsPosition, endCommaPosition, stepCommaPosition); - return node; - } - else - { - return allocator.alloc(Location(start, end), var, from, to, step, body, hasDo, matchDo.location); - } + return node; } else { @@ -659,7 +642,7 @@ AstStat* Parser::parseFor() if (lexer.current().type == ',') { - if (FFlag::LuauStoreCSTData2 && options.storeCstData) + if (options.storeCstData) { Position initialCommaPosition = lexer.current().location.begin; nextLexeme(); @@ -678,7 +661,7 @@ AstStat* Parser::parseFor() TempVector values(scratchExpr); TempVector valuesCommaPositions(scratchPosition); - parseExprList(values, (FFlag::LuauStoreCSTData2 && options.storeCstData) ? &valuesCommaPositions : nullptr); + parseExprList(values, options.storeCstData ? &valuesCommaPositions : nullptr); Lexeme matchDo = lexer.current(); bool hasDo = expectAndConsume(Lexeme::ReservedDo, "for loop"); @@ -703,23 +686,17 @@ AstStat* Parser::parseFor() bool hasEnd = expectMatchEndAndConsume(Lexeme::ReservedEnd, matchDo); body->hasEnd = hasEnd; - if (FFlag::LuauStoreCSTData2) + AstStatForIn* node = + allocator.alloc(Location(start, end), copy(vars), copy(values), body, hasIn, inLocation, hasDo, matchDo.location); + if (options.storeCstData) { - AstStatForIn* node = - allocator.alloc(Location(start, end), copy(vars), copy(values), body, hasIn, inLocation, hasDo, matchDo.location); - if (options.storeCstData) - { - if (FFlag::LuauStoreLocalAnnotationColonPositions) - cstNodeMap[node] = allocator.alloc(extractAnnotationColonPositions(names), varsCommaPosition, copy(valuesCommaPositions)); - else - cstNodeMap[node] = allocator.alloc(AstArray{}, varsCommaPosition, copy(valuesCommaPositions)); - } - return node; - } - else - { - return allocator.alloc(Location(start, end), copy(vars), copy(values), body, hasIn, inLocation, hasDo, matchDo.location); + if (FFlag::LuauStoreLocalAnnotationColonPositions) + cstNodeMap[node] = + allocator.alloc(extractAnnotationColonPositions(names), varsCommaPosition, copy(valuesCommaPositions)); + else + cstNodeMap[node] = allocator.alloc(AstArray{}, varsCommaPosition, copy(valuesCommaPositions)); } + return node; } } @@ -776,11 +753,8 @@ AstStat* Parser::parseFunctionStat(const AstArray& attributes) { Location start = lexer.current().location; - if (FFlag::LuauFixFunctionWithAttributesStartLocation) - { - if (attributes.size > 0) - start = attributes.data[0]->location; - } + if (attributes.size > 0) + start = attributes.data[0]->location; Lexeme matchFunction = lexer.current(); nextLexeme(); @@ -797,17 +771,10 @@ AstStat* Parser::parseFunctionStat(const AstArray& attributes) matchRecoveryStopOnToken[Lexeme::ReservedEnd]--; - if (FFlag::LuauStoreCSTData2 && FFlag::LuauFixFunctionWithAttributesStartLocation) - { - AstStatFunction* node = allocator.alloc(Location(start, body->location), expr, body); - if (options.storeCstData) - cstNodeMap[node] = allocator.alloc(matchFunction.location.begin); - return node; - } - else - { - return allocator.alloc(Location(start, body->location), expr, body); - } + AstStatFunction* node = allocator.alloc(Location(start, body->location), expr, body); + if (options.storeCstData) + cstNodeMap[node] = allocator.alloc(matchFunction.location.begin); + return node; } @@ -922,11 +889,8 @@ AstStat* Parser::parseLocal(const AstArray& attributes) { Location start = lexer.current().location; - if (FFlag::LuauFixFunctionWithAttributesStartLocation) - { - if (attributes.size > 0) - start = attributes.data[0]->location; - } + if (attributes.size > 0) + start = attributes.data[0]->location; Position localKeywordPosition = lexer.current().location.begin; nextLexeme(); // local @@ -953,17 +917,10 @@ AstStat* Parser::parseLocal(const AstArray& attributes) Location location{start.begin, body->location.end}; - if (FFlag::LuauStoreCSTData2) - { - AstStatLocalFunction* node = allocator.alloc(location, var, body); - if (options.storeCstData) - cstNodeMap[node] = allocator.alloc(localKeywordPosition, functionKeywordPosition); - return node; - } - else - { - return allocator.alloc(location, var, body); - } + AstStatLocalFunction* node = allocator.alloc(location, var, body); + if (options.storeCstData) + cstNodeMap[node] = allocator.alloc(localKeywordPosition, functionKeywordPosition); + return node; } else { @@ -982,7 +939,7 @@ AstStat* Parser::parseLocal(const AstArray& attributes) TempVector names(scratchBinding); AstArray varsCommaPositions; - if (FFlag::LuauStoreCSTData2 && options.storeCstData) + if (options.storeCstData) parseBindingList(names, false, &varsCommaPositions); else parseBindingList(names); @@ -1002,7 +959,7 @@ AstStat* Parser::parseLocal(const AstArray& attributes) nextLexeme(); - parseExprList(values, (FFlag::LuauStoreCSTData2 && options.storeCstData) ? &valuesCommaPositions : nullptr); + parseExprList(values, options.storeCstData ? &valuesCommaPositions : nullptr); } for (size_t i = 0; i < names.size(); ++i) @@ -1010,23 +967,17 @@ AstStat* Parser::parseLocal(const AstArray& attributes) Location end = values.empty() ? lexer.previousLocation() : values.back()->location; - if (FFlag::LuauStoreCSTData2) + AstStatLocal* node = allocator.alloc(Location(start, end), copy(vars), copy(values), equalsSignLocation); + if (options.storeCstData) { - AstStatLocal* node = allocator.alloc(Location(start, end), copy(vars), copy(values), equalsSignLocation); - if (options.storeCstData) - { - if (FFlag::LuauStoreLocalAnnotationColonPositions) - cstNodeMap[node] = allocator.alloc(extractAnnotationColonPositions(names), varsCommaPositions, copy(valuesCommaPositions)); - else - cstNodeMap[node] = allocator.alloc(AstArray{}, varsCommaPositions, copy(valuesCommaPositions)); - } + if (FFlag::LuauStoreLocalAnnotationColonPositions) + cstNodeMap[node] = + allocator.alloc(extractAnnotationColonPositions(names), varsCommaPositions, copy(valuesCommaPositions)); + else + cstNodeMap[node] = allocator.alloc(AstArray{}, varsCommaPositions, copy(valuesCommaPositions)); + } - return node; - } - else - { - return allocator.alloc(Location(start, end), copy(vars), copy(values), equalsSignLocation); - } + return node; } } @@ -1041,21 +992,14 @@ AstStat* Parser::parseReturn() TempVector commaPositions(scratchPosition); if (!blockFollow(lexer.current()) && lexer.current().type != ';') - parseExprList(list, (FFlag::LuauStoreCSTData2 && options.storeCstData) ? &commaPositions : nullptr); + parseExprList(list, options.storeCstData ? &commaPositions : nullptr); Location end = list.empty() ? start : list.back()->location; - if (FFlag::LuauStoreCSTData2) - { - AstStatReturn* node = allocator.alloc(Location(start, end), copy(list)); - if (options.storeCstData) - cstNodeMap[node] = allocator.alloc(copy(commaPositions)); - return node; - } - else - { - return allocator.alloc(Location(start, end), copy(list)); - } + AstStatReturn* node = allocator.alloc(Location(start, end), copy(list)); + if (options.storeCstData) + cstNodeMap[node] = allocator.alloc(copy(commaPositions)); + return node; } // type Name [`<' varlist `>'] `=' Type @@ -1078,7 +1022,7 @@ AstStat* Parser::parseTypeAlias(const Location& start, bool exported, Position t Position genericsOpenPosition{0, 0}; AstArray genericsCommaPositions; Position genericsClosePosition{0, 0}; - auto [generics, genericPacks] = FFlag::LuauStoreCSTData2 && options.storeCstData + auto [generics, genericPacks] = options.storeCstData ? parseGenericTypeList( /* withDefaultValues= */ true, &genericsOpenPosition, &genericsCommaPositions, &genericsClosePosition ) @@ -1089,20 +1033,13 @@ AstStat* Parser::parseTypeAlias(const Location& start, bool exported, Position t AstType* type = parseType(); - if (FFlag::LuauStoreCSTData2) - { - AstStatTypeAlias* node = - allocator.alloc(Location(start, type->location), name->name, name->location, generics, genericPacks, type, exported); - if (options.storeCstData) - cstNodeMap[node] = allocator.alloc( - typeKeywordPosition, genericsOpenPosition, genericsCommaPositions, genericsClosePosition, equalsPosition - ); - return node; - } - else - { - return allocator.alloc(Location(start, type->location), name->name, name->location, generics, genericPacks, type, exported); - } + AstStatTypeAlias* node = + allocator.alloc(Location(start, type->location), name->name, name->location, generics, genericPacks, type, exported); + if (options.storeCstData) + cstNodeMap[node] = allocator.alloc( + typeKeywordPosition, genericsOpenPosition, genericsCommaPositions, genericsClosePosition, equalsPosition + ); + return node; } // type function Name `(' arglist `)' `=' funcbody `end' @@ -1111,7 +1048,7 @@ AstStat* Parser::parseTypeFunction(const Location& start, bool exported, Positio Lexeme matchFn = lexer.current(); nextLexeme(); - size_t errorsAtStart = FFlag::LuauTypeFunResultInAutocomplete ? parseErrors.size() : 0; + size_t errorsAtStart = parseErrors.size(); // parse the name of the type function std::optional fnName = parseNameOpt("type function name"); @@ -1132,20 +1069,13 @@ AstStat* Parser::parseTypeFunction(const Location& start, bool exported, Positio matchRecoveryStopOnToken[Lexeme::ReservedEnd]--; - bool hasErrors = FFlag::LuauTypeFunResultInAutocomplete ? parseErrors.size() > errorsAtStart : false; + bool hasErrors = parseErrors.size() > errorsAtStart; - if (FFlag::LuauStoreCSTData2) - { - AstStatTypeFunction* node = - allocator.alloc(Location(start, body->location), fnName->name, fnName->location, body, exported, hasErrors); - if (options.storeCstData) - cstNodeMap[node] = allocator.alloc(typeKeywordPosition, matchFn.location.begin); - return node; - } - else - { - return allocator.alloc(Location(start, body->location), fnName->name, fnName->location, body, exported, hasErrors); - } + AstStatTypeFunction* node = + allocator.alloc(Location(start, body->location), fnName->name, fnName->location, body, exported, hasErrors); + if (options.storeCstData) + cstNodeMap[node] = allocator.alloc(typeKeywordPosition, matchFn.location.begin); + return node; } AstDeclaredExternTypeProperty Parser::parseDeclaredExternTypeMethod(const AstArray& attributes) @@ -1430,10 +1360,7 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray values(scratchExprAux); TempVector valuesCommaPositions(scratchPosition); - parseExprList(values, FFlag::LuauStoreCSTData2 && options.storeCstData ? &valuesCommaPositions : nullptr); + parseExprList(values, options.storeCstData ? &valuesCommaPositions : nullptr); - if (FFlag::LuauStoreCSTData2) - { - AstStatAssign* node = allocator.alloc(Location(initial->location, values.back()->location), copy(vars), copy(values)); - cstNodeMap[node] = allocator.alloc(copy(varsCommaPositions), equalsPosition, copy(valuesCommaPositions)); - return node; - } - else - { - return allocator.alloc(Location(initial->location, values.back()->location), copy(vars), copy(values)); - } + AstStatAssign* node = allocator.alloc(Location(initial->location, values.back()->location), copy(vars), copy(values)); + cstNodeMap[node] = allocator.alloc(copy(varsCommaPositions), equalsPosition, copy(valuesCommaPositions)); + return node; } // var [`+=' | `-=' | `*=' | `/=' | `%=' | `^=' | `..='] exp @@ -1630,17 +1537,10 @@ AstStat* Parser::parseCompoundAssignment(AstExpr* initial, AstExprBinary::Op op) AstExpr* value = parseExpr(); - if (FFlag::LuauStoreCSTData2) - { - AstStatCompoundAssign* node = allocator.alloc(Location(initial->location, value->location), op, initial, value); - if (options.storeCstData) - cstNodeMap[node] = allocator.alloc(opPosition); - return node; - } - else - { - return allocator.alloc(Location(initial->location, value->location), op, initial, value); - } + AstStatCompoundAssign* node = allocator.alloc(Location(initial->location, value->location), op, initial, value); + if (options.storeCstData) + cstNodeMap[node] = allocator.alloc(opPosition); + return node; } std::pair> Parser::prepareFunctionArguments(const Location& start, bool hasself, const TempVector& args) @@ -1672,16 +1572,13 @@ std::pair Parser::parseFunctionBody( Location start = matchFunction.location; - if (FFlag::LuauFixFunctionWithAttributesStartLocation) - { - if (attributes.size > 0) - start = attributes.data[0]->location; - } + if (attributes.size > 0) + start = attributes.data[0]->location; - auto* cstNode = FFlag::LuauStoreCSTData2 && options.storeCstData ? allocator.alloc() : nullptr; + auto* cstNode = options.storeCstData ? allocator.alloc() : nullptr; auto [generics, genericPacks] = - FFlag::LuauStoreCSTData2 && cstNode + cstNode ? parseGenericTypeList( /* withDefaultValues= */ false, &cstNode->openGenericsPosition, &cstNode->genericsCommaPositions, &cstNode->closeGenericsPosition ) @@ -1759,56 +1656,31 @@ std::pair Parser::parseFunctionBody( bool hasEnd = expectMatchEndAndConsume(Lexeme::ReservedEnd, matchFunction); body->hasEnd = hasEnd; - if (FFlag::LuauStoreCSTData2) + AstExprFunction* node = allocator.alloc( + Location(start, end), + attributes, + generics, + genericPacks, + self, + vars, + vararg, + varargLocation, + body, + functionStack.size(), + debugname, + typelist, + varargAnnotation, + argLocation + ); + if (options.storeCstData) { - AstExprFunction* node = allocator.alloc( - Location(start, end), - attributes, - generics, - genericPacks, - self, - vars, - vararg, - varargLocation, - body, - functionStack.size(), - debugname, - typelist, - varargAnnotation, - argLocation - ); - if (options.storeCstData) - { - cstNode->functionKeywordPosition = matchFunction.location.begin; - if (FFlag::LuauStoreLocalAnnotationColonPositions) - cstNode->argsAnnotationColonPositions = extractAnnotationColonPositions(args); - cstNodeMap[node] = cstNode; - } + cstNode->functionKeywordPosition = matchFunction.location.begin; + if (FFlag::LuauStoreLocalAnnotationColonPositions) + cstNode->argsAnnotationColonPositions = extractAnnotationColonPositions(args); + cstNodeMap[node] = cstNode; + } - return {node, funLocal}; - } - else - { - return { - allocator.alloc( - Location(start, end), - attributes, - generics, - genericPacks, - self, - vars, - vararg, - varargLocation, - body, - functionStack.size(), - debugname, - typelist, - varargAnnotation, - argLocation - ), - funLocal - }; - } + return {node, funLocal}; } std::pair Parser::parseFunctionBody_DEPRECATED( @@ -1823,16 +1695,13 @@ std::pair Parser::parseFunctionBody_DEPRECATED( Location start = matchFunction.location; - if (FFlag::LuauFixFunctionWithAttributesStartLocation) - { - if (attributes.size > 0) - start = attributes.data[0]->location; - } + if (attributes.size > 0) + start = attributes.data[0]->location; - auto* cstNode = FFlag::LuauStoreCSTData2 && options.storeCstData ? allocator.alloc() : nullptr; + auto* cstNode = options.storeCstData ? allocator.alloc() : nullptr; auto [generics, genericPacks] = - FFlag::LuauStoreCSTData2 && cstNode + cstNode ? parseGenericTypeList( /* withDefaultValues= */ false, &cstNode->openGenericsPosition, &cstNode->genericsCommaPositions, &cstNode->closeGenericsPosition ) @@ -1899,54 +1768,29 @@ std::pair Parser::parseFunctionBody_DEPRECATED( bool hasEnd = expectMatchEndAndConsume(Lexeme::ReservedEnd, matchFunction); body->hasEnd = hasEnd; - if (FFlag::LuauStoreCSTData2) + AstExprFunction* node = allocator.alloc( + Location(start, end), + attributes, + generics, + genericPacks, + self, + vars, + vararg, + varargLocation, + body, + functionStack.size(), + debugname, + typelist, + varargAnnotation, + argLocation + ); + if (options.storeCstData) { - AstExprFunction* node = allocator.alloc( - Location(start, end), - attributes, - generics, - genericPacks, - self, - vars, - vararg, - varargLocation, - body, - functionStack.size(), - debugname, - typelist, - varargAnnotation, - argLocation - ); - if (options.storeCstData) - { - cstNode->functionKeywordPosition = matchFunction.location.begin; - cstNodeMap[node] = cstNode; - } + cstNode->functionKeywordPosition = matchFunction.location.begin; + cstNodeMap[node] = cstNode; + } - return {node, funLocal}; - } - else - { - return { - allocator.alloc( - Location(start, end), - attributes, - generics, - genericPacks, - self, - vars, - vararg, - varargLocation, - body, - functionStack.size(), - debugname, - typelist, - varargAnnotation, - argLocation - ), - funLocal - }; - } + return {node, funLocal}; } // explist ::= {exp `,'} exp @@ -1956,7 +1800,7 @@ void Parser::parseExprList(TempVector& result, TempVector* c while (lexer.current().type == ',') { - if (FFlag::LuauStoreCSTData2 && commaPositions) + if (commaPositions) commaPositions->push_back(lexer.current().location.begin); nextLexeme(); @@ -2007,7 +1851,7 @@ std::tuple Parser::parseBindingList( { TempVector localCommaPositions(scratchPosition); - if (FFlag::LuauStoreCSTData2 && commaPositions && initialCommaPosition) + if (commaPositions && initialCommaPosition) localCommaPositions.push_back(*initialCommaPosition); while (true) @@ -2027,7 +1871,7 @@ std::tuple Parser::parseBindingList( tailAnnotation = parseVariadicArgumentTypePack(); } - if (FFlag::LuauStoreCSTData2 && commaPositions) + if (commaPositions) *commaPositions = copy(localCommaPositions); return {true, varargLocation, tailAnnotation}; @@ -2037,12 +1881,12 @@ std::tuple Parser::parseBindingList( if (lexer.current().type != ',') break; - if (FFlag::LuauStoreCSTData2 && commaPositions) + if (commaPositions) localCommaPositions.push_back(lexer.current().location.begin); nextLexeme(); } - if (FFlag::LuauStoreCSTData2 && commaPositions) + if (commaPositions) *commaPositions = copy(localCommaPositions); return {false, Location(), nullptr}; @@ -2077,7 +1921,7 @@ AstTypePack* Parser::parseTypeList( // Fill in previous argument names with empty slots while (resultNames.size() < result.size()) resultNames.push_back({}); - if (FFlag::LuauStoreCSTData2 && nameColonPositions) + if (nameColonPositions) { while (nameColonPositions->size() < result.size()) nameColonPositions->push_back({}); @@ -2086,7 +1930,7 @@ AstTypePack* Parser::parseTypeList( resultNames.push_back(AstArgumentName{AstName(lexer.current().name), lexer.current().location}); nextLexeme(); - if (FFlag::LuauStoreCSTData2 && nameColonPositions) + if (nameColonPositions) nameColonPositions->push_back(lexer.current().location.begin); expectAndConsume(':'); } @@ -2094,7 +1938,7 @@ AstTypePack* Parser::parseTypeList( { // If we have a type with named arguments, provide elements for all types resultNames.push_back({}); - if (FFlag::LuauStoreCSTData2 && nameColonPositions) + if (nameColonPositions) nameColonPositions->push_back({}); } @@ -2102,7 +1946,7 @@ AstTypePack* Parser::parseTypeList( if (lexer.current().type != ',') break; - if (FFlag::LuauStoreCSTData2 && commaPositions) + if (commaPositions) commaPositions->push_back(lexer.current().location.begin); nextLexeme(); @@ -2124,7 +1968,7 @@ AstTypePack* Parser::parseOptionalReturnType(Position* returnSpecifierPosition) if (lexer.current().type == Lexeme::SkinnyArrow) report(lexer.current().location, "Function return type annotations are written after ':' instead of '->'"); - if (FFlag::LuauStoreCSTData2 && returnSpecifierPosition) + if (returnSpecifierPosition) *returnSpecifierPosition = lexer.current().location.begin; nextLexeme(); @@ -2158,7 +2002,7 @@ std::optional Parser::parseOptionalReturnType_DEPRECATED(Luau::Posi if (lexer.current().type == Lexeme::SkinnyArrow) report(lexer.current().location, "Function return type annotations are written after ':' instead of '->'"); - if (FFlag::LuauStoreCSTData2 && returnSpecifierPosition) + if (returnSpecifierPosition) *returnSpecifierPosition = lexer.current().location.begin; nextLexeme(); @@ -2342,8 +2186,6 @@ std::pair Parser::parseReturnType_DEPRECATED() std::pair Parser::extractStringDetails() { - LUAU_ASSERT(FFlag::LuauStoreCSTData2); - CstExprConstantString::QuoteStyle style; unsigned int blockDepth = 0; @@ -2396,26 +2238,6 @@ Parser::TableIndexerResult Parser::parseTableIndexer(AstTableAccess access, std: }; } -// Remove with FFlagLuauStoreCSTData2 -AstTableIndexer* Parser::parseTableIndexer_DEPRECATED(AstTableAccess access, std::optional accessLocation, Lexeme begin) -{ - if (!FFlag::LuauParseStringIndexer) - { - begin = lexer.current(); - nextLexeme(); // [ - } - - AstType* index = parseType(); - - expectMatchAndConsume(']', begin); - - expectAndConsume(':', "table field"); - - AstType* result = parseType(); - - return allocator.alloc(AstTableIndexer{index, result, Location(begin.location, result->location), access, accessLocation}); -} - // TableProp ::= Name `:' Type // TablePropOrIndexer ::= TableProp | TableIndexer // PropList ::= TablePropOrIndexer {fieldsep TablePropOrIndexer} [fieldsep] @@ -2467,7 +2289,7 @@ AstType* Parser::parseTableType(bool inDeclarationContext) { CstExprConstantString::QuoteStyle style; unsigned int blockDepth = 0; - if (FFlag::LuauStoreCSTData2 && options.storeCstData) + if (options.storeCstData) std::tie(style, blockDepth) = extractStringDetails(); Position stringPosition = lexer.current().location.begin; @@ -2487,7 +2309,7 @@ AstType* Parser::parseTableType(bool inDeclarationContext) if (chars && !containsNull) { props.push_back(AstTableProp{AstName(chars->data), begin.location, type, access, accessLocation}); - if (FFlag::LuauStoreCSTData2 && options.storeCstData) + if (options.storeCstData) cstItems.push_back(CstTypeTable::Item{ CstTypeTable::Item::Kind::StringProperty, begin.location.begin, @@ -2508,35 +2330,24 @@ AstType* Parser::parseTableType(bool inDeclarationContext) { // maybe we don't need to parse the entire badIndexer... // however, we either have { or [ to lint, not the entire table type or the bad indexer. - AstTableIndexer* badIndexer; - if (FFlag::LuauStoreCSTData2) - badIndexer = parseTableIndexer(access, accessLocation, begin).node; - else - badIndexer = parseTableIndexer_DEPRECATED(access, accessLocation, begin); + AstTableIndexer* badIndexer = parseTableIndexer(access, accessLocation, begin).node; // we lose all additional indexer expressions from the AST after error recovery here report(badIndexer->location, "Cannot have more than one table indexer"); } else { - if (FFlag::LuauStoreCSTData2) - { - auto tableIndexerResult = parseTableIndexer(access, accessLocation, begin); - indexer = tableIndexerResult.node; - if (options.storeCstData) - cstItems.push_back(CstTypeTable::Item{ - CstTypeTable::Item::Kind::Indexer, - tableIndexerResult.indexerOpenPosition, - tableIndexerResult.indexerClosePosition, - tableIndexerResult.colonPosition, - tableSeparator(), - lexer.current().location.begin, - }); - } - else - { - indexer = parseTableIndexer_DEPRECATED(access, accessLocation, begin); - } + auto tableIndexerResult = parseTableIndexer(access, accessLocation, begin); + indexer = tableIndexerResult.node; + if (options.storeCstData) + cstItems.push_back(CstTypeTable::Item{ + CstTypeTable::Item::Kind::Indexer, + tableIndexerResult.indexerOpenPosition, + tableIndexerResult.indexerClosePosition, + tableIndexerResult.colonPosition, + tableSeparator(), + lexer.current().location.begin, + }); } } } @@ -2564,7 +2375,7 @@ AstType* Parser::parseTableType(bool inDeclarationContext) AstType* type = parseType(inDeclarationContext); props.push_back(AstTableProp{name->name, name->location, type, access, accessLocation}); - if (FFlag::LuauStoreCSTData2 && options.storeCstData) + if (options.storeCstData) cstItems.push_back(CstTypeTable::Item{ CstTypeTable::Item::Kind::Property, Position{0, 0}, @@ -2584,7 +2395,7 @@ AstType* Parser::parseTableType(bool inDeclarationContext) CstExprConstantString::QuoteStyle style; unsigned int blockDepth = 0; - if (FFlag::LuauStoreCSTData2 && options.storeCstData) + if (options.storeCstData) std::tie(style, blockDepth) = extractStringDetails(); Position stringPosition = lexer.current().location.begin; @@ -2604,7 +2415,7 @@ AstType* Parser::parseTableType(bool inDeclarationContext) if (chars && !containsNull) { props.push_back(AstTableProp{AstName(chars->data), begin.location, type, access, accessLocation}); - if (FFlag::LuauStoreCSTData2 && options.storeCstData) + if (options.storeCstData) cstItems.push_back(CstTypeTable::Item{ CstTypeTable::Item::Kind::StringProperty, begin.location.begin, @@ -2625,39 +2436,26 @@ AstType* Parser::parseTableType(bool inDeclarationContext) { // maybe we don't need to parse the entire badIndexer... // however, we either have { or [ to lint, not the entire table type or the bad indexer. - AstTableIndexer* badIndexer; - if (FFlag::LuauStoreCSTData2) - // the last param in the parseTableIndexer is ignored - badIndexer = parseTableIndexer(access, accessLocation, lexer.current()).node; - else - // the last param in the parseTableIndexer is ignored - badIndexer = parseTableIndexer_DEPRECATED(access, accessLocation, lexer.current()); + // the last param in the parseTableIndexer is ignored + AstTableIndexer* badIndexer = parseTableIndexer(access, accessLocation, lexer.current()).node; // we lose all additional indexer expressions from the AST after error recovery here report(badIndexer->location, "Cannot have more than one table indexer"); } else { - if (FFlag::LuauStoreCSTData2) - { - // the last param in the parseTableIndexer is ignored - auto tableIndexerResult = parseTableIndexer(access, accessLocation, lexer.current()); - indexer = tableIndexerResult.node; - if (options.storeCstData) - cstItems.push_back(CstTypeTable::Item{ - CstTypeTable::Item::Kind::Indexer, - tableIndexerResult.indexerOpenPosition, - tableIndexerResult.indexerClosePosition, - tableIndexerResult.colonPosition, - tableSeparator(), - lexer.current().location.begin, - }); - } - else - { - // the last param in the parseTableIndexer is ignored - indexer = parseTableIndexer_DEPRECATED(access, accessLocation, lexer.current()); - } + // the last param in the parseTableIndexer is ignored + auto tableIndexerResult = parseTableIndexer(access, accessLocation, lexer.current()); + indexer = tableIndexerResult.node; + if (options.storeCstData) + cstItems.push_back(CstTypeTable::Item{ + CstTypeTable::Item::Kind::Indexer, + tableIndexerResult.indexerOpenPosition, + tableIndexerResult.indexerClosePosition, + tableIndexerResult.colonPosition, + tableSeparator(), + lexer.current().location.begin, + }); } } else if (props.empty() && !indexer && !(lexer.current().type == Lexeme::Name && lexer.lookahead().type == ':')) @@ -2684,7 +2482,7 @@ AstType* Parser::parseTableType(bool inDeclarationContext) AstType* type = parseType(inDeclarationContext); props.push_back(AstTableProp{name->name, name->location, type, access, accessLocation}); - if (FFlag::LuauStoreCSTData2 && options.storeCstData) + if (options.storeCstData) cstItems.push_back(CstTypeTable::Item{ CstTypeTable::Item::Kind::Property, Position{0, 0}, @@ -2712,17 +2510,10 @@ AstType* Parser::parseTableType(bool inDeclarationContext) if (!expectMatchAndConsume('}', matchBrace, /* searchForMissing = */ true)) end = lexer.previousLocation(); - if (FFlag::LuauStoreCSTData2) - { - AstTypeTable* node = allocator.alloc(Location(start, end), copy(props), indexer); - if (options.storeCstData) - cstNodeMap[node] = allocator.alloc(copy(cstItems), isArray); - return node; - } - else - { - return allocator.alloc(Location(start, end), copy(props), indexer); - } + AstTypeTable* node = allocator.alloc(Location(start, end), copy(props), indexer); + if (options.storeCstData) + cstNodeMap[node] = allocator.alloc(copy(cstItems), isArray); + return node; } // ReturnType ::= Type | `(' TypeList `)' @@ -2738,7 +2529,7 @@ AstTypeOrPack Parser::parseFunctionType(bool allowPack, const AstArray Position genericsOpenPosition{0, 0}; AstArray genericsCommaPositions; Position genericsClosePosition{0, 0}; - auto [generics, genericPacks] = FFlag::LuauStoreCSTData2 && options.storeCstData + auto [generics, genericPacks] = options.storeCstData ? parseGenericTypeList( /* withDefaultValues= */ false, &genericsOpenPosition, &genericsCommaPositions, &genericsClosePosition ) @@ -2758,7 +2549,7 @@ AstTypeOrPack Parser::parseFunctionType(bool allowPack, const AstArray if (lexer.current().type != ')') { - if (FFlag::LuauStoreCSTData2 && options.storeCstData) + if (options.storeCstData) varargAnnotation = parseTypeList(params, names, &argCommaPositions, &nameColonPositions); else varargAnnotation = parseTypeList(params, names); @@ -2781,18 +2572,11 @@ AstTypeOrPack Parser::parseFunctionType(bool allowPack, const AstArray { if (allowPack) { - if (FFlag::LuauStoreCSTData2) - { - AstTypePackExplicit* node = allocator.alloc(begin.location, AstTypeList{paramTypes, nullptr}); - if (options.storeCstData) - cstNodeMap[node] = - allocator.alloc(parameterStart.location.begin, closeArgsLocation.begin, copy(argCommaPositions)); - return {{}, node}; - } - else - { - return {{}, allocator.alloc(begin.location, AstTypeList{paramTypes, nullptr})}; - } + AstTypePackExplicit* node = allocator.alloc(begin.location, AstTypeList{paramTypes, nullptr}); + if (options.storeCstData) + cstNodeMap[node] = + allocator.alloc(parameterStart.location.begin, closeArgsLocation.begin, copy(argCommaPositions)); + return {{}, node}; } else { @@ -2802,45 +2586,30 @@ AstTypeOrPack Parser::parseFunctionType(bool allowPack, const AstArray if (!forceFunctionType && !returnTypeIntroducer && allowPack) { - if (FFlag::LuauStoreCSTData2) - { - AstTypePackExplicit* node = allocator.alloc(begin.location, AstTypeList{paramTypes, varargAnnotation}); - if (options.storeCstData) - cstNodeMap[node] = - allocator.alloc(parameterStart.location.begin, closeArgsLocation.begin, copy(argCommaPositions)); - return {{}, node}; - } - else - { - return {{}, allocator.alloc(begin.location, AstTypeList{paramTypes, varargAnnotation})}; - } + AstTypePackExplicit* node = allocator.alloc(begin.location, AstTypeList{paramTypes, varargAnnotation}); + if (options.storeCstData) + cstNodeMap[node] = allocator.alloc(parameterStart.location.begin, closeArgsLocation.begin, copy(argCommaPositions)); + return {{}, node}; } AstArray> paramNames = copy(names); - if (FFlag::LuauStoreCSTData2) + Position returnArrowPosition = lexer.current().location.begin; + AstType* node = parseFunctionTypeTail(begin, attributes, generics, genericPacks, paramTypes, paramNames, varargAnnotation); + if (options.storeCstData && node->is()) { - Position returnArrowPosition = lexer.current().location.begin; - AstType* node = parseFunctionTypeTail(begin, attributes, generics, genericPacks, paramTypes, paramNames, varargAnnotation); - if (options.storeCstData && node->is()) - { - cstNodeMap[node] = allocator.alloc( - genericsOpenPosition, - genericsCommaPositions, - genericsClosePosition, - parameterStart.location.begin, - copy(nameColonPositions), - copy(argCommaPositions), - closeArgsLocation.begin, - returnArrowPosition - ); - } - return {node, {}}; - } - else - { - return {parseFunctionTypeTail(begin, attributes, generics, genericPacks, paramTypes, paramNames, varargAnnotation), {}}; + cstNodeMap[node] = allocator.alloc( + genericsOpenPosition, + genericsCommaPositions, + genericsClosePosition, + parameterStart.location.begin, + copy(nameColonPositions), + copy(argCommaPositions), + closeArgsLocation.begin, + returnArrowPosition + ); } + return {node, {}}; } AstType* Parser::parseFunctionTypeTail( @@ -2935,7 +2704,7 @@ AstType* Parser::parseTypeSuffix(AstType* type, const Location& begin) isUnion = true; - if (FFlag::LuauStoreCSTData2 && options.storeCstData) + if (options.storeCstData) { if (type == nullptr && !leadingPosition.has_value()) leadingPosition = separatorPosition; @@ -2965,7 +2734,7 @@ AstType* Parser::parseTypeSuffix(AstType* type, const Location& begin) isIntersection = true; - if (FFlag::LuauStoreCSTData2 && options.storeCstData) + if (options.storeCstData) { if (type == nullptr && !leadingPosition.has_value()) leadingPosition = separatorPosition; @@ -2998,31 +2767,20 @@ AstType* Parser::parseTypeSuffix(AstType* type, const Location& begin) location.end = parts.back()->location.end; - if (FFlag::LuauStoreCSTData2) + if (isUnion) { - if (isUnion) - { - AstTypeUnion* node = allocator.alloc(location, copy(parts)); - if (options.storeCstData) - cstNodeMap[node] = allocator.alloc(leadingPosition, copy(separatorPositions)); - return node; - } - - if (isIntersection) - { - AstTypeIntersection* node = allocator.alloc(location, copy(parts)); - if (options.storeCstData) - cstNodeMap[node] = allocator.alloc(leadingPosition, copy(separatorPositions)); - return node; - } + AstTypeUnion* node = allocator.alloc(location, copy(parts)); + if (options.storeCstData) + cstNodeMap[node] = allocator.alloc(leadingPosition, copy(separatorPositions)); + return node; } - else - { - if (isUnion) - return allocator.alloc(location, copy(parts)); - if (isIntersection) - return allocator.alloc(location, copy(parts)); + if (isIntersection) + { + AstTypeIntersection* node = allocator.alloc(location, copy(parts)); + if (options.storeCstData) + cstNodeMap[node] = allocator.alloc(leadingPosition, copy(separatorPositions)); + return node; } LUAU_ASSERT(false); @@ -3110,35 +2868,22 @@ AstTypeOrPack Parser::parseSimpleType(bool allowPack, bool inDeclarationContext) } else if (lexer.current().type == Lexeme::RawString || lexer.current().type == Lexeme::QuotedString) { - if (FFlag::LuauStoreCSTData2) - { - CstExprConstantString::QuoteStyle style; - unsigned int blockDepth = 0; - if (options.storeCstData) - std::tie(style, blockDepth) = extractStringDetails(); + CstExprConstantString::QuoteStyle style; + unsigned int blockDepth = 0; + if (options.storeCstData) + std::tie(style, blockDepth) = extractStringDetails(); - AstArray originalString; - if (std::optional> value = parseCharArray(options.storeCstData ? &originalString : nullptr)) - { - AstArray svalue = *value; - auto node = allocator.alloc(start, svalue); - if (options.storeCstData) - cstNodeMap[node] = allocator.alloc(originalString, style, blockDepth); - return {node}; - } - else - return {reportTypeError(start, {}, "String literal contains malformed escape sequence")}; + AstArray originalString; + if (std::optional> value = parseCharArray(options.storeCstData ? &originalString : nullptr)) + { + AstArray svalue = *value; + auto node = allocator.alloc(start, svalue); + if (options.storeCstData) + cstNodeMap[node] = allocator.alloc(originalString, style, blockDepth); + return {node}; } else - { - if (std::optional> value = parseCharArray()) - { - AstArray svalue = *value; - return {allocator.alloc(start, svalue)}; - } - else - return {reportTypeError(start, {}, "String literal contains malformed escape sequence")}; - } + return {reportTypeError(start, {}, "String literal contains malformed escape sequence")}; } else if (lexer.current().type == Lexeme::InterpStringBegin || lexer.current().type == Lexeme::InterpStringSimple) { @@ -3160,24 +2905,12 @@ AstTypeOrPack Parser::parseSimpleType(bool allowPack, bool inDeclarationContext) if (lexer.current().type == '.') { - if (FFlag::LuauStoreCSTData2) - { - prefixPointPosition = lexer.current().location.begin; - nextLexeme(); + prefixPointPosition = lexer.current().location.begin; + nextLexeme(); - prefix = name.name; - prefixLocation = name.location; - name = parseIndexName("field name", *prefixPointPosition); - } - else - { - Position pointPosition = lexer.current().location.begin; - nextLexeme(); - - prefix = name.name; - prefixLocation = name.location; - name = parseIndexName("field name", pointPosition); - } + prefix = name.name; + prefixLocation = name.location; + name = parseIndexName("field name", *prefixPointPosition); } else if (lexer.current().type == Lexeme::Dot3) { @@ -3195,17 +2928,10 @@ AstTypeOrPack Parser::parseSimpleType(bool allowPack, bool inDeclarationContext) expectMatchAndConsume(')', typeofBegin); - if (FFlag::LuauStoreCSTData2) - { - AstTypeTypeof* node = allocator.alloc(Location(start, end), expr); - if (options.storeCstData) - cstNodeMap[node] = allocator.alloc(typeofBegin.location.begin, end.begin); - return {node, {}}; - } - else - { - return {allocator.alloc(Location(start, end), expr), {}}; - } + AstTypeTypeof* node = allocator.alloc(Location(start, end), expr); + if (options.storeCstData) + cstNodeMap[node] = allocator.alloc(typeofBegin.location.begin, end.begin); + return {node, {}}; } bool hasParameters = false; @@ -3217,7 +2943,7 @@ AstTypeOrPack Parser::parseSimpleType(bool allowPack, bool inDeclarationContext) if (lexer.current().type == '<') { hasParameters = true; - if (FFlag::LuauStoreCSTData2 && options.storeCstData) + if (options.storeCstData) parameters = parseTypeParams(¶metersOpeningPosition, ¶metersCommaPositions, ¶metersClosingPosition); else parameters = parseTypeParams(); @@ -3225,23 +2951,13 @@ AstTypeOrPack Parser::parseSimpleType(bool allowPack, bool inDeclarationContext) Location end = lexer.previousLocation(); - if (FFlag::LuauStoreCSTData2) - { - AstTypeReference* node = - allocator.alloc(Location(start, end), prefix, name.name, prefixLocation, name.location, hasParameters, parameters); - if (options.storeCstData) - cstNodeMap[node] = allocator.alloc( - prefixPointPosition, parametersOpeningPosition, copy(parametersCommaPositions), parametersClosingPosition - ); - return {node, {}}; - } - else - { - return { - allocator.alloc(Location(start, end), prefix, name.name, prefixLocation, name.location, hasParameters, parameters), - {} - }; - } + AstTypeReference* node = + allocator.alloc(Location(start, end), prefix, name.name, prefixLocation, name.location, hasParameters, parameters); + if (options.storeCstData) + cstNodeMap[node] = allocator.alloc( + prefixPointPosition, parametersOpeningPosition, copy(parametersCommaPositions), parametersClosingPosition + ); + return {node, {}}; } else if (lexer.current().type == '{') { @@ -3286,17 +3002,10 @@ AstTypePack* Parser::parseVariadicArgumentTypePack() // This will not fail because of the lookahead guard. expectAndConsume(Lexeme::Dot3, "generic type pack annotation"); - if (FFlag::LuauStoreCSTData2) - { - AstTypePackGeneric* node = allocator.alloc(Location(name.location, end), name.name); - if (options.storeCstData) - cstNodeMap[node] = allocator.alloc(end.begin); - return node; - } - else - { - return allocator.alloc(Location(name.location, end), name.name); - } + AstTypePackGeneric* node = allocator.alloc(Location(name.location, end), name.name); + if (options.storeCstData) + cstNodeMap[node] = allocator.alloc(end.begin); + return node; } // Variadic: T else @@ -3324,17 +3033,10 @@ AstTypePack* Parser::parseTypePack() // This will not fail because of the lookahead guard. expectAndConsume(Lexeme::Dot3, "generic type pack annotation"); - if (FFlag::LuauStoreCSTData2) - { - AstTypePackGeneric* node = allocator.alloc(Location(name.location, end), name.name); - if (options.storeCstData) - cstNodeMap[node] = allocator.alloc(end.begin); - return node; - } - else - { - return allocator.alloc(Location(name.location, end), name.name); - } + AstTypePackGeneric* node = allocator.alloc(Location(name.location, end), name.name); + if (options.storeCstData) + cstNodeMap[node] = allocator.alloc(end.begin); + return node; } // TODO: shouldParseTypePack can be removed and parseTypePack can be called unconditionally instead @@ -3518,7 +3220,7 @@ AstExpr* Parser::parseExpr(unsigned int limit) AstExpr* subexpr = parseExpr(unaryPriority); expr = allocator.alloc(Location(start, subexpr->location), *uop, subexpr); - if (FFlag::LuauStoreCSTData2 && options.storeCstData) + if (options.storeCstData) cstNodeMap[expr] = allocator.alloc(opPosition); } else @@ -3541,7 +3243,7 @@ AstExpr* Parser::parseExpr(unsigned int limit) AstExpr* next = parseExpr(binaryPriority[*op].right); expr = allocator.alloc(Location(start, next->location), *op, expr, next); - if (FFlag::LuauStoreCSTData2 && options.storeCstData) + if (options.storeCstData) cstNodeMap[expr] = allocator.alloc(opPosition); op = parseBinaryOp(lexer.current()); @@ -3648,7 +3350,7 @@ AstExpr* Parser::parsePrimaryExpr(bool asStatement) expectMatchAndConsume(']', matchBracket); expr = allocator.alloc(Location(start, end), expr, index); - if (FFlag::LuauStoreCSTData2 && options.storeCstData) + if (options.storeCstData) cstNodeMap[expr] = allocator.alloc(matchBracket.position, closeBracketPosition); } else if (lexer.current().type == ':') @@ -3699,7 +3401,7 @@ AstExpr* Parser::parseAssertionExpr() if (lexer.current().type == Lexeme::DoubleColon) { CstExprTypeAssertion* cstNode = nullptr; - if (FFlag::LuauStoreCSTData2 && options.storeCstData) + if (options.storeCstData) { Position opPosition = lexer.current().location.begin; cstNode = allocator.alloc(opPosition); @@ -3707,7 +3409,7 @@ AstExpr* Parser::parseAssertionExpr() nextLexeme(); AstType* annotation = parseType(); AstExprTypeAssertion* node = allocator.alloc(Location(start, annotation->location), expr, annotation); - if (FFlag::LuauStoreCSTData2 && options.storeCstData) + if (options.storeCstData) cstNodeMap[node] = cstNode; return node; } @@ -3892,24 +3594,17 @@ AstExpr* Parser::parseFunctionArgs(AstExpr* func, bool self) TempVector commaPositions(scratchPosition); if (lexer.current().type != ')') - parseExprList(args, (FFlag::LuauStoreCSTData2 && options.storeCstData) ? &commaPositions : nullptr); + parseExprList(args, options.storeCstData ? &commaPositions : nullptr); Location end = lexer.current().location; Position argEnd = end.end; expectMatchAndConsume(')', matchParen); - if (FFlag::LuauStoreCSTData2) - { - AstExprCall* node = allocator.alloc(Location(func->location, end), func, copy(args), self, Location(argStart, argEnd)); - if (options.storeCstData) - cstNodeMap[node] = allocator.alloc(matchParen.position, lexer.previousLocation().begin, copy(commaPositions)); - return node; - } - else - { - return allocator.alloc(Location(func->location, end), func, copy(args), self, Location(argStart, argEnd)); - } + AstExprCall* node = allocator.alloc(Location(func->location, end), func, copy(args), self, Location(argStart, argEnd)); + if (options.storeCstData) + cstNodeMap[node] = allocator.alloc(matchParen.position, lexer.previousLocation().begin, copy(commaPositions)); + return node; } else if (lexer.current().type == '{') { @@ -3917,35 +3612,21 @@ AstExpr* Parser::parseFunctionArgs(AstExpr* func, bool self) AstExpr* expr = parseTableConstructor(); Position argEnd = lexer.previousLocation().end; - if (FFlag::LuauStoreCSTData2) - { - AstExprCall* node = - allocator.alloc(Location(func->location, expr->location), func, copy(&expr, 1), self, Location(argStart, argEnd)); - if (options.storeCstData) - cstNodeMap[node] = allocator.alloc(std::nullopt, std::nullopt, AstArray{nullptr, 0}); - return node; - } - else - { - return allocator.alloc(Location(func->location, expr->location), func, copy(&expr, 1), self, Location(argStart, argEnd)); - } + AstExprCall* node = + allocator.alloc(Location(func->location, expr->location), func, copy(&expr, 1), self, Location(argStart, argEnd)); + if (options.storeCstData) + cstNodeMap[node] = allocator.alloc(std::nullopt, std::nullopt, AstArray{nullptr, 0}); + return node; } else if (lexer.current().type == Lexeme::RawString || lexer.current().type == Lexeme::QuotedString) { Location argLocation = lexer.current().location; AstExpr* expr = parseString(); - if (FFlag::LuauStoreCSTData2) - { - AstExprCall* node = allocator.alloc(Location(func->location, expr->location), func, copy(&expr, 1), self, argLocation); - if (options.storeCstData) - cstNodeMap[node] = allocator.alloc(std::nullopt, std::nullopt, AstArray{nullptr, 0}); - return node; - } - else - { - return allocator.alloc(Location(func->location, expr->location), func, copy(&expr, 1), self, argLocation); - } + AstExprCall* node = allocator.alloc(Location(func->location, expr->location), func, copy(&expr, 1), self, argLocation); + if (options.storeCstData) + cstNodeMap[node] = allocator.alloc(std::nullopt, std::nullopt, AstArray{nullptr, 0}); + return node; } else { @@ -3981,7 +3662,6 @@ LUAU_NOINLINE void Parser::reportAmbiguousCallError() std::optional Parser::tableSeparator() { - LUAU_ASSERT(FFlag::LuauStoreCSTData2); if (lexer.current().type == ',') return CstExprTable::Comma; else if (lexer.current().type == ';') @@ -4026,7 +3706,7 @@ AstExpr* Parser::parseTableConstructor() AstExpr* value = parseExpr(); items.push_back({AstExprTable::Item::General, key, value}); - if (FFlag::LuauStoreCSTData2 && options.storeCstData) + if (options.storeCstData) cstItems.push_back({indexerOpenPosition, indexerClosePosition, equalsPosition, tableSeparator(), lexer.current().location.begin}); } else if (lexer.current().type == Lexeme::Name && lexer.lookahead().type == '=') @@ -4047,7 +3727,7 @@ AstExpr* Parser::parseTableConstructor() func->debugname = name.name; items.push_back({AstExprTable::Item::Record, key, value}); - if (FFlag::LuauStoreCSTData2 && options.storeCstData) + if (options.storeCstData) cstItems.push_back({std::nullopt, std::nullopt, equalsPosition, tableSeparator(), lexer.current().location.begin}); } else @@ -4055,7 +3735,7 @@ AstExpr* Parser::parseTableConstructor() AstExpr* expr = parseExpr(); items.push_back({AstExprTable::Item::List, nullptr, expr}); - if (FFlag::LuauStoreCSTData2 && options.storeCstData) + if (options.storeCstData) cstItems.push_back({std::nullopt, std::nullopt, std::nullopt, tableSeparator(), lexer.current().location.begin}); } @@ -4078,17 +3758,10 @@ AstExpr* Parser::parseTableConstructor() if (!expectMatchAndConsume('}', matchBrace)) end = lexer.previousLocation(); - if (FFlag::LuauStoreCSTData2) - { - AstExprTable* node = allocator.alloc(Location(start, end), copy(items)); - if (options.storeCstData) - cstNodeMap[node] = allocator.alloc(copy(cstItems)); - return node; - } - else - { - return allocator.alloc(Location(start, end), copy(items)); - } + AstExprTable* node = allocator.alloc(Location(start, end), copy(items)); + if (options.storeCstData) + cstNodeMap[node] = allocator.alloc(copy(cstItems)); + return node; } AstExpr* Parser::parseIfElseExpr() @@ -4115,8 +3788,7 @@ AstExpr* Parser::parseIfElseExpr() hasElse = true; falseExpr = parseIfElseExpr(); recursionCounter = oldRecursionCount; - if (FFlag::LuauStoreCSTData2) - isElseIf = true; + isElseIf = true; } else { @@ -4126,17 +3798,10 @@ AstExpr* Parser::parseIfElseExpr() Location end = falseExpr->location; - if (FFlag::LuauStoreCSTData2) - { - AstExprIfElse* node = allocator.alloc(Location(start, end), condition, hasThen, trueExpr, hasElse, falseExpr); - if (options.storeCstData) - cstNodeMap[node] = allocator.alloc(thenPosition, elsePosition, isElseIf); - return node; - } - else - { - return allocator.alloc(Location(start, end), condition, hasThen, trueExpr, hasElse, falseExpr); - } + AstExprIfElse* node = allocator.alloc(Location(start, end), condition, hasThen, trueExpr, hasElse, falseExpr); + if (options.storeCstData) + cstNodeMap[node] = allocator.alloc(thenPosition, elsePosition, isElseIf); + return node; } // Name @@ -4203,7 +3868,7 @@ std::pair, AstArray> Parser::pars if (lexer.current().type == '<') { Lexeme begin = lexer.current(); - if (FFlag::LuauStoreCSTData2 && openPosition) + if (openPosition) *openPosition = begin.location.begin; nextLexeme(); @@ -4234,17 +3899,10 @@ std::pair, AstArray> Parser::pars { AstTypePack* typePack = parseTypePack(); - if (FFlag::LuauStoreCSTData2) - { - AstGenericTypePack* node = allocator.alloc(nameLocation, name, typePack); - if (options.storeCstData) - cstNodeMap[node] = allocator.alloc(ellipsisPosition, equalsPosition); - namePacks.push_back(node); - } - else - { - namePacks.push_back(allocator.alloc(nameLocation, name, typePack)); - } + AstGenericTypePack* node = allocator.alloc(nameLocation, name, typePack); + if (options.storeCstData) + cstNodeMap[node] = allocator.alloc(ellipsisPosition, equalsPosition); + namePacks.push_back(node); } else { @@ -4253,17 +3911,10 @@ std::pair, AstArray> Parser::pars if (type) report(type->location, "Expected type pack after '=', got type"); - if (FFlag::LuauStoreCSTData2) - { - AstGenericTypePack* node = allocator.alloc(nameLocation, name, typePack); - if (options.storeCstData) - cstNodeMap[node] = allocator.alloc(ellipsisPosition, equalsPosition); - namePacks.push_back(node); - } - else - { - namePacks.push_back(allocator.alloc(nameLocation, name, typePack)); - } + AstGenericTypePack* node = allocator.alloc(nameLocation, name, typePack); + if (options.storeCstData) + cstNodeMap[node] = allocator.alloc(ellipsisPosition, equalsPosition); + namePacks.push_back(node); } } else @@ -4271,17 +3922,10 @@ std::pair, AstArray> Parser::pars if (seenDefault) report(lexer.current().location, "Expected default type pack after type pack name"); - if (FFlag::LuauStoreCSTData2) - { - AstGenericTypePack* node = allocator.alloc(nameLocation, name, nullptr); - if (options.storeCstData) - cstNodeMap[node] = allocator.alloc(ellipsisPosition, std::nullopt); - namePacks.push_back(node); - } - else - { - namePacks.push_back(allocator.alloc(nameLocation, name, nullptr)); - } + AstGenericTypePack* node = allocator.alloc(nameLocation, name, nullptr); + if (options.storeCstData) + cstNodeMap[node] = allocator.alloc(ellipsisPosition, std::nullopt); + namePacks.push_back(node); } } else @@ -4294,40 +3938,26 @@ std::pair, AstArray> Parser::pars AstType* defaultType = parseType(); - if (FFlag::LuauStoreCSTData2) - { - AstGenericType* node = allocator.alloc(nameLocation, name, defaultType); - if (options.storeCstData) - cstNodeMap[node] = allocator.alloc(equalsPosition); - names.push_back(node); - } - else - { - names.push_back(allocator.alloc(nameLocation, name, defaultType)); - } + AstGenericType* node = allocator.alloc(nameLocation, name, defaultType); + if (options.storeCstData) + cstNodeMap[node] = allocator.alloc(equalsPosition); + names.push_back(node); } else { if (seenDefault) report(lexer.current().location, "Expected default type after type name"); - if (FFlag::LuauStoreCSTData2) - { - AstGenericType* node = allocator.alloc(nameLocation, name, nullptr); - if (options.storeCstData) - cstNodeMap[node] = allocator.alloc(std::nullopt); - names.push_back(node); - } - else - { - names.push_back(allocator.alloc(nameLocation, name, nullptr)); - } + AstGenericType* node = allocator.alloc(nameLocation, name, nullptr); + if (options.storeCstData) + cstNodeMap[node] = allocator.alloc(std::nullopt); + names.push_back(node); } } if (lexer.current().type == ',') { - if (FFlag::LuauStoreCSTData2 && commaPositions) + if (commaPositions) localCommaPositions.push_back(lexer.current().location.begin); nextLexeme(); @@ -4341,12 +3971,12 @@ std::pair, AstArray> Parser::pars break; } - if (FFlag::LuauStoreCSTData2 && closePosition) + if (closePosition) *closePosition = lexer.current().location.begin; expectMatchAndConsume('>', begin); } - if (FFlag::LuauStoreCSTData2 && commaPositions) + if (commaPositions) *commaPositions = copy(localCommaPositions); AstArray generics = copy(names); @@ -4361,7 +3991,7 @@ AstArray Parser::parseTypeParams(Position* openingPosition, TempV if (lexer.current().type == '<') { Lexeme begin = lexer.current(); - if (FFlag::LuauStoreCSTData2 && openingPosition) + if (openingPosition) *openingPosition = begin.location.begin; nextLexeme(); @@ -4442,7 +4072,7 @@ AstArray Parser::parseTypeParams(Position* openingPosition, TempV if (lexer.current().type == ',') { - if (FFlag::LuauStoreCSTData2 && commaPositions) + if (commaPositions) commaPositions->push_back(lexer.current().location.begin); nextLexeme(); } @@ -4450,7 +4080,7 @@ AstArray Parser::parseTypeParams(Position* openingPosition, TempV break; } - if (FFlag::LuauStoreCSTData2 && closingPosition) + if (closingPosition) *closingPosition = lexer.current().location.begin; expectMatchAndConsume('>', begin); } @@ -4466,11 +4096,8 @@ std::optional> Parser::parseCharArray(AstArray* originalStr ); scratchData.assign(lexer.current().data, lexer.current().getLength()); - if (FFlag::LuauStoreCSTData2) - { - if (originalString) - *originalString = copy(scratchData); - } + if (originalString) + *originalString = copy(scratchData); if (lexer.current().type == Lexeme::QuotedString || lexer.current().type == Lexeme::InterpStringSimple) { @@ -4508,31 +4135,21 @@ AstExpr* Parser::parseString() LUAU_ASSERT(false && "Invalid string type"); } - if (FFlag::LuauStoreCSTData2) - { - CstExprConstantString::QuoteStyle fullStyle; - unsigned int blockDepth; - if (options.storeCstData) - std::tie(fullStyle, blockDepth) = extractStringDetails(); + CstExprConstantString::QuoteStyle fullStyle; + unsigned int blockDepth; + if (options.storeCstData) + std::tie(fullStyle, blockDepth) = extractStringDetails(); - AstArray originalString; - if (std::optional> value = parseCharArray(options.storeCstData ? &originalString : nullptr)) - { - AstExprConstantString* node = allocator.alloc(location, *value, style); - if (options.storeCstData) - cstNodeMap[node] = allocator.alloc(originalString, fullStyle, blockDepth); - return node; - } - else - return reportExprError(location, {}, "String literal contains malformed escape sequence"); + AstArray originalString; + if (std::optional> value = parseCharArray(options.storeCstData ? &originalString : nullptr)) + { + AstExprConstantString* node = allocator.alloc(location, *value, style); + if (options.storeCstData) + cstNodeMap[node] = allocator.alloc(originalString, fullStyle, blockDepth); + return node; } else - { - if (std::optional> value = parseCharArray()) - return allocator.alloc(location, *value, style); - else - return reportExprError(location, {}, "String literal contains malformed escape sequence"); - } + return reportExprError(location, {}, "String literal contains malformed escape sequence"); } AstExpr* Parser::parseInterpString() @@ -4557,7 +4174,7 @@ AstExpr* Parser::parseInterpString() scratchData.assign(currentLexeme.data, currentLexeme.getLength()); - if (FFlag::LuauStoreCSTData2 && options.storeCstData) + if (options.storeCstData) { sourceStrings.push_back(copy(scratchData)); stringPositions.push_back(currentLexeme.location.begin); @@ -4627,15 +4244,10 @@ AstExpr* Parser::parseInterpString() AstArray> stringsArray = copy(strings); AstArray expressionsArray = copy(expressions); - if (FFlag::LuauStoreCSTData2) - { - AstExprInterpString* node = allocator.alloc(Location{startLocation, endLocation}, stringsArray, expressionsArray); - if (options.storeCstData) - cstNodeMap[node] = allocator.alloc(copy(sourceStrings), copy(stringPositions)); - return node; - } - else - return allocator.alloc(Location{startLocation, endLocation}, stringsArray, expressionsArray); + AstExprInterpString* node = allocator.alloc(Location{startLocation, endLocation}, stringsArray, expressionsArray); + if (options.storeCstData) + cstNodeMap[node] = allocator.alloc(copy(sourceStrings), copy(stringPositions)); + return node; } AstExpr* Parser::parseNumber() @@ -4644,7 +4256,7 @@ AstExpr* Parser::parseNumber() scratchData.assign(lexer.current().data, lexer.current().getLength()); AstArray sourceData; - if (FFlag::LuauStoreCSTData2 && options.storeCstData) + if (options.storeCstData) sourceData = copy(scratchData); // Remove all internal _ - they don't hold any meaning and this allows parsing code to just pass the string pointer to strtod et al @@ -4660,17 +4272,10 @@ AstExpr* Parser::parseNumber() if (result == ConstantNumberParseResult::Malformed) return reportExprError(start, {}, "Malformed number"); - if (FFlag::LuauStoreCSTData2) - { - AstExprConstantNumber* node = allocator.alloc(start, value, result); - if (options.storeCstData) - cstNodeMap[node] = allocator.alloc(sourceData); - return node; - } - else - { - return allocator.alloc(start, value, result); - } + AstExprConstantNumber* node = allocator.alloc(start, value, result); + if (options.storeCstData) + cstNodeMap[node] = allocator.alloc(sourceData); + return node; } AstLocal* Parser::pushLocal(const Binding& binding) diff --git a/CLI/include/Luau/AnalyzeRequirer.h b/CLI/include/Luau/AnalyzeRequirer.h index a4b395da..0e1d5ee8 100644 --- a/CLI/include/Luau/AnalyzeRequirer.h +++ b/CLI/include/Luau/AnalyzeRequirer.h @@ -2,7 +2,7 @@ #pragma once #include "Luau/RequireNavigator.h" -#include "Luau/RequirerUtils.h" +#include "Luau/VfsNavigator.h" struct FileNavigationContext : Luau::Require::NavigationContext { @@ -13,23 +13,22 @@ struct FileNavigationContext : Luau::Require::NavigationContext std::string getRequirerIdentifier() const override; // Navigation interface - NavigateResult reset(const std::string& requirerChunkname) override; + NavigateResult reset(const std::string& identifier) override; NavigateResult jumpToAlias(const std::string& path) override; NavigateResult toParent() override; NavigateResult toChild(const std::string& component) override; bool isConfigPresent() const override; - std::optional getConfig() const override; + virtual ConfigBehavior getConfigBehavior() const override; + virtual std::optional getAlias(const std::string& alias) const override; + virtual std::optional getConfig() const override; // Custom capabilities bool isModulePresent() const; std::optional getIdentifier() const; - std::string path; - std::string suffix; - std::string requirerPath; - private: - NavigateResult storePathResult(PathResult result); + std::string requirerPath; + VfsNavigator vfs; }; diff --git a/CLI/include/Luau/ReplRequirer.h b/CLI/include/Luau/ReplRequirer.h index 6390e5e9..cc631f78 100644 --- a/CLI/include/Luau/ReplRequirer.h +++ b/CLI/include/Luau/ReplRequirer.h @@ -4,6 +4,7 @@ #include "Luau/Require.h" #include "Luau/Compiler.h" +#include "Luau/VfsNavigator.h" #include "lua.h" @@ -29,7 +30,5 @@ struct ReplRequirer BoolCheck codegenEnabled; Coverage coverageTrack; - std::string absPath; - std::string relPath; - std::string suffix; + VfsNavigator vfs; }; diff --git a/CLI/include/Luau/RequirerUtils.h b/CLI/include/Luau/RequirerUtils.h deleted file mode 100644 index a9cda9f9..00000000 --- a/CLI/include/Luau/RequirerUtils.h +++ /dev/null @@ -1,36 +0,0 @@ -// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details -#pragma once - -#include -#include -#include - -struct PathResult -{ - enum class Status - { - SUCCESS, - AMBIGUOUS, - NOT_FOUND - }; - - Status status; - std::string absPath; - std::string relPath; - std::string suffix; -}; - -PathResult getStdInResult(); - -PathResult getAbsolutePathResult(const std::string& path); - -// If given an absolute path, this will implicitly call getAbsolutePathResult. -// Aliases prevent us from solely operating on relative paths, so we need to -// be able to fall back to operating on absolute paths if needed. -PathResult tryGetRelativePathResult(const std::string& path); - -PathResult getParent(const std::string& absPath, const std::string& relPath); -PathResult getChild(const std::string& absPath, const std::string& relPath, const std::string& name); - -bool isFilePresent(const std::string& path, const std::string& suffix); -std::optional getFileContents(const std::string& path, const std::string& suffix); diff --git a/CLI/include/Luau/VfsNavigator.h b/CLI/include/Luau/VfsNavigator.h new file mode 100644 index 00000000..b781b13d --- /dev/null +++ b/CLI/include/Luau/VfsNavigator.h @@ -0,0 +1,35 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#pragma once + +#include + +enum class NavigationStatus +{ + Success, + Ambiguous, + NotFound +}; + +class VfsNavigator +{ +public: + NavigationStatus resetToStdIn(); + NavigationStatus resetToPath(const std::string& path); + + NavigationStatus toParent(); + NavigationStatus toChild(const std::string& name); + + std::string getFilePath() const; + std::string getAbsoluteFilePath() const; + std::string getLuaurcPath() const; + +private: + NavigationStatus updateRealPaths(); + + std::string realPath; + std::string absoluteRealPath; + std::string absolutePathPrefix; + + std::string modulePath; + std::string absoluteModulePath; +}; diff --git a/CLI/src/AnalyzeRequirer.cpp b/CLI/src/AnalyzeRequirer.cpp index 19d1b431..318a352f 100644 --- a/CLI/src/AnalyzeRequirer.cpp +++ b/CLI/src/AnalyzeRequirer.cpp @@ -1,47 +1,25 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/AnalyzeRequirer.h" +#include "Luau/FileUtils.h" #include "Luau/RequireNavigator.h" -#include "Luau/RequirerUtils.h" +#include "Luau/VfsNavigator.h" #include -#include -Luau::Require::NavigationContext::NavigateResult FileNavigationContext::storePathResult(PathResult result) +static Luau::Require::NavigationContext::NavigateResult convert(NavigationStatus status) { - if (result.status == PathResult::Status::AMBIGUOUS) + if (status == NavigationStatus::Success) + return Luau::Require::NavigationContext::NavigateResult::Success; + else if (status == NavigationStatus::Ambiguous) return Luau::Require::NavigationContext::NavigateResult::Ambiguous; - - if (result.status == PathResult::Status::NOT_FOUND) + else return Luau::Require::NavigationContext::NavigateResult::NotFound; - - path = result.absPath; - suffix = result.suffix; - - return Luau::Require::NavigationContext::NavigateResult::Success; } FileNavigationContext::FileNavigationContext(std::string requirerPath) + : requirerPath(std::move(requirerPath)) { - std::string_view path = requirerPath; - if (path.size() >= 10 && path.substr(path.size() - 10) == "/init.luau") - { - path.remove_suffix(10); - } - else if (path.size() >= 9 && path.substr(path.size() - 9) == "/init.lua") - { - path.remove_suffix(9); - } - else if (path.size() >= 5 && path.substr(path.size() - 5) == ".luau") - { - path.remove_suffix(5); - } - else if (path.size() >= 4 && path.substr(path.size() - 4) == ".lua") - { - path.remove_suffix(4); - } - - this->requirerPath = path; } std::string FileNavigationContext::getRequirerIdentifier() const @@ -49,51 +27,58 @@ std::string FileNavigationContext::getRequirerIdentifier() const return requirerPath; } -Luau::Require::NavigationContext::NavigateResult FileNavigationContext::reset(const std::string& requirerChunkname) +Luau::Require::NavigationContext::NavigateResult FileNavigationContext::reset(const std::string& identifier) { - if (requirerChunkname == "-") - { - return storePathResult(getStdInResult()); - } + if (identifier == "-") + return convert(vfs.resetToStdIn()); - return storePathResult(tryGetRelativePathResult(requirerChunkname)); + return convert(vfs.resetToPath(identifier)); } Luau::Require::NavigationContext::NavigateResult FileNavigationContext::jumpToAlias(const std::string& path) { - Luau::Require::NavigationContext::NavigateResult result = storePathResult(getAbsolutePathResult(path)); - if (result != Luau::Require::NavigationContext::NavigateResult::Success) - return result; + if (!isAbsolutePath(path)) + return Luau::Require::NavigationContext::NavigateResult::NotFound; - return Luau::Require::NavigationContext::NavigateResult::Success; + return convert(vfs.resetToPath(path)); } Luau::Require::NavigationContext::NavigateResult FileNavigationContext::toParent() { - return storePathResult(getParent(path, path)); + return convert(vfs.toParent()); } Luau::Require::NavigationContext::NavigateResult FileNavigationContext::toChild(const std::string& component) { - return storePathResult(getChild(path, path, component)); + return convert(vfs.toChild(component)); } bool FileNavigationContext::isModulePresent() const { - return isFilePresent(path, suffix); + return isFile(vfs.getAbsoluteFilePath()); } std::optional FileNavigationContext::getIdentifier() const { - return path + suffix; + return vfs.getAbsoluteFilePath(); } bool FileNavigationContext::isConfigPresent() const { - return isFilePresent(path, "/.luaurc"); + return isFile(vfs.getLuaurcPath()); +} + +Luau::Require::NavigationContext::ConfigBehavior FileNavigationContext::getConfigBehavior() const +{ + return Luau::Require::NavigationContext::ConfigBehavior::GetConfig; +} + +std::optional FileNavigationContext::getAlias(const std::string& alias) const +{ + return std::nullopt; } std::optional FileNavigationContext::getConfig() const { - return getFileContents(path, "/.luaurc"); + return readFile(vfs.getLuaurcPath()); } diff --git a/CLI/src/Repl.cpp b/CLI/src/Repl.cpp index 770bdec7..0b96410f 100644 --- a/CLI/src/Repl.cpp +++ b/CLI/src/Repl.cpp @@ -581,14 +581,7 @@ static bool runFile(const char* name, lua_State* GL, bool repl) // new thread needs to have the globals sandboxed luaL_sandboxthread(L); - // ignore file extension when storing module's chunkname - std::string chunkname = "@"; - std::string_view nameView = name; - if (size_t dotPos = nameView.find_last_of('.'); dotPos != std::string_view::npos) - { - nameView.remove_suffix(nameView.size() - dotPos); - } - chunkname += nameView; + std::string chunkname = "@" + normalizePath(name); std::string bytecode = Luau::compile(*source, copts()); int status = 0; diff --git a/CLI/src/ReplRequirer.cpp b/CLI/src/ReplRequirer.cpp index 631f91eb..14af6455 100644 --- a/CLI/src/ReplRequirer.cpp +++ b/CLI/src/ReplRequirer.cpp @@ -5,8 +5,8 @@ #include "Luau/CodeGenOptions.h" #include "Luau/FileUtils.h" #include "Luau/Require.h" +#include "Luau/VfsNavigator.h" -#include "Luau/RequirerUtils.h" #include "lua.h" #include "lualib.h" @@ -32,19 +32,14 @@ static luarequire_WriteResult write(std::optional contents, char* b return luarequire_WriteResult::WRITE_SUCCESS; } -static luarequire_NavigateResult storePathResult(ReplRequirer* req, PathResult result) +static luarequire_NavigateResult convert(NavigationStatus status) { - if (result.status == PathResult::Status::AMBIGUOUS) + if (status == NavigationStatus::Success) + return NAVIGATE_SUCCESS; + else if (status == NavigationStatus::Ambiguous) return NAVIGATE_AMBIGUOUS; - - if (result.status == PathResult::Status::NOT_FOUND) + else return NAVIGATE_NOT_FOUND; - - req->absPath = result.absPath; - req->relPath = result.relPath; - req->suffix = result.suffix; - - return NAVIGATE_SUCCESS; } static bool is_require_allowed(lua_State* L, void* ctx, const char* requirer_chunkname) @@ -59,13 +54,9 @@ static luarequire_NavigateResult reset(lua_State* L, void* ctx, const char* requ std::string chunkname = requirer_chunkname; if (chunkname == "=stdin") - { - return storePathResult(req, getStdInResult()); - } + return convert(req->vfs.resetToStdIn()); else if (!chunkname.empty() && chunkname[0] == '@') - { - return storePathResult(req, tryGetRelativePathResult(chunkname.substr(1))); - } + return convert(req->vfs.resetToPath(chunkname.substr(1))); return NAVIGATE_NOT_FOUND; } @@ -74,62 +65,58 @@ static luarequire_NavigateResult jump_to_alias(lua_State* L, void* ctx, const ch { ReplRequirer* req = static_cast(ctx); - luarequire_NavigateResult result = storePathResult(req, getAbsolutePathResult(path)); - if (result != NAVIGATE_SUCCESS) - return result; + if (!isAbsolutePath(path)) + return NAVIGATE_NOT_FOUND; - // Jumping to an absolute path breaks the relative-require chain. The best - // we can do is to store the absolute path itself. - req->relPath = req->absPath; - return NAVIGATE_SUCCESS; + return convert(req->vfs.resetToPath(path)); } static luarequire_NavigateResult to_parent(lua_State* L, void* ctx) { ReplRequirer* req = static_cast(ctx); - return storePathResult(req, getParent(req->absPath, req->relPath)); + return convert(req->vfs.toParent()); } static luarequire_NavigateResult to_child(lua_State* L, void* ctx, const char* name) { ReplRequirer* req = static_cast(ctx); - return storePathResult(req, getChild(req->absPath, req->relPath, name)); + return convert(req->vfs.toChild(name)); } static bool is_module_present(lua_State* L, void* ctx) { ReplRequirer* req = static_cast(ctx); - return isFilePresent(req->absPath, req->suffix); + return isFile(req->vfs.getFilePath()); } static luarequire_WriteResult get_chunkname(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out) { ReplRequirer* req = static_cast(ctx); - return write("@" + req->relPath, buffer, buffer_size, size_out); + return write("@" + req->vfs.getFilePath(), buffer, buffer_size, size_out); } static luarequire_WriteResult get_loadname(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out) { ReplRequirer* req = static_cast(ctx); - return write(req->absPath + req->suffix, buffer, buffer_size, size_out); + return write(req->vfs.getAbsoluteFilePath(), buffer, buffer_size, size_out); } static luarequire_WriteResult get_cache_key(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out) { ReplRequirer* req = static_cast(ctx); - return write(req->absPath + req->suffix, buffer, buffer_size, size_out); + return write(req->vfs.getAbsoluteFilePath(), buffer, buffer_size, size_out); } static bool is_config_present(lua_State* L, void* ctx) { ReplRequirer* req = static_cast(ctx); - return isFilePresent(req->absPath, "/.luaurc"); + return isFile(req->vfs.getLuaurcPath()); } static luarequire_WriteResult get_config(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out) { ReplRequirer* req = static_cast(ctx); - return write(getFileContents(req->absPath, "/.luaurc"), buffer, buffer_size, size_out); + return write(readFile(req->vfs.getLuaurcPath()), buffer, buffer_size, size_out); } static int load(lua_State* L, void* ctx, const char* path, const char* chunkname, const char* loadname) @@ -145,13 +132,26 @@ static int load(lua_State* L, void* ctx, const char* path, const char* chunkname // new thread needs to have the globals sandboxed luaL_sandboxthread(ML); - std::optional contents = readFile(loadname); - if (!contents) + bool hadContents = false; + int status = LUA_OK; + + // Handle C++ RAII objects in a scope which doesn't cause a Luau error + { + std::optional contents = readFile(loadname); + hadContents = contents.has_value(); + + if (contents) + { + // now we can compile & run module on the new thread + std::string bytecode = Luau::compile(*contents, req->copts()); + status = luau_load(ML, chunkname, bytecode.data(), bytecode.size(), 0); + } + } + + if (!hadContents) luaL_error(L, "could not read file '%s'", loadname); - // now we can compile & run module on the new thread - std::string bytecode = Luau::compile(*contents, req->copts()); - if (luau_load(ML, chunkname, bytecode.data(), bytecode.size(), 0) == 0) + if (status == 0) { if (req->codegenEnabled()) { @@ -208,6 +208,7 @@ void requireConfigInit(luarequire_Configuration* config) config->get_chunkname = get_chunkname; config->get_loadname = get_loadname; config->get_cache_key = get_cache_key; + config->get_alias = nullptr; config->get_config = get_config; config->load = load; } diff --git a/CLI/src/RequirerUtils.cpp b/CLI/src/RequirerUtils.cpp deleted file mode 100644 index dcbcdb5f..00000000 --- a/CLI/src/RequirerUtils.cpp +++ /dev/null @@ -1,119 +0,0 @@ -// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details -#include "Luau/RequirerUtils.h" - -#include "Luau/FileUtils.h" - -#include -#include -#include - -static std::pair getSuffixWithAmbiguityCheck(const std::string& path) -{ - bool found = false; - std::string suffix; - - for (const char* potentialSuffix : {".luau", ".lua"}) - { - if (isFile(path + potentialSuffix)) - { - if (found) - return {PathResult::Status::AMBIGUOUS, ""}; - - suffix = potentialSuffix; - found = true; - } - } - if (isDirectory(path)) - { - if (found) - return {PathResult::Status::AMBIGUOUS, ""}; - - for (const char* potentialSuffix : {"/init.luau", "/init.lua"}) - { - if (isFile(path + potentialSuffix)) - { - if (found) - return {PathResult::Status::AMBIGUOUS, ""}; - - suffix = potentialSuffix; - found = true; - } - } - - found = true; - } - - if (!found) - return {PathResult::Status::NOT_FOUND, ""}; - - return {PathResult::Status::SUCCESS, suffix}; -} - -static PathResult addSuffix(PathResult partialResult) -{ - if (partialResult.status != PathResult::Status::SUCCESS) - return partialResult; - - auto [status, suffix] = getSuffixWithAmbiguityCheck(partialResult.absPath); - if (status != PathResult::Status::SUCCESS) - return PathResult{status}; - - partialResult.suffix = std::move(suffix); - return partialResult; -} - -PathResult getStdInResult() -{ - std::optional cwd = getCurrentWorkingDirectory(); - if (!cwd) - return PathResult{PathResult::Status::NOT_FOUND}; - - std::replace(cwd->begin(), cwd->end(), '\\', '/'); - - return PathResult{PathResult::Status::SUCCESS, *cwd + "/stdin", "./stdin", ""}; -} - -PathResult getAbsolutePathResult(const std::string& path) -{ - return addSuffix(PathResult{PathResult::Status::SUCCESS, path}); -} - -PathResult tryGetRelativePathResult(const std::string& path) -{ - if (isAbsolutePath(path)) - return getAbsolutePathResult(path); - - std::optional cwd = getCurrentWorkingDirectory(); - if (!cwd) - return PathResult{PathResult::Status::NOT_FOUND}; - - std::optional resolvedAbsPath = resolvePath(path, *cwd + "/stdin"); - if (!resolvedAbsPath) - return PathResult{PathResult::Status::NOT_FOUND}; - - return addSuffix(PathResult{PathResult::Status::SUCCESS, std::move(*resolvedAbsPath), path}); -} - -PathResult getParent(const std::string& absPath, const std::string& relPath) -{ - std::optional parent = getParentPath(absPath); - if (!parent) - return PathResult{PathResult::Status::NOT_FOUND}; - - return addSuffix(PathResult{PathResult::Status::SUCCESS, *parent, normalizePath(relPath + "/..")}); -} - -PathResult getChild(const std::string& absPath, const std::string& relPath, const std::string& name) -{ - return addSuffix(PathResult{PathResult::Status::SUCCESS, joinPaths(absPath, name), joinPaths(relPath, name)}); -} - -bool isFilePresent(const std::string& path, const std::string& suffix) -{ - return isFile(path + suffix); -} - -std::optional getFileContents(const std::string& path, const std::string& suffix) -{ - return readFile(path + suffix); -} diff --git a/CLI/src/VfsNavigator.cpp b/CLI/src/VfsNavigator.cpp new file mode 100644 index 00000000..5bea9e71 --- /dev/null +++ b/CLI/src/VfsNavigator.cpp @@ -0,0 +1,234 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#include "Luau/VfsNavigator.h" + +#include "Luau/Common.h" +#include "Luau/FileUtils.h" + +#include +#include +#include + +const std::array kSuffixes = {".luau", ".lua"}; +const std::array kInitSuffixes = {"/init.luau", "/init.lua"}; + +struct ResolvedRealPath +{ + NavigationStatus status; + std::string realPath; +}; + +static ResolvedRealPath getRealPath(std::string modulePath) +{ + bool found = false; + std::string suffix; + + size_t lastSlash = modulePath.find_last_of('/'); + LUAU_ASSERT(lastSlash != std::string::npos); + std::string lastComponent = modulePath.substr(lastSlash + 1); + + if (lastComponent != "init") + { + for (std::string_view potentialSuffix : kSuffixes) + { + if (isFile(modulePath + std::string(potentialSuffix))) + { + if (found) + return {NavigationStatus::Ambiguous}; + + suffix = potentialSuffix; + found = true; + } + } + } + if (isDirectory(modulePath)) + { + if (found) + return {NavigationStatus::Ambiguous}; + + for (std::string_view potentialSuffix : kInitSuffixes) + { + if (isFile(modulePath + std::string(potentialSuffix))) + { + if (found) + return {NavigationStatus::Ambiguous}; + + suffix = potentialSuffix; + found = true; + } + } + + found = true; + } + + if (!found) + return {NavigationStatus::NotFound}; + + return {NavigationStatus::Success, modulePath + suffix}; +} + +static bool hasSuffix(std::string_view str, std::string_view suffix) +{ + return str.size() >= suffix.size() && str.substr(str.size() - suffix.size()) == suffix; +} + +static std::string getModulePath(std::string filePath) +{ + for (char& c : filePath) + { + if (c == '\\') + c = '/'; + } + + std::string_view pathView = filePath; + + if (isAbsolutePath(pathView)) + { + size_t firstSlash = pathView.find_first_of('/'); + LUAU_ASSERT(firstSlash != std::string::npos); + pathView.remove_prefix(firstSlash); + } + + for (std::string_view suffix : kInitSuffixes) + { + if (hasSuffix(pathView, suffix)) + { + pathView.remove_suffix(suffix.size()); + return std::string(pathView); + } + } + for (std::string_view suffix : kSuffixes) + { + if (hasSuffix(pathView, suffix)) + { + pathView.remove_suffix(suffix.size()); + return std::string(pathView); + } + } + + return std::string(pathView); +} + +NavigationStatus VfsNavigator::updateRealPaths() +{ + ResolvedRealPath result = getRealPath(modulePath); + ResolvedRealPath absoluteResult = getRealPath(absoluteModulePath); + if (result.status != NavigationStatus::Success || absoluteResult.status != NavigationStatus::Success) + return result.status; + + realPath = isAbsolutePath(result.realPath) ? absolutePathPrefix + result.realPath : result.realPath; + absoluteRealPath = absolutePathPrefix + absoluteResult.realPath; + return NavigationStatus::Success; +} + +NavigationStatus VfsNavigator::resetToStdIn() +{ + std::optional cwd = getCurrentWorkingDirectory(); + if (!cwd) + return NavigationStatus::NotFound; + + realPath = "./stdin"; + absoluteRealPath = normalizePath(*cwd + "/stdin"); + modulePath = "./stdin"; + absoluteModulePath = getModulePath(absoluteRealPath); + + size_t firstSlash = absoluteRealPath.find_first_of('/'); + LUAU_ASSERT(firstSlash != std::string::npos); + absolutePathPrefix = absoluteRealPath.substr(0, firstSlash); + + return NavigationStatus::Success; +} + +NavigationStatus VfsNavigator::resetToPath(const std::string& path) +{ + std::string normalizedPath = normalizePath(path); + + if (isAbsolutePath(normalizedPath)) + { + modulePath = getModulePath(normalizedPath); + absoluteModulePath = modulePath; + + size_t firstSlash = normalizedPath.find_first_of('/'); + LUAU_ASSERT(firstSlash != std::string::npos); + absolutePathPrefix = normalizedPath.substr(0, firstSlash); + } + else + { + std::optional cwd = getCurrentWorkingDirectory(); + if (!cwd) + return NavigationStatus::NotFound; + + modulePath = getModulePath(normalizedPath); + std::string joinedPath = normalizePath(*cwd + "/" + normalizedPath); + absoluteModulePath = getModulePath(joinedPath); + + size_t firstSlash = joinedPath.find_first_of('/'); + LUAU_ASSERT(firstSlash != std::string::npos); + absolutePathPrefix = joinedPath.substr(0, firstSlash); + } + + return updateRealPaths(); +} + +NavigationStatus VfsNavigator::toParent() +{ + if (absoluteModulePath == "/") + return NavigationStatus::NotFound; + + size_t numSlashes = 0; + for (char c : absoluteModulePath) + { + if (c == '/') + numSlashes++; + } + LUAU_ASSERT(numSlashes > 0); + + if (numSlashes == 1) + return NavigationStatus::NotFound; + + modulePath = normalizePath(modulePath + "/.."); + absoluteModulePath = normalizePath(absoluteModulePath + "/.."); + + return updateRealPaths(); +} + +NavigationStatus VfsNavigator::toChild(const std::string& name) +{ + modulePath = normalizePath(modulePath + "/" + name); + absoluteModulePath = normalizePath(absoluteModulePath + "/" + name); + + return updateRealPaths(); +} + +std::string VfsNavigator::getFilePath() const +{ + return realPath; +} + +std::string VfsNavigator::getAbsoluteFilePath() const +{ + return absoluteRealPath; +} + +std::string VfsNavigator::getLuaurcPath() const +{ + std::string_view directory = realPath; + + for (std::string_view suffix : kInitSuffixes) + { + if (hasSuffix(directory, suffix)) + { + directory.remove_suffix(suffix.size()); + return std::string(directory) + "/.luaurc"; + } + } + for (std::string_view suffix : kSuffixes) + { + if (hasSuffix(directory, suffix)) + { + directory.remove_suffix(suffix.size()); + return std::string(directory) + "/.luaurc"; + } + } + + return std::string(directory) + "/.luaurc"; +} diff --git a/CodeGen/include/Luau/IrData.h b/CodeGen/include/Luau/IrData.h index 38519f95..52a394d8 100644 --- a/CodeGen/include/Luau/IrData.h +++ b/CodeGen/include/Luau/IrData.h @@ -832,7 +832,7 @@ enum class IrOpKind : uint32_t // VmExit uses a special value to indicate that pcpos update should be skipped // This is only used during type checking at function entry -constexpr uint32_t kVmExitEntryGuardPc = (1u << 28) - 1; +inline constexpr uint32_t kVmExitEntryGuardPc = (1u << 28) - 1; struct IrOp { @@ -900,7 +900,7 @@ struct IrInst }; // When IrInst operands are used, current instruction index is often required to track lifetime -constexpr uint32_t kInvalidInstIdx = ~0u; +inline constexpr uint32_t kInvalidInstIdx = ~0u; struct IrInstHash { diff --git a/CodeGen/include/Luau/OperandX64.h b/CodeGen/include/Luau/OperandX64.h index 2be72ea8..b0c42639 100644 --- a/CodeGen/include/Luau/OperandX64.h +++ b/CodeGen/include/Luau/OperandX64.h @@ -72,13 +72,13 @@ struct OperandX64 } }; -constexpr OperandX64 addr{SizeX64::none, noreg, 1, noreg, 0}; -constexpr OperandX64 byte{SizeX64::byte, noreg, 1, noreg, 0}; -constexpr OperandX64 word{SizeX64::word, noreg, 1, noreg, 0}; -constexpr OperandX64 dword{SizeX64::dword, noreg, 1, noreg, 0}; -constexpr OperandX64 qword{SizeX64::qword, noreg, 1, noreg, 0}; -constexpr OperandX64 xmmword{SizeX64::xmmword, noreg, 1, noreg, 0}; -constexpr OperandX64 ymmword{SizeX64::ymmword, noreg, 1, noreg, 0}; +inline constexpr OperandX64 addr{SizeX64::none, noreg, 1, noreg, 0}; +inline constexpr OperandX64 byte{SizeX64::byte, noreg, 1, noreg, 0}; +inline constexpr OperandX64 word{SizeX64::word, noreg, 1, noreg, 0}; +inline constexpr OperandX64 dword{SizeX64::dword, noreg, 1, noreg, 0}; +inline constexpr OperandX64 qword{SizeX64::qword, noreg, 1, noreg, 0}; +inline constexpr OperandX64 xmmword{SizeX64::xmmword, noreg, 1, noreg, 0}; +inline constexpr OperandX64 ymmword{SizeX64::ymmword, noreg, 1, noreg, 0}; constexpr OperandX64 operator*(RegisterX64 reg, uint8_t scale) { diff --git a/CodeGen/include/Luau/RegisterA64.h b/CodeGen/include/Luau/RegisterA64.h index f661cdfc..86171821 100644 --- a/CodeGen/include/Luau/RegisterA64.h +++ b/CodeGen/include/Luau/RegisterA64.h @@ -47,174 +47,174 @@ constexpr RegisterA64 castReg(KindA64 kind, RegisterA64 reg) return RegisterA64{kind, reg.index}; } -constexpr RegisterA64 noreg{KindA64::none, 0}; +inline constexpr RegisterA64 noreg{KindA64::none, 0}; -constexpr RegisterA64 w0{KindA64::w, 0}; -constexpr RegisterA64 w1{KindA64::w, 1}; -constexpr RegisterA64 w2{KindA64::w, 2}; -constexpr RegisterA64 w3{KindA64::w, 3}; -constexpr RegisterA64 w4{KindA64::w, 4}; -constexpr RegisterA64 w5{KindA64::w, 5}; -constexpr RegisterA64 w6{KindA64::w, 6}; -constexpr RegisterA64 w7{KindA64::w, 7}; -constexpr RegisterA64 w8{KindA64::w, 8}; -constexpr RegisterA64 w9{KindA64::w, 9}; -constexpr RegisterA64 w10{KindA64::w, 10}; -constexpr RegisterA64 w11{KindA64::w, 11}; -constexpr RegisterA64 w12{KindA64::w, 12}; -constexpr RegisterA64 w13{KindA64::w, 13}; -constexpr RegisterA64 w14{KindA64::w, 14}; -constexpr RegisterA64 w15{KindA64::w, 15}; -constexpr RegisterA64 w16{KindA64::w, 16}; -constexpr RegisterA64 w17{KindA64::w, 17}; -constexpr RegisterA64 w18{KindA64::w, 18}; -constexpr RegisterA64 w19{KindA64::w, 19}; -constexpr RegisterA64 w20{KindA64::w, 20}; -constexpr RegisterA64 w21{KindA64::w, 21}; -constexpr RegisterA64 w22{KindA64::w, 22}; -constexpr RegisterA64 w23{KindA64::w, 23}; -constexpr RegisterA64 w24{KindA64::w, 24}; -constexpr RegisterA64 w25{KindA64::w, 25}; -constexpr RegisterA64 w26{KindA64::w, 26}; -constexpr RegisterA64 w27{KindA64::w, 27}; -constexpr RegisterA64 w28{KindA64::w, 28}; -constexpr RegisterA64 w29{KindA64::w, 29}; -constexpr RegisterA64 w30{KindA64::w, 30}; -constexpr RegisterA64 wzr{KindA64::w, 31}; +inline constexpr RegisterA64 w0{KindA64::w, 0}; +inline constexpr RegisterA64 w1{KindA64::w, 1}; +inline constexpr RegisterA64 w2{KindA64::w, 2}; +inline constexpr RegisterA64 w3{KindA64::w, 3}; +inline constexpr RegisterA64 w4{KindA64::w, 4}; +inline constexpr RegisterA64 w5{KindA64::w, 5}; +inline constexpr RegisterA64 w6{KindA64::w, 6}; +inline constexpr RegisterA64 w7{KindA64::w, 7}; +inline constexpr RegisterA64 w8{KindA64::w, 8}; +inline constexpr RegisterA64 w9{KindA64::w, 9}; +inline constexpr RegisterA64 w10{KindA64::w, 10}; +inline constexpr RegisterA64 w11{KindA64::w, 11}; +inline constexpr RegisterA64 w12{KindA64::w, 12}; +inline constexpr RegisterA64 w13{KindA64::w, 13}; +inline constexpr RegisterA64 w14{KindA64::w, 14}; +inline constexpr RegisterA64 w15{KindA64::w, 15}; +inline constexpr RegisterA64 w16{KindA64::w, 16}; +inline constexpr RegisterA64 w17{KindA64::w, 17}; +inline constexpr RegisterA64 w18{KindA64::w, 18}; +inline constexpr RegisterA64 w19{KindA64::w, 19}; +inline constexpr RegisterA64 w20{KindA64::w, 20}; +inline constexpr RegisterA64 w21{KindA64::w, 21}; +inline constexpr RegisterA64 w22{KindA64::w, 22}; +inline constexpr RegisterA64 w23{KindA64::w, 23}; +inline constexpr RegisterA64 w24{KindA64::w, 24}; +inline constexpr RegisterA64 w25{KindA64::w, 25}; +inline constexpr RegisterA64 w26{KindA64::w, 26}; +inline constexpr RegisterA64 w27{KindA64::w, 27}; +inline constexpr RegisterA64 w28{KindA64::w, 28}; +inline constexpr RegisterA64 w29{KindA64::w, 29}; +inline constexpr RegisterA64 w30{KindA64::w, 30}; +inline constexpr RegisterA64 wzr{KindA64::w, 31}; -constexpr RegisterA64 x0{KindA64::x, 0}; -constexpr RegisterA64 x1{KindA64::x, 1}; -constexpr RegisterA64 x2{KindA64::x, 2}; -constexpr RegisterA64 x3{KindA64::x, 3}; -constexpr RegisterA64 x4{KindA64::x, 4}; -constexpr RegisterA64 x5{KindA64::x, 5}; -constexpr RegisterA64 x6{KindA64::x, 6}; -constexpr RegisterA64 x7{KindA64::x, 7}; -constexpr RegisterA64 x8{KindA64::x, 8}; -constexpr RegisterA64 x9{KindA64::x, 9}; -constexpr RegisterA64 x10{KindA64::x, 10}; -constexpr RegisterA64 x11{KindA64::x, 11}; -constexpr RegisterA64 x12{KindA64::x, 12}; -constexpr RegisterA64 x13{KindA64::x, 13}; -constexpr RegisterA64 x14{KindA64::x, 14}; -constexpr RegisterA64 x15{KindA64::x, 15}; -constexpr RegisterA64 x16{KindA64::x, 16}; -constexpr RegisterA64 x17{KindA64::x, 17}; -constexpr RegisterA64 x18{KindA64::x, 18}; -constexpr RegisterA64 x19{KindA64::x, 19}; -constexpr RegisterA64 x20{KindA64::x, 20}; -constexpr RegisterA64 x21{KindA64::x, 21}; -constexpr RegisterA64 x22{KindA64::x, 22}; -constexpr RegisterA64 x23{KindA64::x, 23}; -constexpr RegisterA64 x24{KindA64::x, 24}; -constexpr RegisterA64 x25{KindA64::x, 25}; -constexpr RegisterA64 x26{KindA64::x, 26}; -constexpr RegisterA64 x27{KindA64::x, 27}; -constexpr RegisterA64 x28{KindA64::x, 28}; -constexpr RegisterA64 x29{KindA64::x, 29}; -constexpr RegisterA64 x30{KindA64::x, 30}; -constexpr RegisterA64 xzr{KindA64::x, 31}; +inline constexpr RegisterA64 x0{KindA64::x, 0}; +inline constexpr RegisterA64 x1{KindA64::x, 1}; +inline constexpr RegisterA64 x2{KindA64::x, 2}; +inline constexpr RegisterA64 x3{KindA64::x, 3}; +inline constexpr RegisterA64 x4{KindA64::x, 4}; +inline constexpr RegisterA64 x5{KindA64::x, 5}; +inline constexpr RegisterA64 x6{KindA64::x, 6}; +inline constexpr RegisterA64 x7{KindA64::x, 7}; +inline constexpr RegisterA64 x8{KindA64::x, 8}; +inline constexpr RegisterA64 x9{KindA64::x, 9}; +inline constexpr RegisterA64 x10{KindA64::x, 10}; +inline constexpr RegisterA64 x11{KindA64::x, 11}; +inline constexpr RegisterA64 x12{KindA64::x, 12}; +inline constexpr RegisterA64 x13{KindA64::x, 13}; +inline constexpr RegisterA64 x14{KindA64::x, 14}; +inline constexpr RegisterA64 x15{KindA64::x, 15}; +inline constexpr RegisterA64 x16{KindA64::x, 16}; +inline constexpr RegisterA64 x17{KindA64::x, 17}; +inline constexpr RegisterA64 x18{KindA64::x, 18}; +inline constexpr RegisterA64 x19{KindA64::x, 19}; +inline constexpr RegisterA64 x20{KindA64::x, 20}; +inline constexpr RegisterA64 x21{KindA64::x, 21}; +inline constexpr RegisterA64 x22{KindA64::x, 22}; +inline constexpr RegisterA64 x23{KindA64::x, 23}; +inline constexpr RegisterA64 x24{KindA64::x, 24}; +inline constexpr RegisterA64 x25{KindA64::x, 25}; +inline constexpr RegisterA64 x26{KindA64::x, 26}; +inline constexpr RegisterA64 x27{KindA64::x, 27}; +inline constexpr RegisterA64 x28{KindA64::x, 28}; +inline constexpr RegisterA64 x29{KindA64::x, 29}; +inline constexpr RegisterA64 x30{KindA64::x, 30}; +inline constexpr RegisterA64 xzr{KindA64::x, 31}; -constexpr RegisterA64 sp{KindA64::none, 31}; +inline constexpr RegisterA64 sp{KindA64::none, 31}; -constexpr RegisterA64 s0{KindA64::s, 0}; -constexpr RegisterA64 s1{KindA64::s, 1}; -constexpr RegisterA64 s2{KindA64::s, 2}; -constexpr RegisterA64 s3{KindA64::s, 3}; -constexpr RegisterA64 s4{KindA64::s, 4}; -constexpr RegisterA64 s5{KindA64::s, 5}; -constexpr RegisterA64 s6{KindA64::s, 6}; -constexpr RegisterA64 s7{KindA64::s, 7}; -constexpr RegisterA64 s8{KindA64::s, 8}; -constexpr RegisterA64 s9{KindA64::s, 9}; -constexpr RegisterA64 s10{KindA64::s, 10}; -constexpr RegisterA64 s11{KindA64::s, 11}; -constexpr RegisterA64 s12{KindA64::s, 12}; -constexpr RegisterA64 s13{KindA64::s, 13}; -constexpr RegisterA64 s14{KindA64::s, 14}; -constexpr RegisterA64 s15{KindA64::s, 15}; -constexpr RegisterA64 s16{KindA64::s, 16}; -constexpr RegisterA64 s17{KindA64::s, 17}; -constexpr RegisterA64 s18{KindA64::s, 18}; -constexpr RegisterA64 s19{KindA64::s, 19}; -constexpr RegisterA64 s20{KindA64::s, 20}; -constexpr RegisterA64 s21{KindA64::s, 21}; -constexpr RegisterA64 s22{KindA64::s, 22}; -constexpr RegisterA64 s23{KindA64::s, 23}; -constexpr RegisterA64 s24{KindA64::s, 24}; -constexpr RegisterA64 s25{KindA64::s, 25}; -constexpr RegisterA64 s26{KindA64::s, 26}; -constexpr RegisterA64 s27{KindA64::s, 27}; -constexpr RegisterA64 s28{KindA64::s, 28}; -constexpr RegisterA64 s29{KindA64::s, 29}; -constexpr RegisterA64 s30{KindA64::s, 30}; -constexpr RegisterA64 s31{KindA64::s, 31}; +inline constexpr RegisterA64 s0{KindA64::s, 0}; +inline constexpr RegisterA64 s1{KindA64::s, 1}; +inline constexpr RegisterA64 s2{KindA64::s, 2}; +inline constexpr RegisterA64 s3{KindA64::s, 3}; +inline constexpr RegisterA64 s4{KindA64::s, 4}; +inline constexpr RegisterA64 s5{KindA64::s, 5}; +inline constexpr RegisterA64 s6{KindA64::s, 6}; +inline constexpr RegisterA64 s7{KindA64::s, 7}; +inline constexpr RegisterA64 s8{KindA64::s, 8}; +inline constexpr RegisterA64 s9{KindA64::s, 9}; +inline constexpr RegisterA64 s10{KindA64::s, 10}; +inline constexpr RegisterA64 s11{KindA64::s, 11}; +inline constexpr RegisterA64 s12{KindA64::s, 12}; +inline constexpr RegisterA64 s13{KindA64::s, 13}; +inline constexpr RegisterA64 s14{KindA64::s, 14}; +inline constexpr RegisterA64 s15{KindA64::s, 15}; +inline constexpr RegisterA64 s16{KindA64::s, 16}; +inline constexpr RegisterA64 s17{KindA64::s, 17}; +inline constexpr RegisterA64 s18{KindA64::s, 18}; +inline constexpr RegisterA64 s19{KindA64::s, 19}; +inline constexpr RegisterA64 s20{KindA64::s, 20}; +inline constexpr RegisterA64 s21{KindA64::s, 21}; +inline constexpr RegisterA64 s22{KindA64::s, 22}; +inline constexpr RegisterA64 s23{KindA64::s, 23}; +inline constexpr RegisterA64 s24{KindA64::s, 24}; +inline constexpr RegisterA64 s25{KindA64::s, 25}; +inline constexpr RegisterA64 s26{KindA64::s, 26}; +inline constexpr RegisterA64 s27{KindA64::s, 27}; +inline constexpr RegisterA64 s28{KindA64::s, 28}; +inline constexpr RegisterA64 s29{KindA64::s, 29}; +inline constexpr RegisterA64 s30{KindA64::s, 30}; +inline constexpr RegisterA64 s31{KindA64::s, 31}; -constexpr RegisterA64 d0{KindA64::d, 0}; -constexpr RegisterA64 d1{KindA64::d, 1}; -constexpr RegisterA64 d2{KindA64::d, 2}; -constexpr RegisterA64 d3{KindA64::d, 3}; -constexpr RegisterA64 d4{KindA64::d, 4}; -constexpr RegisterA64 d5{KindA64::d, 5}; -constexpr RegisterA64 d6{KindA64::d, 6}; -constexpr RegisterA64 d7{KindA64::d, 7}; -constexpr RegisterA64 d8{KindA64::d, 8}; -constexpr RegisterA64 d9{KindA64::d, 9}; -constexpr RegisterA64 d10{KindA64::d, 10}; -constexpr RegisterA64 d11{KindA64::d, 11}; -constexpr RegisterA64 d12{KindA64::d, 12}; -constexpr RegisterA64 d13{KindA64::d, 13}; -constexpr RegisterA64 d14{KindA64::d, 14}; -constexpr RegisterA64 d15{KindA64::d, 15}; -constexpr RegisterA64 d16{KindA64::d, 16}; -constexpr RegisterA64 d17{KindA64::d, 17}; -constexpr RegisterA64 d18{KindA64::d, 18}; -constexpr RegisterA64 d19{KindA64::d, 19}; -constexpr RegisterA64 d20{KindA64::d, 20}; -constexpr RegisterA64 d21{KindA64::d, 21}; -constexpr RegisterA64 d22{KindA64::d, 22}; -constexpr RegisterA64 d23{KindA64::d, 23}; -constexpr RegisterA64 d24{KindA64::d, 24}; -constexpr RegisterA64 d25{KindA64::d, 25}; -constexpr RegisterA64 d26{KindA64::d, 26}; -constexpr RegisterA64 d27{KindA64::d, 27}; -constexpr RegisterA64 d28{KindA64::d, 28}; -constexpr RegisterA64 d29{KindA64::d, 29}; -constexpr RegisterA64 d30{KindA64::d, 30}; -constexpr RegisterA64 d31{KindA64::d, 31}; +inline constexpr RegisterA64 d0{KindA64::d, 0}; +inline constexpr RegisterA64 d1{KindA64::d, 1}; +inline constexpr RegisterA64 d2{KindA64::d, 2}; +inline constexpr RegisterA64 d3{KindA64::d, 3}; +inline constexpr RegisterA64 d4{KindA64::d, 4}; +inline constexpr RegisterA64 d5{KindA64::d, 5}; +inline constexpr RegisterA64 d6{KindA64::d, 6}; +inline constexpr RegisterA64 d7{KindA64::d, 7}; +inline constexpr RegisterA64 d8{KindA64::d, 8}; +inline constexpr RegisterA64 d9{KindA64::d, 9}; +inline constexpr RegisterA64 d10{KindA64::d, 10}; +inline constexpr RegisterA64 d11{KindA64::d, 11}; +inline constexpr RegisterA64 d12{KindA64::d, 12}; +inline constexpr RegisterA64 d13{KindA64::d, 13}; +inline constexpr RegisterA64 d14{KindA64::d, 14}; +inline constexpr RegisterA64 d15{KindA64::d, 15}; +inline constexpr RegisterA64 d16{KindA64::d, 16}; +inline constexpr RegisterA64 d17{KindA64::d, 17}; +inline constexpr RegisterA64 d18{KindA64::d, 18}; +inline constexpr RegisterA64 d19{KindA64::d, 19}; +inline constexpr RegisterA64 d20{KindA64::d, 20}; +inline constexpr RegisterA64 d21{KindA64::d, 21}; +inline constexpr RegisterA64 d22{KindA64::d, 22}; +inline constexpr RegisterA64 d23{KindA64::d, 23}; +inline constexpr RegisterA64 d24{KindA64::d, 24}; +inline constexpr RegisterA64 d25{KindA64::d, 25}; +inline constexpr RegisterA64 d26{KindA64::d, 26}; +inline constexpr RegisterA64 d27{KindA64::d, 27}; +inline constexpr RegisterA64 d28{KindA64::d, 28}; +inline constexpr RegisterA64 d29{KindA64::d, 29}; +inline constexpr RegisterA64 d30{KindA64::d, 30}; +inline constexpr RegisterA64 d31{KindA64::d, 31}; -constexpr RegisterA64 q0{KindA64::q, 0}; -constexpr RegisterA64 q1{KindA64::q, 1}; -constexpr RegisterA64 q2{KindA64::q, 2}; -constexpr RegisterA64 q3{KindA64::q, 3}; -constexpr RegisterA64 q4{KindA64::q, 4}; -constexpr RegisterA64 q5{KindA64::q, 5}; -constexpr RegisterA64 q6{KindA64::q, 6}; -constexpr RegisterA64 q7{KindA64::q, 7}; -constexpr RegisterA64 q8{KindA64::q, 8}; -constexpr RegisterA64 q9{KindA64::q, 9}; -constexpr RegisterA64 q10{KindA64::q, 10}; -constexpr RegisterA64 q11{KindA64::q, 11}; -constexpr RegisterA64 q12{KindA64::q, 12}; -constexpr RegisterA64 q13{KindA64::q, 13}; -constexpr RegisterA64 q14{KindA64::q, 14}; -constexpr RegisterA64 q15{KindA64::q, 15}; -constexpr RegisterA64 q16{KindA64::q, 16}; -constexpr RegisterA64 q17{KindA64::q, 17}; -constexpr RegisterA64 q18{KindA64::q, 18}; -constexpr RegisterA64 q19{KindA64::q, 19}; -constexpr RegisterA64 q20{KindA64::q, 20}; -constexpr RegisterA64 q21{KindA64::q, 21}; -constexpr RegisterA64 q22{KindA64::q, 22}; -constexpr RegisterA64 q23{KindA64::q, 23}; -constexpr RegisterA64 q24{KindA64::q, 24}; -constexpr RegisterA64 q25{KindA64::q, 25}; -constexpr RegisterA64 q26{KindA64::q, 26}; -constexpr RegisterA64 q27{KindA64::q, 27}; -constexpr RegisterA64 q28{KindA64::q, 28}; -constexpr RegisterA64 q29{KindA64::q, 29}; -constexpr RegisterA64 q30{KindA64::q, 30}; -constexpr RegisterA64 q31{KindA64::q, 31}; +inline constexpr RegisterA64 q0{KindA64::q, 0}; +inline constexpr RegisterA64 q1{KindA64::q, 1}; +inline constexpr RegisterA64 q2{KindA64::q, 2}; +inline constexpr RegisterA64 q3{KindA64::q, 3}; +inline constexpr RegisterA64 q4{KindA64::q, 4}; +inline constexpr RegisterA64 q5{KindA64::q, 5}; +inline constexpr RegisterA64 q6{KindA64::q, 6}; +inline constexpr RegisterA64 q7{KindA64::q, 7}; +inline constexpr RegisterA64 q8{KindA64::q, 8}; +inline constexpr RegisterA64 q9{KindA64::q, 9}; +inline constexpr RegisterA64 q10{KindA64::q, 10}; +inline constexpr RegisterA64 q11{KindA64::q, 11}; +inline constexpr RegisterA64 q12{KindA64::q, 12}; +inline constexpr RegisterA64 q13{KindA64::q, 13}; +inline constexpr RegisterA64 q14{KindA64::q, 14}; +inline constexpr RegisterA64 q15{KindA64::q, 15}; +inline constexpr RegisterA64 q16{KindA64::q, 16}; +inline constexpr RegisterA64 q17{KindA64::q, 17}; +inline constexpr RegisterA64 q18{KindA64::q, 18}; +inline constexpr RegisterA64 q19{KindA64::q, 19}; +inline constexpr RegisterA64 q20{KindA64::q, 20}; +inline constexpr RegisterA64 q21{KindA64::q, 21}; +inline constexpr RegisterA64 q22{KindA64::q, 22}; +inline constexpr RegisterA64 q23{KindA64::q, 23}; +inline constexpr RegisterA64 q24{KindA64::q, 24}; +inline constexpr RegisterA64 q25{KindA64::q, 25}; +inline constexpr RegisterA64 q26{KindA64::q, 26}; +inline constexpr RegisterA64 q27{KindA64::q, 27}; +inline constexpr RegisterA64 q28{KindA64::q, 28}; +inline constexpr RegisterA64 q29{KindA64::q, 29}; +inline constexpr RegisterA64 q30{KindA64::q, 30}; +inline constexpr RegisterA64 q31{KindA64::q, 31}; } // namespace A64 } // namespace CodeGen diff --git a/CodeGen/include/Luau/RegisterX64.h b/CodeGen/include/Luau/RegisterX64.h index 74e0ab46..91eb0f20 100644 --- a/CodeGen/include/Luau/RegisterX64.h +++ b/CodeGen/include/Luau/RegisterX64.h @@ -39,93 +39,93 @@ struct RegisterX64 } }; -constexpr RegisterX64 noreg{SizeX64::none, 16}; -constexpr RegisterX64 rip{SizeX64::none, 0}; +inline constexpr RegisterX64 noreg{SizeX64::none, 16}; +inline constexpr RegisterX64 rip{SizeX64::none, 0}; -constexpr RegisterX64 al{SizeX64::byte, 0}; -constexpr RegisterX64 cl{SizeX64::byte, 1}; -constexpr RegisterX64 dl{SizeX64::byte, 2}; -constexpr RegisterX64 bl{SizeX64::byte, 3}; -constexpr RegisterX64 spl{SizeX64::byte, 4}; -constexpr RegisterX64 bpl{SizeX64::byte, 5}; -constexpr RegisterX64 sil{SizeX64::byte, 6}; -constexpr RegisterX64 dil{SizeX64::byte, 7}; -constexpr RegisterX64 r8b{SizeX64::byte, 8}; -constexpr RegisterX64 r9b{SizeX64::byte, 9}; -constexpr RegisterX64 r10b{SizeX64::byte, 10}; -constexpr RegisterX64 r11b{SizeX64::byte, 11}; -constexpr RegisterX64 r12b{SizeX64::byte, 12}; -constexpr RegisterX64 r13b{SizeX64::byte, 13}; -constexpr RegisterX64 r14b{SizeX64::byte, 14}; -constexpr RegisterX64 r15b{SizeX64::byte, 15}; +inline constexpr RegisterX64 al{SizeX64::byte, 0}; +inline constexpr RegisterX64 cl{SizeX64::byte, 1}; +inline constexpr RegisterX64 dl{SizeX64::byte, 2}; +inline constexpr RegisterX64 bl{SizeX64::byte, 3}; +inline constexpr RegisterX64 spl{SizeX64::byte, 4}; +inline constexpr RegisterX64 bpl{SizeX64::byte, 5}; +inline constexpr RegisterX64 sil{SizeX64::byte, 6}; +inline constexpr RegisterX64 dil{SizeX64::byte, 7}; +inline constexpr RegisterX64 r8b{SizeX64::byte, 8}; +inline constexpr RegisterX64 r9b{SizeX64::byte, 9}; +inline constexpr RegisterX64 r10b{SizeX64::byte, 10}; +inline constexpr RegisterX64 r11b{SizeX64::byte, 11}; +inline constexpr RegisterX64 r12b{SizeX64::byte, 12}; +inline constexpr RegisterX64 r13b{SizeX64::byte, 13}; +inline constexpr RegisterX64 r14b{SizeX64::byte, 14}; +inline constexpr RegisterX64 r15b{SizeX64::byte, 15}; -constexpr RegisterX64 eax{SizeX64::dword, 0}; -constexpr RegisterX64 ecx{SizeX64::dword, 1}; -constexpr RegisterX64 edx{SizeX64::dword, 2}; -constexpr RegisterX64 ebx{SizeX64::dword, 3}; -constexpr RegisterX64 esp{SizeX64::dword, 4}; -constexpr RegisterX64 ebp{SizeX64::dword, 5}; -constexpr RegisterX64 esi{SizeX64::dword, 6}; -constexpr RegisterX64 edi{SizeX64::dword, 7}; -constexpr RegisterX64 r8d{SizeX64::dword, 8}; -constexpr RegisterX64 r9d{SizeX64::dword, 9}; -constexpr RegisterX64 r10d{SizeX64::dword, 10}; -constexpr RegisterX64 r11d{SizeX64::dword, 11}; -constexpr RegisterX64 r12d{SizeX64::dword, 12}; -constexpr RegisterX64 r13d{SizeX64::dword, 13}; -constexpr RegisterX64 r14d{SizeX64::dword, 14}; -constexpr RegisterX64 r15d{SizeX64::dword, 15}; +inline constexpr RegisterX64 eax{SizeX64::dword, 0}; +inline constexpr RegisterX64 ecx{SizeX64::dword, 1}; +inline constexpr RegisterX64 edx{SizeX64::dword, 2}; +inline constexpr RegisterX64 ebx{SizeX64::dword, 3}; +inline constexpr RegisterX64 esp{SizeX64::dword, 4}; +inline constexpr RegisterX64 ebp{SizeX64::dword, 5}; +inline constexpr RegisterX64 esi{SizeX64::dword, 6}; +inline constexpr RegisterX64 edi{SizeX64::dword, 7}; +inline constexpr RegisterX64 r8d{SizeX64::dword, 8}; +inline constexpr RegisterX64 r9d{SizeX64::dword, 9}; +inline constexpr RegisterX64 r10d{SizeX64::dword, 10}; +inline constexpr RegisterX64 r11d{SizeX64::dword, 11}; +inline constexpr RegisterX64 r12d{SizeX64::dword, 12}; +inline constexpr RegisterX64 r13d{SizeX64::dword, 13}; +inline constexpr RegisterX64 r14d{SizeX64::dword, 14}; +inline constexpr RegisterX64 r15d{SizeX64::dword, 15}; -constexpr RegisterX64 rax{SizeX64::qword, 0}; -constexpr RegisterX64 rcx{SizeX64::qword, 1}; -constexpr RegisterX64 rdx{SizeX64::qword, 2}; -constexpr RegisterX64 rbx{SizeX64::qword, 3}; -constexpr RegisterX64 rsp{SizeX64::qword, 4}; -constexpr RegisterX64 rbp{SizeX64::qword, 5}; -constexpr RegisterX64 rsi{SizeX64::qword, 6}; -constexpr RegisterX64 rdi{SizeX64::qword, 7}; -constexpr RegisterX64 r8{SizeX64::qword, 8}; -constexpr RegisterX64 r9{SizeX64::qword, 9}; -constexpr RegisterX64 r10{SizeX64::qword, 10}; -constexpr RegisterX64 r11{SizeX64::qword, 11}; -constexpr RegisterX64 r12{SizeX64::qword, 12}; -constexpr RegisterX64 r13{SizeX64::qword, 13}; -constexpr RegisterX64 r14{SizeX64::qword, 14}; -constexpr RegisterX64 r15{SizeX64::qword, 15}; +inline constexpr RegisterX64 rax{SizeX64::qword, 0}; +inline constexpr RegisterX64 rcx{SizeX64::qword, 1}; +inline constexpr RegisterX64 rdx{SizeX64::qword, 2}; +inline constexpr RegisterX64 rbx{SizeX64::qword, 3}; +inline constexpr RegisterX64 rsp{SizeX64::qword, 4}; +inline constexpr RegisterX64 rbp{SizeX64::qword, 5}; +inline constexpr RegisterX64 rsi{SizeX64::qword, 6}; +inline constexpr RegisterX64 rdi{SizeX64::qword, 7}; +inline constexpr RegisterX64 r8{SizeX64::qword, 8}; +inline constexpr RegisterX64 r9{SizeX64::qword, 9}; +inline constexpr RegisterX64 r10{SizeX64::qword, 10}; +inline constexpr RegisterX64 r11{SizeX64::qword, 11}; +inline constexpr RegisterX64 r12{SizeX64::qword, 12}; +inline constexpr RegisterX64 r13{SizeX64::qword, 13}; +inline constexpr RegisterX64 r14{SizeX64::qword, 14}; +inline constexpr RegisterX64 r15{SizeX64::qword, 15}; -constexpr RegisterX64 xmm0{SizeX64::xmmword, 0}; -constexpr RegisterX64 xmm1{SizeX64::xmmword, 1}; -constexpr RegisterX64 xmm2{SizeX64::xmmword, 2}; -constexpr RegisterX64 xmm3{SizeX64::xmmword, 3}; -constexpr RegisterX64 xmm4{SizeX64::xmmword, 4}; -constexpr RegisterX64 xmm5{SizeX64::xmmword, 5}; -constexpr RegisterX64 xmm6{SizeX64::xmmword, 6}; -constexpr RegisterX64 xmm7{SizeX64::xmmword, 7}; -constexpr RegisterX64 xmm8{SizeX64::xmmword, 8}; -constexpr RegisterX64 xmm9{SizeX64::xmmword, 9}; -constexpr RegisterX64 xmm10{SizeX64::xmmword, 10}; -constexpr RegisterX64 xmm11{SizeX64::xmmword, 11}; -constexpr RegisterX64 xmm12{SizeX64::xmmword, 12}; -constexpr RegisterX64 xmm13{SizeX64::xmmword, 13}; -constexpr RegisterX64 xmm14{SizeX64::xmmword, 14}; -constexpr RegisterX64 xmm15{SizeX64::xmmword, 15}; +inline constexpr RegisterX64 xmm0{SizeX64::xmmword, 0}; +inline constexpr RegisterX64 xmm1{SizeX64::xmmword, 1}; +inline constexpr RegisterX64 xmm2{SizeX64::xmmword, 2}; +inline constexpr RegisterX64 xmm3{SizeX64::xmmword, 3}; +inline constexpr RegisterX64 xmm4{SizeX64::xmmword, 4}; +inline constexpr RegisterX64 xmm5{SizeX64::xmmword, 5}; +inline constexpr RegisterX64 xmm6{SizeX64::xmmword, 6}; +inline constexpr RegisterX64 xmm7{SizeX64::xmmword, 7}; +inline constexpr RegisterX64 xmm8{SizeX64::xmmword, 8}; +inline constexpr RegisterX64 xmm9{SizeX64::xmmword, 9}; +inline constexpr RegisterX64 xmm10{SizeX64::xmmword, 10}; +inline constexpr RegisterX64 xmm11{SizeX64::xmmword, 11}; +inline constexpr RegisterX64 xmm12{SizeX64::xmmword, 12}; +inline constexpr RegisterX64 xmm13{SizeX64::xmmword, 13}; +inline constexpr RegisterX64 xmm14{SizeX64::xmmword, 14}; +inline constexpr RegisterX64 xmm15{SizeX64::xmmword, 15}; -constexpr RegisterX64 ymm0{SizeX64::ymmword, 0}; -constexpr RegisterX64 ymm1{SizeX64::ymmword, 1}; -constexpr RegisterX64 ymm2{SizeX64::ymmword, 2}; -constexpr RegisterX64 ymm3{SizeX64::ymmword, 3}; -constexpr RegisterX64 ymm4{SizeX64::ymmword, 4}; -constexpr RegisterX64 ymm5{SizeX64::ymmword, 5}; -constexpr RegisterX64 ymm6{SizeX64::ymmword, 6}; -constexpr RegisterX64 ymm7{SizeX64::ymmword, 7}; -constexpr RegisterX64 ymm8{SizeX64::ymmword, 8}; -constexpr RegisterX64 ymm9{SizeX64::ymmword, 9}; -constexpr RegisterX64 ymm10{SizeX64::ymmword, 10}; -constexpr RegisterX64 ymm11{SizeX64::ymmword, 11}; -constexpr RegisterX64 ymm12{SizeX64::ymmword, 12}; -constexpr RegisterX64 ymm13{SizeX64::ymmword, 13}; -constexpr RegisterX64 ymm14{SizeX64::ymmword, 14}; -constexpr RegisterX64 ymm15{SizeX64::ymmword, 15}; +inline constexpr RegisterX64 ymm0{SizeX64::ymmword, 0}; +inline constexpr RegisterX64 ymm1{SizeX64::ymmword, 1}; +inline constexpr RegisterX64 ymm2{SizeX64::ymmword, 2}; +inline constexpr RegisterX64 ymm3{SizeX64::ymmword, 3}; +inline constexpr RegisterX64 ymm4{SizeX64::ymmword, 4}; +inline constexpr RegisterX64 ymm5{SizeX64::ymmword, 5}; +inline constexpr RegisterX64 ymm6{SizeX64::ymmword, 6}; +inline constexpr RegisterX64 ymm7{SizeX64::ymmword, 7}; +inline constexpr RegisterX64 ymm8{SizeX64::ymmword, 8}; +inline constexpr RegisterX64 ymm9{SizeX64::ymmword, 9}; +inline constexpr RegisterX64 ymm10{SizeX64::ymmword, 10}; +inline constexpr RegisterX64 ymm11{SizeX64::ymmword, 11}; +inline constexpr RegisterX64 ymm12{SizeX64::ymmword, 12}; +inline constexpr RegisterX64 ymm13{SizeX64::ymmword, 13}; +inline constexpr RegisterX64 ymm14{SizeX64::ymmword, 14}; +inline constexpr RegisterX64 ymm15{SizeX64::ymmword, 15}; constexpr RegisterX64 byteReg(RegisterX64 reg) { diff --git a/CodeGen/include/Luau/UnwindBuilder.h b/CodeGen/include/Luau/UnwindBuilder.h index ff3c2aae..a1f8cb27 100644 --- a/CodeGen/include/Luau/UnwindBuilder.h +++ b/CodeGen/include/Luau/UnwindBuilder.h @@ -16,7 +16,7 @@ namespace CodeGen { // This value is used in 'finishFunction' to mark the function that spans to the end of the whole code block -static uint32_t kFullBlockFunction = ~0u; +inline constexpr uint32_t kFullBlockFunction = ~0u; class UnwindBuilder { diff --git a/CodeGen/src/EmitCommonA64.h b/CodeGen/src/EmitCommonA64.h index d61fd2a7..5da992d8 100644 --- a/CodeGen/src/EmitCommonA64.h +++ b/CodeGen/src/EmitCommonA64.h @@ -27,26 +27,26 @@ namespace A64 // Data that is very common to access is placed in non-volatile registers: // 1. Constant registers (only loaded during codegen entry) -constexpr RegisterA64 rState = x19; // lua_State* L -constexpr RegisterA64 rNativeContext = x20; // NativeContext* context -constexpr RegisterA64 rGlobalState = x21; // global_State* L->global +inline constexpr RegisterA64 rState = x19; // lua_State* L +inline constexpr RegisterA64 rNativeContext = x20; // NativeContext* context +inline constexpr RegisterA64 rGlobalState = x21; // global_State* L->global // 2. Frame registers (reloaded when call frame changes; rBase is also reloaded after all calls that may reallocate stack) -constexpr RegisterA64 rConstants = x22; // TValue* k -constexpr RegisterA64 rClosure = x23; // Closure* cl -constexpr RegisterA64 rCode = x24; // Instruction* code -constexpr RegisterA64 rBase = x25; // StkId base +inline constexpr RegisterA64 rConstants = x22; // TValue* k +inline constexpr RegisterA64 rClosure = x23; // Closure* cl +inline constexpr RegisterA64 rCode = x24; // Instruction* code +inline constexpr RegisterA64 rBase = x25; // StkId base // Native code is as stackless as the interpreter, so we can place some data on the stack once and have it accessible at any point // See CodeGenA64.cpp for layout -constexpr unsigned kStashSlots = 9; // stashed non-volatile registers -constexpr unsigned kTempSlots = 1; // 8 bytes of temporary space, such luxury! -constexpr unsigned kSpillSlots = 22; // slots for spilling temporary registers +inline constexpr unsigned kStashSlots = 9; // stashed non-volatile registers +inline constexpr unsigned kTempSlots = 1; // 8 bytes of temporary space, such luxury! +inline constexpr unsigned kSpillSlots = 22; // slots for spilling temporary registers -constexpr unsigned kStackSize = (kStashSlots + kTempSlots + kSpillSlots) * 8; +inline constexpr unsigned kStackSize = (kStashSlots + kTempSlots + kSpillSlots) * 8; -constexpr AddressA64 sSpillArea = mem(sp, (kStashSlots + kTempSlots) * 8); -constexpr AddressA64 sTemporary = mem(sp, kStashSlots * 8); +inline constexpr AddressA64 sSpillArea = mem(sp, (kStashSlots + kTempSlots) * 8); +inline constexpr AddressA64 sTemporary = mem(sp, kStashSlots * 8); inline void emitUpdateBase(AssemblyBuilderA64& build) { diff --git a/CodeGen/src/EmitCommonX64.h b/CodeGen/src/EmitCommonX64.h index f88944e5..efa4bd9e 100644 --- a/CodeGen/src/EmitCommonX64.h +++ b/CodeGen/src/EmitCommonX64.h @@ -33,22 +33,22 @@ namespace X64 struct IrRegAllocX64; -constexpr uint32_t kFunctionAlignment = 32; +inline constexpr uint32_t kFunctionAlignment = 32; // Data that is very common to access is placed in non-volatile registers -constexpr RegisterX64 rState = r15; // lua_State* L -constexpr RegisterX64 rBase = r14; // StkId base -constexpr RegisterX64 rNativeContext = r13; // NativeContext* context -constexpr RegisterX64 rConstants = r12; // TValue* k +inline constexpr RegisterX64 rState = r15; // lua_State* L +inline constexpr RegisterX64 rBase = r14; // StkId base +inline constexpr RegisterX64 rNativeContext = r13; // NativeContext* context +inline constexpr RegisterX64 rConstants = r12; // TValue* k -constexpr unsigned kExtraLocals = 3; // Number of 8 byte slots available for specialized local variables specified below -constexpr unsigned kSpillSlots = 13; // Number of 8 byte slots available for register allocator to spill data into +inline constexpr unsigned kExtraLocals = 3; // Number of 8 byte slots available for specialized local variables specified below +inline constexpr unsigned kSpillSlots = 13; // Number of 8 byte slots available for register allocator to spill data into static_assert((kExtraLocals + kSpillSlots) * 8 % 16 == 0, "locals have to preserve 16 byte alignment"); -constexpr uint8_t kWindowsFirstNonVolXmmReg = 6; +inline constexpr uint8_t kWindowsFirstNonVolXmmReg = 6; -constexpr uint8_t kWindowsUsableXmmRegs = 10; // Some xmm regs are non-volatile, we have to balance how many we want to use/preserve -constexpr uint8_t kSystemVUsableXmmRegs = 16; // All xmm regs are volatile +inline constexpr uint8_t kWindowsUsableXmmRegs = 10; // Some xmm regs are non-volatile, we have to balance how many we want to use/preserve +inline constexpr uint8_t kSystemVUsableXmmRegs = 16; // All xmm regs are volatile inline uint8_t getXmmRegisterCount(ABIX64 abi) { @@ -57,11 +57,11 @@ inline uint8_t getXmmRegisterCount(ABIX64 abi) // Native code is as stackless as the interpreter, so we can place some data on the stack once and have it accessible at any point // Stack is separated into sections for different data. See CodeGenX64.cpp for layout overview -constexpr unsigned kStackAlign = 8; // Bytes we need to align the stack for non-vol xmm register storage -constexpr unsigned kStackLocalStorage = 8 * kExtraLocals; -constexpr unsigned kStackSpillStorage = 8 * kSpillSlots; -constexpr unsigned kStackExtraArgumentStorage = 2 * 8; // Bytes for 5th and 6th function call arguments used under Windows ABI -constexpr unsigned kStackRegHomeStorage = 4 * 8; // Register 'home' locations that can be used by callees under Windows ABI +inline constexpr unsigned kStackAlign = 8; // Bytes we need to align the stack for non-vol xmm register storage +inline constexpr unsigned kStackLocalStorage = 8 * kExtraLocals; +inline constexpr unsigned kStackSpillStorage = 8 * kSpillSlots; +inline constexpr unsigned kStackExtraArgumentStorage = 2 * 8; // Bytes for 5th and 6th function call arguments used under Windows ABI +inline constexpr unsigned kStackRegHomeStorage = 4 * 8; // Register 'home' locations that can be used by callees under Windows ABI inline unsigned getNonVolXmmStorageSize(ABIX64 abi, uint8_t xmmRegCount) { @@ -77,19 +77,19 @@ inline unsigned getNonVolXmmStorageSize(ABIX64 abi, uint8_t xmmRegCount) } // Useful offsets to specific parts -constexpr unsigned kStackOffsetToLocals = kStackExtraArgumentStorage + kStackRegHomeStorage; -constexpr unsigned kStackOffsetToSpillSlots = kStackOffsetToLocals + kStackLocalStorage; +inline constexpr unsigned kStackOffsetToLocals = kStackExtraArgumentStorage + kStackRegHomeStorage; +inline constexpr unsigned kStackOffsetToSpillSlots = kStackOffsetToLocals + kStackLocalStorage; inline unsigned getFullStackSize(ABIX64 abi, uint8_t xmmRegCount) { return kStackOffsetToSpillSlots + kStackSpillStorage + getNonVolXmmStorageSize(abi, xmmRegCount) + kStackAlign; } -constexpr OperandX64 sClosure = qword[rsp + kStackOffsetToLocals + 0]; // Closure* cl -constexpr OperandX64 sCode = qword[rsp + kStackOffsetToLocals + 8]; // Instruction* code -constexpr OperandX64 sTemporarySlot = addr[rsp + kStackOffsetToLocals + 16]; +inline constexpr OperandX64 sClosure = qword[rsp + kStackOffsetToLocals + 0]; // Closure* cl +inline constexpr OperandX64 sCode = qword[rsp + kStackOffsetToLocals + 8]; // Instruction* code +inline constexpr OperandX64 sTemporarySlot = addr[rsp + kStackOffsetToLocals + 16]; -constexpr OperandX64 sSpillArea = addr[rsp + kStackOffsetToSpillSlots]; +inline constexpr OperandX64 sSpillArea = addr[rsp + kStackOffsetToSpillSlots]; inline OperandX64 luauReg(int ri) { diff --git a/Compiler/src/Compiler.cpp b/Compiler/src/Compiler.cpp index f7418f46..447f944a 100644 --- a/Compiler/src/Compiler.cpp +++ b/Compiler/src/Compiler.cpp @@ -29,6 +29,7 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5) LUAU_FASTFLAGVARIABLE(LuauSeparateCompilerTypeInfo) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) +LUAU_FASTFLAGVARIABLE(LuauCompileFixTypeFunctionSkip) namespace Luau { @@ -3933,6 +3934,11 @@ struct Compiler return false; } + + bool visit(AstStatTypeFunction* node) override + { + return !FFlag::LuauCompileFixTypeFunctionSkip; + } }; struct UndefinedLocalVisitor : AstVisitor diff --git a/Config/include/Luau/LinterConfig.h b/Config/include/Luau/LinterConfig.h index e9305009..6926ef2e 100644 --- a/Config/include/Luau/LinterConfig.h +++ b/Config/include/Luau/LinterConfig.h @@ -85,7 +85,7 @@ struct LintOptions }; // clang-format off -static const char* kWarningNames[] = { +inline constexpr const char* kWarningNames[] = { "Unknown", "UnknownGlobal", diff --git a/Makefile b/Makefile index a3ec7c66..58ad0db9 100644 --- a/Makefile +++ b/Makefile @@ -50,15 +50,15 @@ ISOCLINE_SOURCES=extern/isocline/src/isocline.c ISOCLINE_OBJECTS=$(ISOCLINE_SOURCES:%=$(BUILD)/%.o) ISOCLINE_TARGET=$(BUILD)/libisocline.a -TESTS_SOURCES=$(wildcard tests/*.cpp) CLI/src/FileUtils.cpp CLI/src/Flags.cpp CLI/src/Profiler.cpp CLI/src/Coverage.cpp CLI/src/Repl.cpp CLI/src/ReplRequirer.cpp CLI/src/RequirerUtils.cpp +TESTS_SOURCES=$(wildcard tests/*.cpp) CLI/src/FileUtils.cpp CLI/src/Flags.cpp CLI/src/Profiler.cpp CLI/src/Coverage.cpp CLI/src/Repl.cpp CLI/src/ReplRequirer.cpp CLI/src/VfsNavigator.cpp TESTS_OBJECTS=$(TESTS_SOURCES:%=$(BUILD)/%.o) TESTS_TARGET=$(BUILD)/luau-tests -REPL_CLI_SOURCES=CLI/src/FileUtils.cpp CLI/src/Flags.cpp CLI/src/Profiler.cpp CLI/src/Coverage.cpp CLI/src/Repl.cpp CLI/src/ReplEntry.cpp CLI/src/ReplRequirer.cpp CLI/src/RequirerUtils.cpp +REPL_CLI_SOURCES=CLI/src/FileUtils.cpp CLI/src/Flags.cpp CLI/src/Profiler.cpp CLI/src/Coverage.cpp CLI/src/Repl.cpp CLI/src/ReplEntry.cpp CLI/src/ReplRequirer.cpp CLI/src/VfsNavigator.cpp REPL_CLI_OBJECTS=$(REPL_CLI_SOURCES:%=$(BUILD)/%.o) REPL_CLI_TARGET=$(BUILD)/luau -ANALYZE_CLI_SOURCES=CLI/src/FileUtils.cpp CLI/src/Flags.cpp CLI/src/Analyze.cpp CLI/src/AnalyzeRequirer.cpp CLI/src/RequirerUtils.cpp +ANALYZE_CLI_SOURCES=CLI/src/FileUtils.cpp CLI/src/Flags.cpp CLI/src/Analyze.cpp CLI/src/AnalyzeRequirer.cpp CLI/src/VfsNavigator.cpp ANALYZE_CLI_OBJECTS=$(ANALYZE_CLI_SOURCES:%=$(BUILD)/%.o) ANALYZE_CLI_TARGET=$(BUILD)/luau-analyze diff --git a/Require/Navigator/include/Luau/RequireNavigator.h b/Require/Navigator/include/Luau/RequireNavigator.h index 7e5a3dba..af5646a4 100644 --- a/Require/Navigator/include/Luau/RequireNavigator.h +++ b/Require/Navigator/include/Luau/RequireNavigator.h @@ -59,7 +59,18 @@ public: virtual NavigateResult toParent() = 0; virtual NavigateResult toChild(const std::string& component) = 0; + enum class ConfigBehavior + { + GetAlias, + GetConfig + }; + virtual bool isConfigPresent() const = 0; + + // The result of getConfigBehavior determines whether getAlias or getConfig + // is called when isConfigPresent returns true. + virtual ConfigBehavior getConfigBehavior() const = 0; + virtual std::optional getAlias(const std::string& alias) const = 0; virtual std::optional getConfig() const = 0; }; @@ -94,7 +105,8 @@ private: NavigationContext& navigationContext; ErrorHandler& errorHandler; - Luau::Config config; + + std::optional foundAliasValue; }; } // namespace Luau::Require diff --git a/Require/Navigator/src/RequireNavigator.cpp b/Require/Navigator/src/RequireNavigator.cpp index 243bbaf7..e5a713c9 100644 --- a/Require/Navigator/src/RequireNavigator.cpp +++ b/Require/Navigator/src/RequireNavigator.cpp @@ -78,7 +78,7 @@ Error Navigator::navigateImpl(std::string_view path) if (Error error = navigateToAndPopulateConfig(alias)) return error; - if (!config.aliases.contains(alias)) + if (!foundAliasValue) { if (alias != "self") return "@" + alias + " is not a valid alias"; @@ -93,7 +93,7 @@ Error Navigator::navigateImpl(std::string_view path) return std::nullopt; } - if (Error error = navigateToAlias(alias, config.aliases[alias].value)) + if (Error error = navigateToAlias(alias, *foundAliasValue)) return error; if (Error error = navigateThroughPath(path)) return error; @@ -169,25 +169,37 @@ Error Navigator::navigateToAlias(const std::string& alias, const std::string& va Error Navigator::navigateToAndPopulateConfig(const std::string& desiredAlias) { - while (!config.aliases.contains(desiredAlias)) + Luau::Config config; + + while (!foundAliasValue) { if (navigationContext.toParent() != NavigationContext::NavigateResult::Success) break; if (navigationContext.isConfigPresent()) { - std::optional configContents = navigationContext.getConfig(); - if (!configContents) - return "could not get configuration file contents to resolve alias \"" + desiredAlias + "\""; + if (navigationContext.getConfigBehavior() == NavigationContext::ConfigBehavior::GetAlias) + { + foundAliasValue = navigationContext.getAlias(desiredAlias); + } + else + { + std::optional configContents = navigationContext.getConfig(); + if (!configContents) + return "could not get configuration file contents to resolve alias \"" + desiredAlias + "\""; - Luau::ConfigOptions opts; - Luau::ConfigOptions::AliasOptions aliasOpts; - aliasOpts.configLocation = "unused"; - aliasOpts.overwriteAliases = false; - opts.aliasOptions = std::move(aliasOpts); + Luau::ConfigOptions opts; + Luau::ConfigOptions::AliasOptions aliasOpts; + aliasOpts.configLocation = "unused"; + aliasOpts.overwriteAliases = false; + opts.aliasOptions = std::move(aliasOpts); - if (Error error = Luau::parseConfig(*configContents, config, opts)) - return error; + if (Error error = Luau::parseConfig(*configContents, config, opts)) + return error; + + if (config.aliases.contains(desiredAlias)) + foundAliasValue = config.aliases[desiredAlias].value; + } } }; diff --git a/Require/Runtime/include/Luau/Require.h b/Require/Runtime/include/Luau/Require.h index 1686e24e..386caffc 100644 --- a/Require/Runtime/include/Luau/Require.h +++ b/Require/Runtime/include/Luau/Require.h @@ -101,8 +101,18 @@ struct luarequire_Configuration // configuration file is present or NAVIGATE_FAILURE is returned (at root). bool (*is_config_present)(lua_State* L, void* ctx); + // Parses the configuration file in the current context for the given alias + // and returns its value or WRITE_FAILURE if not found. This function is + // only called if is_config_present returns true. If this function pointer + // is set, get_config must not be set. Opting in to this function pointer + // disables parsing configuration files internally and can be used for finer + // control over the configuration file parsing process. + luarequire_WriteResult (*get_alias)(lua_State* L, void* ctx, const char* alias, char* buffer, size_t buffer_size, size_t* size_out); + // Provides the contents of the configuration file in the current context. - // This function is only called if is_config_present returns true. + // This function is only called if is_config_present returns true. If this + // function pointer is set, get_alias must not be set. Opting in to this + // function pointer enables parsing configuration files internally. luarequire_WriteResult (*get_config)(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out); // Executes the module and places the result on the stack. Returns the diff --git a/Require/Runtime/src/Navigation.cpp b/Require/Runtime/src/Navigation.cpp index 251707a9..ba679866 100644 --- a/Require/Runtime/src/Navigation.cpp +++ b/Require/Runtime/src/Navigation.cpp @@ -82,6 +82,18 @@ bool RuntimeNavigationContext::isConfigPresent() const return config->is_config_present(L, ctx); } +NavigationContext::ConfigBehavior RuntimeNavigationContext::getConfigBehavior() const +{ + if (config->get_alias) + return ConfigBehavior::GetAlias; + return ConfigBehavior::GetConfig; +} + +std::optional RuntimeNavigationContext::getAlias(const std::string& alias) const +{ + return getStringFromCWriterWithInput(config->get_alias, alias, initalIdentifierBufferSize); +} + std::optional RuntimeNavigationContext::getConfig() const { return getStringFromCWriter(config->get_config, initalFileBufferSize); @@ -112,17 +124,45 @@ std::optional RuntimeNavigationContext::getStringFromCWriter( return std::nullopt; } +std::optional RuntimeNavigationContext::getStringFromCWriterWithInput( + luarequire_WriteResult (*writer)(lua_State* L, void* ctx, const char* input, char* buffer, size_t buffer_size, size_t* size_out), + std::string input, + size_t initalBufferSize +) const +{ + std::string buffer; + buffer.resize(initalBufferSize); -RuntimeErrorHandler::RuntimeErrorHandler(lua_State* L, std::string requiredPath) - : L(L) - , errorPrefix("error requiring module \"" + std::move(requiredPath) + "\": ") + size_t size; + luarequire_WriteResult result = writer(L, ctx, input.c_str(), buffer.data(), buffer.size(), &size); + if (result == WRITE_BUFFER_TOO_SMALL) + { + buffer.resize(size); + result = writer(L, ctx, input.c_str(), buffer.data(), buffer.size(), &size); + } + + if (result == WRITE_SUCCESS) + { + buffer.resize(size); + return buffer; + } + + return std::nullopt; +} + +RuntimeErrorHandler::RuntimeErrorHandler(std::string requiredPath) + : errorPrefix("error requiring module \"" + std::move(requiredPath) + "\": ") { } void RuntimeErrorHandler::reportError(std::string message) { - std::string fullError = errorPrefix + std::move(message); - luaL_errorL(L, "%s", fullError.c_str()); + errorMessage = errorPrefix + std::move(message); +} + +const std::string& RuntimeErrorHandler::getReportedError() const +{ + return errorMessage; } } // namespace Luau::Require diff --git a/Require/Runtime/src/Navigation.h b/Require/Runtime/src/Navigation.h index bbb69118..36c30846 100644 --- a/Require/Runtime/src/Navigation.h +++ b/Require/Runtime/src/Navigation.h @@ -27,6 +27,8 @@ public: NavigateResult toChild(const std::string& component) override; bool isConfigPresent() const override; + NavigationContext::ConfigBehavior getConfigBehavior() const override; + std::optional getAlias(const std::string& alias) const override; std::optional getConfig() const override; // Custom capabilities @@ -40,6 +42,11 @@ private: luarequire_WriteResult (*writer)(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out), size_t initalBufferSize ) const; + std::optional getStringFromCWriterWithInput( + luarequire_WriteResult (*writer)(lua_State* L, void* ctx, const char* input, char* buffer, size_t buffer_size, size_t* size_out), + std::string input, + size_t initalBufferSize + ) const; luarequire_Configuration* config; lua_State* L; @@ -47,15 +54,18 @@ private: std::string requirerChunkname; }; +// Non-throwing error reporter class RuntimeErrorHandler : public ErrorHandler { public: - RuntimeErrorHandler(lua_State* L, std::string requiredPath); + RuntimeErrorHandler(std::string requiredPath); void reportError(std::string message) override; + const std::string& getReportedError() const; + private: - lua_State* L; std::string errorPrefix; + std::string errorMessage; }; } // namespace Luau::Require diff --git a/Require/Runtime/src/Require.cpp b/Require/Runtime/src/Require.cpp index 7a3b3f01..97535187 100644 --- a/Require/Runtime/src/Require.cpp +++ b/Require/Runtime/src/Require.cpp @@ -29,8 +29,10 @@ static void validateConfig(lua_State* L, const luarequire_Configuration& config) luaL_error(L, "require configuration is missing required function pointer: get_cache_key"); if (!config.is_config_present) luaL_error(L, "require configuration is missing required function pointer: is_config_present"); - if (!config.get_config) - luaL_error(L, "require configuration is missing required function pointer: get_config"); + if (config.get_alias && config.get_config) + luaL_error(L, "require configuration cannot define both get_alias and get_config"); + if (!config.get_alias && !config.get_config) + luaL_error(L, "require configuration is missing required function pointer: either get_alias or get_config (not both)"); if (!config.load) luaL_error(L, "require configuration is missing required function pointer: load"); } diff --git a/Require/Runtime/src/RequireImpl.cpp b/Require/Runtime/src/RequireImpl.cpp index 257f66ba..7106b375 100644 --- a/Require/Runtime/src/RequireImpl.cpp +++ b/Require/Runtime/src/RequireImpl.cpp @@ -21,6 +21,16 @@ static const char* requiredCacheTableKey = "_MODULES"; struct ResolvedRequire { + static ResolvedRequire fromErrorHandler(const RuntimeErrorHandler& errorHandler) + { + return {ResolvedRequire::Status::ErrorReported, "", "", "", errorHandler.getReportedError()}; + } + + static ResolvedRequire fromErrorMessage(const char* message) + { + return {ResolvedRequire::Status::ErrorReported, "", "", "", message}; + } + enum class Status { Cached, @@ -32,6 +42,7 @@ struct ResolvedRequire std::string chunkname; std::string loadname; std::string cacheKey; + std::string error; }; static bool isCached(lua_State* L, const std::string& key) @@ -47,30 +58,24 @@ static bool isCached(lua_State* L, const std::string& key) static ResolvedRequire resolveRequire(luarequire_Configuration* lrc, lua_State* L, void* ctx, const char* requirerChunkname, std::string path) { if (!lrc->is_require_allowed(L, ctx, requirerChunkname)) - luaL_error(L, "require is not supported in this context"); + return ResolvedRequire::fromErrorMessage("require is not supported in this context"); RuntimeNavigationContext navigationContext{lrc, L, ctx, requirerChunkname}; - RuntimeErrorHandler errorHandler{L, path}; // Errors reported directly to lua_State. + RuntimeErrorHandler errorHandler{path}; Navigator navigator(navigationContext, errorHandler); // Updates navigationContext while navigating through the given path. Navigator::Status status = navigator.navigate(std::move(path)); if (status == Navigator::Status::ErrorReported) - return {ResolvedRequire::Status::ErrorReported}; + return ResolvedRequire::fromErrorHandler(errorHandler); if (!navigationContext.isModulePresent()) - { - errorHandler.reportError("no module present at resolved path"); - return ResolvedRequire{ResolvedRequire::Status::ErrorReported}; - } + return ResolvedRequire::fromErrorMessage("no module present at resolved path"); std::optional cacheKey = navigationContext.getCacheKey(); if (!cacheKey) - { - errorHandler.reportError("could not get cache key for module"); - return ResolvedRequire{ResolvedRequire::Status::ErrorReported}; - } + return ResolvedRequire::fromErrorMessage("could not get cache key for module"); if (isCached(L, *cacheKey)) { @@ -84,17 +89,11 @@ static ResolvedRequire resolveRequire(luarequire_Configuration* lrc, lua_State* std::optional chunkname = navigationContext.getChunkname(); if (!chunkname) - { - errorHandler.reportError("could not get chunkname for module"); - return ResolvedRequire{ResolvedRequire::Status::ErrorReported}; - } + return ResolvedRequire::fromErrorMessage("could not get chunkname for module"); std::optional loadname = navigationContext.getLoadname(); if (!loadname) - { - errorHandler.reportError("could not get loadname for module"); - return ResolvedRequire{ResolvedRequire::Status::ErrorReported}; - } + return ResolvedRequire::fromErrorMessage("could not get loadname for module"); return ResolvedRequire{ ResolvedRequire::Status::ModuleRead, @@ -118,12 +117,14 @@ static int checkRegisteredModules(lua_State* L, const char* path) return 1; } +static const int kRequireStackValues = 4; + int lua_requirecont(lua_State* L, int status) { // Number of stack arguments present before this continuation is called. - const int numStackArgs = lua_tointeger(L, 1); - const int numResults = lua_gettop(L) - numStackArgs; - const char* cacheKey = luaL_checkstring(L, numStackArgs); + LUAU_ASSERT(lua_gettop(L) >= kRequireStackValues); + const int numResults = lua_gettop(L) - kRequireStackValues; + const char* cacheKey = luaL_checkstring(L, 2); if (numResults > 1) luaL_error(L, "module must return a single value"); @@ -152,10 +153,8 @@ int lua_requirecont(lua_State* L, int status) int lua_requireinternal(lua_State* L, const char* requirerChunkname) { - int stackTop = lua_gettop(L); - - // If modifying the state of the stack, please update numStackArgs in the - // lua_requirecont continuation function. + // Discard extra arguments, we only use path + lua_settop(L, 1); luarequire_Configuration* lrc = static_cast(lua_touserdata(L, lua_upvalueindex(1))); if (!lrc) @@ -169,22 +168,42 @@ int lua_requireinternal(lua_State* L, const char* requirerChunkname) if (checkRegisteredModules(L, path) == 1) return 1; - ResolvedRequire resolvedRequire = resolveRequire(lrc, L, ctx, requirerChunkname, path); - if (resolvedRequire.status == ResolvedRequire::Status::Cached) - return 1; + // ResolvedRequire will be destroyed and any string will be pinned to Luau stack, so that luaL_error doesn't need destructors + bool resolveError = false; - // (1) path, ..., cacheKey - lua_pushstring(L, resolvedRequire.cacheKey.c_str()); + { + ResolvedRequire resolvedRequire = resolveRequire(lrc, L, ctx, requirerChunkname, path); - // Insert number of arguments before the continuation to check the results. - int numArgsBeforeLoad = stackTop + 2; - lua_pushinteger(L, numArgsBeforeLoad); - lua_insert(L, 1); + if (resolvedRequire.status == ResolvedRequire::Status::Cached) + return 1; - int numResults = lrc->load(L, ctx, path, resolvedRequire.chunkname.c_str(), resolvedRequire.loadname.c_str()); + if (resolvedRequire.status == ResolvedRequire::Status::ErrorReported) + { + lua_pushstring(L, resolvedRequire.error.c_str()); + resolveError = true; + } + else + { + // (1) path, ..., cacheKey, chunkname, loadname + lua_pushstring(L, resolvedRequire.cacheKey.c_str()); + lua_pushstring(L, resolvedRequire.chunkname.c_str()); + lua_pushstring(L, resolvedRequire.loadname.c_str()); + } + } + + if (resolveError) + lua_error(L); // Error already on top of the stack + + int stackValues = lua_gettop(L); + LUAU_ASSERT(stackValues == kRequireStackValues); + + const char* chunkname = lua_tostring(L, -2); + const char* loadname = lua_tostring(L, -1); + + int numResults = lrc->load(L, ctx, path, chunkname, loadname); if (numResults == -1) { - if (lua_gettop(L) != numArgsBeforeLoad) + if (lua_gettop(L) != stackValues) luaL_error(L, "stack cannot be modified when require yields"); return lua_yield(L, 0); diff --git a/Sources.cmake b/Sources.cmake index c8bb7214..33cac80d 100644 --- a/Sources.cmake +++ b/Sources.cmake @@ -398,9 +398,11 @@ target_sources(isocline PRIVATE target_sources(Luau.CLI.lib PRIVATE CLI/include/Luau/FileUtils.h CLI/include/Luau/Flags.h + CLI/include/Luau/VfsNavigator.h CLI/src/FileUtils.cpp CLI/src/Flags.cpp + CLI/src/VfsNavigator.cpp ) if(TARGET Luau.Repl.CLI) @@ -409,14 +411,12 @@ if(TARGET Luau.Repl.CLI) CLI/include/Luau/Coverage.h CLI/include/Luau/Profiler.h CLI/include/Luau/ReplRequirer.h - CLI/include/Luau/RequirerUtils.h CLI/src/Coverage.cpp CLI/src/Profiler.cpp CLI/src/Repl.cpp CLI/src/ReplEntry.cpp CLI/src/ReplRequirer.cpp - CLI/src/RequirerUtils.cpp ) endif() @@ -424,11 +424,9 @@ if(TARGET Luau.Analyze.CLI) # Luau.Analyze.CLI Sources target_sources(Luau.Analyze.CLI PRIVATE CLI/include/Luau/AnalyzeRequirer.h - CLI/include/Luau/RequirerUtils.h CLI/src/Analyze.cpp CLI/src/AnalyzeRequirer.cpp - CLI/src/RequirerUtils.cpp ) endif() @@ -563,13 +561,11 @@ if(TARGET Luau.CLI.Test) CLI/include/Luau/Coverage.h CLI/include/Luau/Profiler.h CLI/include/Luau/ReplRequirer.h - CLI/include/Luau/RequirerUtils.h CLI/src/Coverage.cpp CLI/src/Profiler.cpp CLI/src/Repl.cpp CLI/src/ReplRequirer.cpp - CLI/src/RequirerUtils.cpp tests/RegisterCallbacks.h tests/RegisterCallbacks.cpp diff --git a/VM/include/lua.h b/VM/include/lua.h index 92f84738..9df40dfb 100644 --- a/VM/include/lua.h +++ b/VM/include/lua.h @@ -329,8 +329,6 @@ LUA_API lua_Destructor lua_getuserdatadtor(lua_State* L, int tag); LUA_API void lua_setuserdatametatable(lua_State* L, int tag); LUA_API void lua_getuserdatametatable(lua_State* L, int tag); -LUA_API void lua_setuserdatametatable_DEPRECATED(lua_State* L, int tag, int idx); // Deprecated for incorrect behavior with 'idx != -1' - LUA_API void lua_setlightuserdataname(lua_State* L, int tag, const char* name); LUA_API const char* lua_getlightuserdataname(lua_State* L, int tag); diff --git a/VM/include/luaconf.h b/VM/include/luaconf.h index 05d44f82..931794a9 100644 --- a/VM/include/luaconf.h +++ b/VM/include/luaconf.h @@ -128,21 +128,6 @@ // }================================================================== -/* -@@ LUAI_USER_ALIGNMENT_T is a type that requires maximum alignment. -** CHANGE it if your system requires alignments larger than double. (For -** instance, if your system supports long doubles and they must be -** aligned in 16-byte boundaries, then you should add long double in the -** union.) Probably you do not need to change this. -*/ -#define LUAI_USER_ALIGNMENT_T \ - union \ - { \ - double u; \ - void* s; \ - long l; \ - } - #ifndef LUA_VECTOR_SIZE #define LUA_VECTOR_SIZE 3 // must be 3 or 4 #endif diff --git a/VM/src/lapi.cpp b/VM/src/lapi.cpp index 77e3b013..4f4bb84e 100644 --- a/VM/src/lapi.cpp +++ b/VM/src/lapi.cpp @@ -1479,16 +1479,6 @@ void lua_setuserdatametatable(lua_State* L, int tag) L->top--; } -void lua_setuserdatametatable_DEPRECATED(lua_State* L, int tag, int idx) -{ - api_check(L, unsigned(tag) < LUA_UTAG_LIMIT); - api_check(L, !L->global->udatamt[tag]); // reassignment not supported - StkId o = index2addr(L, idx); - api_check(L, ttistable(o)); - L->global->udatamt[tag] = hvalue(o); - L->top--; -} - void lua_getuserdatametatable(lua_State* L, int tag) { api_check(L, unsigned(tag) < LUA_UTAG_LIMIT); diff --git a/VM/src/lcommon.h b/VM/src/lcommon.h index c9d95c77..ed047a18 100644 --- a/VM/src/lcommon.h +++ b/VM/src/lcommon.h @@ -9,8 +9,6 @@ #include "Luau/Common.h" -typedef LUAI_USER_ALIGNMENT_T L_Umaxalign; - // internal assertions for in-house debugging #define check_exp(c, e) (LUAU_ASSERT(c), (e)) #define api_check(l, e) LUAU_ASSERT(e) diff --git a/VM/src/ldebug.cpp b/VM/src/ldebug.cpp index 44da57c2..a9b26930 100644 --- a/VM/src/ldebug.cpp +++ b/VM/src/ldebug.cpp @@ -12,11 +12,16 @@ #include #include +LUAU_FASTFLAGVARIABLE(LuauCurrentLineBounds) + static const char* getfuncname(Closure* f); static int currentpc(lua_State* L, CallInfo* ci) { - return pcRel(ci->savedpc, ci_func(ci)->l.p); + if (FFlag::LuauCurrentLineBounds) + return pcRel(ci->savedpc, ci_func(ci)->l.p); + else + return pcRel_DEPRECATED(ci->savedpc, ci_func(ci)->l.p); } static int currentline(lua_State* L, CallInfo* ci) diff --git a/VM/src/ldebug.h b/VM/src/ldebug.h index f215e815..3e3006b0 100644 --- a/VM/src/ldebug.h +++ b/VM/src/ldebug.h @@ -4,7 +4,9 @@ #include "lstate.h" -#define pcRel(pc, p) ((pc) ? cast_to(int, (pc) - (p)->code) - 1 : 0) +#define pcRel(pc, p) ((pc) && (pc) != (p)->code ? cast_to(int, (pc) - (p)->code) - 1 : 0) +// TODO: remove with FFlagLuauCurrentLineBounds +#define pcRel_DEPRECATED(pc, p) ((pc) ? cast_to(int, (pc) - (p)->code) - 1 : 0) #define luaG_typeerror(L, o, opname) luaG_typeerrorL(L, o, opname) #define luaG_forerror(L, o, what) luaG_forerrorL(L, o, what) diff --git a/VM/src/lgcdebug.cpp b/VM/src/lgcdebug.cpp index 7a47ab86..0a96c2e8 100644 --- a/VM/src/lgcdebug.cpp +++ b/VM/src/lgcdebug.cpp @@ -14,6 +14,8 @@ #include #include +LUAU_FASTFLAG(LuauCurrentLineBounds) + static void validateobjref(global_State* g, GCObject* f, GCObject* t) { LUAU_ASSERT(!isdead(g, t)); @@ -462,7 +464,7 @@ static void dumpthread(FILE* f, lua_State* th) else if (isLua(ci)) { Proto* p = ci_func(ci)->l.p; - int pc = pcRel(ci->savedpc, p); + int pc = FFlag::LuauCurrentLineBounds ? pcRel(ci->savedpc, p) : pcRel_DEPRECATED(ci->savedpc, p); const LocVar* var = luaF_findlocal(p, int(v - ci->base), pc); if (var && var->varname) diff --git a/VM/src/lmem.cpp b/VM/src/lmem.cpp index 0738840b..c6e14951 100644 --- a/VM/src/lmem.cpp +++ b/VM/src/lmem.cpp @@ -96,7 +96,7 @@ #endif /* - * The sizes of Luau objects aren't crucial for code correctness, but they are crucial for memory efficiency + * The sizes of most Luau objects aren't crucial for code correctness, but they are crucial for memory efficiency * To prevent some of them accidentally growing and us losing memory without realizing it, we're going to lock * the sizes of all critical structures down. */ @@ -120,10 +120,12 @@ static_assert(sizeof(LuaNode) == ABISWITCH(32, 32, 32), "size mismatch for table #endif static_assert(offsetof(TString, data) == ABISWITCH(24, 20, 20), "size mismatch for string header"); -static_assert(offsetof(Udata, data) == ABISWITCH(16, 16, 12), "size mismatch for userdata header"); static_assert(sizeof(LuaTable) == ABISWITCH(48, 32, 32), "size mismatch for table header"); static_assert(offsetof(Buffer, data) == ABISWITCH(8, 8, 8), "size mismatch for buffer header"); +// The userdata is designed to provide 16 byte alignment for 16 byte and larger userdata sizes +static_assert(offsetof(Udata, data) == 16, "data must be at precise offset provide proper alignment"); + const size_t kSizeClasses = LUA_SIZECLASSES; // Controls the number of entries in SizeClassConfig and define the maximum possible paged allocation size @@ -221,14 +223,15 @@ struct lua_Page int freeNext; // next free block offset in this page, in bytes; when negative, freeList is used instead int busyBlocks; // number of blocks allocated out of this page - union - { - char data[1]; - double align1; - void* align2; - }; + // provide additional padding based on current object size to provide 16 byte alignment of data + // later static_assert checks that this requirement is held + char padding[sizeof(void*) == 8 ? 8 : 12]; + + char data[1]; }; +static_assert(offsetof(lua_Page, data) % 16 == 0, "data must be 16 byte aligned to provide properly aligned allocation of userdata objects"); + l_noret luaM_toobig(lua_State* L) { luaG_runerror(L, "memory allocation error: block too big"); diff --git a/VM/src/lobject.h b/VM/src/lobject.h index bd2dca94..19001626 100644 --- a/VM/src/lobject.h +++ b/VM/src/lobject.h @@ -265,11 +265,9 @@ typedef struct Udata struct LuaTable* metatable; - union - { - char data[1]; // userdata is allocated right after the header - L_Umaxalign dummy; // ensures maximum alignment for data - }; + // userdata is allocated right after the header + // while the alignment is only 8 here, for sizes starting at 16 bytes, 16 byte alignment is provided + alignas(8) char data[1]; } Udata; typedef struct LuauBuffer @@ -278,11 +276,7 @@ typedef struct LuauBuffer unsigned int len; - union - { - char data[1]; // buffer is allocated right after the header - L_Umaxalign dummy; // ensures maximum alignment for data - }; + alignas(8) char data[1]; } Buffer; /* diff --git a/VM/src/ltable.cpp b/VM/src/ltable.cpp index ee5ae7ec..9d857cbf 100644 --- a/VM/src/ltable.cpp +++ b/VM/src/ltable.cpp @@ -366,7 +366,7 @@ static TValue* arrayornewkey(lua_State* L, LuaTable* t, const TValue* key) int k; double n = nvalue(key); luai_num2int(k, n); - if (luai_numeq(cast_num(k), n) && cast_to(unsigned int, k - 1) < cast_to(unsigned int, t->sizearray)) + if (luai_numeq(cast_num(k), n) && unsigned(k) - 1 < unsigned(t->sizearray)) return &t->array[k - 1]; } @@ -604,7 +604,7 @@ static TValue* newkey(lua_State* L, LuaTable* t, const TValue* key) const TValue* luaH_getnum(LuaTable* t, int key) { // (1 <= key && key <= t->sizearray) - if (cast_to(unsigned int, key - 1) < cast_to(unsigned int, t->sizearray)) + if (unsigned(key) - 1 < unsigned(t->sizearray)) return &t->array[key - 1]; else if (t->node != dummynode) { @@ -701,7 +701,7 @@ TValue* luaH_newkey(lua_State* L, LuaTable* t, const TValue* key) TValue* luaH_setnum(lua_State* L, LuaTable* t, int key) { // (1 <= key && key <= t->sizearray) - if (cast_to(unsigned int, key - 1) < cast_to(unsigned int, t->sizearray)) + if (unsigned(key) - 1 < unsigned(t->sizearray)) return &t->array[key - 1]; // hash fallback const TValue* p = luaH_getnum(t, key); diff --git a/VM/src/ltablib.cpp b/VM/src/ltablib.cpp index dbe60e4e..c4a471fa 100644 --- a/VM/src/ltablib.cpp +++ b/VM/src/ltablib.cpp @@ -95,10 +95,8 @@ static void moveelements(lua_State* L, int srct, int dstt, int f, int e, int t) int n = e - f + 1; // number of elements to move - if (cast_to(unsigned int, f - 1) < cast_to(unsigned int, src->sizearray) && - cast_to(unsigned int, t - 1) < cast_to(unsigned int, dst->sizearray) && - cast_to(unsigned int, f - 1 + n) <= cast_to(unsigned int, src->sizearray) && - cast_to(unsigned int, t - 1 + n) <= cast_to(unsigned int, dst->sizearray)) + if (unsigned(f) - 1 < unsigned(src->sizearray) && unsigned(t) - 1 < unsigned(dst->sizearray) && + unsigned(f) - 1 + unsigned(n) <= unsigned(src->sizearray) && unsigned(t) - 1 + unsigned(n) <= unsigned(dst->sizearray)) { TValue* srcarray = src->array; TValue* dstarray = dst->array; diff --git a/VM/src/ludata.h b/VM/src/ludata.h index 9b7ba26a..eebe925c 100644 --- a/VM/src/ludata.h +++ b/VM/src/ludata.h @@ -10,7 +10,8 @@ // special tag value is used for newproxy-created user data (all other user data objects are host-exposed) #define UTAG_PROXY (LUA_UTAG_LIMIT + 1) -#define sizeudata(len) (offsetof(Udata, data) + len) +// userdata larger than 16 bytes will be extended to guarantee 16 byte alignment of subsequent blocks +#define sizeudata(len) (offsetof(Udata, data) + (len > 16 ? ((len + 15) & ~15) : len)) LUAI_FUNC Udata* luaU_newudata(lua_State* L, size_t s, int tag); LUAI_FUNC void luaU_freeudata(lua_State* L, Udata* u, struct lua_Page* page); diff --git a/VM/src/lvmexecute.cpp b/VM/src/lvmexecute.cpp index a355af34..ea409127 100644 --- a/VM/src/lvmexecute.cpp +++ b/VM/src/lvmexecute.cpp @@ -16,6 +16,8 @@ #include +LUAU_FASTFLAG(LuauCurrentLineBounds) + // Disable c99-designator to avoid the warning in CGOTO dispatch table #ifdef __clang__ #if __has_warning("-Wc99-designator") @@ -147,26 +149,53 @@ LUAU_NOINLINE void luau_callhook(lua_State* L, lua_Hook hook, void* userdata) L->base = L->ci->base; } - // note: the pc expectations of the hook are matching the general "pc points to next instruction" - // however, for the hook to be able to continue execution from the same point, this is called with savedpc at the *current* instruction - // this needs to be called before luaD_checkstack in case it fails to reallocate stack - if (L->ci->savedpc) - L->ci->savedpc++; + if (FFlag::LuauCurrentLineBounds) + { + Closure* cl = clvalue(L->ci->func); - luaD_checkstack(L, LUA_MINSTACK); // ensure minimum stack size - L->ci->top = L->top + LUA_MINSTACK; - LUAU_ASSERT(L->ci->top <= L->stack_last); + // note: the pc expectations of the hook are matching the general "pc points to next instruction" + // however, for the hook to be able to continue execution from the same point, this is called with savedpc at the *current* instruction + // this needs to be called before luaD_checkstack in case it fails to reallocate stack + const Instruction* oldsavedpc = L->ci->savedpc; - Closure* cl = clvalue(L->ci->func); + if (L->ci->savedpc && L->ci->savedpc != cl->l.p->code + cl->l.p->sizecode) + L->ci->savedpc++; - lua_Debug ar; - ar.currentline = cl->isC ? -1 : luaG_getline(cl->l.p, pcRel(L->ci->savedpc, cl->l.p)); - ar.userdata = userdata; + luaD_checkstack(L, LUA_MINSTACK); // ensure minimum stack size + L->ci->top = L->top + LUA_MINSTACK; + LUAU_ASSERT(L->ci->top <= L->stack_last); - hook(L, &ar); + lua_Debug ar; + ar.currentline = cl->isC ? -1 : luaG_getline(cl->l.p, pcRel(L->ci->savedpc, cl->l.p)); + ar.userdata = userdata; - if (L->ci->savedpc) - L->ci->savedpc--; + hook(L, &ar); + + L->ci->savedpc = oldsavedpc; + } + else + { + // note: the pc expectations of the hook are matching the general "pc points to next instruction" + // however, for the hook to be able to continue execution from the same point, this is called with savedpc at the *current* instruction + // this needs to be called before luaD_checkstack in case it fails to reallocate stack + if (L->ci->savedpc) + L->ci->savedpc++; + + luaD_checkstack(L, LUA_MINSTACK); // ensure minimum stack size + L->ci->top = L->top + LUA_MINSTACK; + LUAU_ASSERT(L->ci->top <= L->stack_last); + + Closure* cl = clvalue(L->ci->func); + + lua_Debug ar; + ar.currentline = cl->isC ? -1 : luaG_getline(cl->l.p, pcRel(L->ci->savedpc, cl->l.p)); + ar.userdata = userdata; + + hook(L, &ar); + + if (L->ci->savedpc) + L->ci->savedpc--; + } L->ci->top = restorestack(L, ci_top); L->top = restorestack(L, top); @@ -646,7 +675,7 @@ reentry: int index = int(indexd); // index has to be an exact integer and in-bounds for the array portion - if (LUAU_LIKELY(unsigned(index - 1) < unsigned(h->sizearray) && !h->metatable && double(index) == indexd)) + if (LUAU_LIKELY(unsigned(index) - 1 < unsigned(h->sizearray) && !h->metatable && double(index) == indexd)) { setobj2s(L, ra, &h->array[unsigned(index - 1)]); VM_NEXT(); @@ -676,7 +705,7 @@ reentry: int index = int(indexd); // index has to be an exact integer and in-bounds for the array portion - if (LUAU_LIKELY(unsigned(index - 1) < unsigned(h->sizearray) && !h->metatable && !h->readonly && double(index) == indexd)) + if (LUAU_LIKELY(unsigned(index) - 1 < unsigned(h->sizearray) && !h->metatable && !h->readonly && double(index) == indexd)) { setobj2t(L, &h->array[unsigned(index - 1)], ra); luaC_barriert(L, h, ra); diff --git a/VM/src/lvmload.cpp b/VM/src/lvmload.cpp index 2a3443eb..10096798 100644 --- a/VM/src/lvmload.cpp +++ b/VM/src/lvmload.cpp @@ -13,7 +13,8 @@ #include -// TODO: RAII deallocation doesn't work for longjmp builds if a memory error happens +LUAU_FASTFLAGVARIABLE(LuauLoadNoOomThrow) + template struct TempBuffer { @@ -21,11 +22,20 @@ struct TempBuffer T* data; size_t count; + TempBuffer() + : L(NULL) + , data(NULL) + , count(0) + { + LUAU_ASSERT(FFlag::LuauLoadNoOomThrow); + } + TempBuffer(lua_State* L, size_t count) : L(L) , data(luaM_newarray(L, count, T, 0)) , count(count) { + LUAU_ASSERT(!FFlag::LuauLoadNoOomThrow); } TempBuffer(const TempBuffer&) = delete; @@ -36,7 +46,16 @@ struct TempBuffer ~TempBuffer() noexcept { - luaM_freearray(L, data, count, T, 0); + if (data) + luaM_freearray(L, data, count, T, 0); + } + + void allocate(lua_State* L, size_t count) + { + LUAU_ASSERT(this->L == nullptr); + this->L = L; + this->data = luaM_newarray(L, count, T, 0); + this->count = count; } T& operator[](size_t index) @@ -242,7 +261,360 @@ static void remapUserdataTypes(char* data, size_t size, uint8_t* userdataRemappi LUAU_ASSERT(offset == size); } -int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size, int env) +static int loadsafe( + lua_State* L, + TempBuffer& strings, + TempBuffer& protos, + const char* chunkname, + const char* data, + size_t size, + int env +) +{ + size_t offset = 0; + + uint8_t version = read(data, size, offset); + + + // 0 means the rest of the bytecode is the error message + if (version == 0) + { + char chunkbuf[LUA_IDSIZE]; + const char* chunkid = luaO_chunkid(chunkbuf, sizeof(chunkbuf), chunkname, strlen(chunkname)); + lua_pushfstring(L, "%s%.*s", chunkid, int(size - offset), data + offset); + return 1; + } + + if (version < LBC_VERSION_MIN || version > LBC_VERSION_MAX) + { + char chunkbuf[LUA_IDSIZE]; + const char* chunkid = luaO_chunkid(chunkbuf, sizeof(chunkbuf), chunkname, strlen(chunkname)); + lua_pushfstring(L, "%s: bytecode version mismatch (expected [%d..%d], got %d)", chunkid, LBC_VERSION_MIN, LBC_VERSION_MAX, version); + return 1; + } + + uint8_t typesversion = 0; + + if (version >= 4) + { + typesversion = read(data, size, offset); + + if (typesversion < LBC_TYPE_VERSION_MIN || typesversion > LBC_TYPE_VERSION_MAX) + { + char chunkbuf[LUA_IDSIZE]; + const char* chunkid = luaO_chunkid(chunkbuf, sizeof(chunkbuf), chunkname, strlen(chunkname)); + lua_pushfstring( + L, "%s: bytecode type version mismatch (expected [%d..%d], got %d)", chunkid, LBC_TYPE_VERSION_MIN, LBC_TYPE_VERSION_MAX, typesversion + ); + return 1; + } + } + + // env is 0 for current environment and a stack index otherwise + LuaTable* envt = (env == 0) ? L->gt : hvalue(luaA_toobject(L, env)); + + TString* source = luaS_new(L, chunkname); + + // string table + unsigned int stringCount = readVarInt(data, size, offset); + strings.allocate(L, stringCount); + + for (unsigned int i = 0; i < stringCount; ++i) + { + unsigned int length = readVarInt(data, size, offset); + + strings[i] = luaS_newlstr(L, data + offset, length); + offset += length; + } + + // userdata type remapping table + // for unknown userdata types, the entry will remap to common 'userdata' type + const uint32_t userdataTypeLimit = LBC_TYPE_TAGGED_USERDATA_END - LBC_TYPE_TAGGED_USERDATA_BASE; + uint8_t userdataRemapping[userdataTypeLimit]; + + if (typesversion == 3) + { + memset(userdataRemapping, LBC_TYPE_USERDATA, userdataTypeLimit); + + uint8_t index = read(data, size, offset); + + while (index != 0) + { + TString* name = readString(strings, data, size, offset); + + if (uint32_t(index - 1) < userdataTypeLimit) + { + if (auto cb = L->global->ecb.gettypemapping) + userdataRemapping[index - 1] = cb(L, getstr(name), name->len); + } + + index = read(data, size, offset); + } + } + + // proto table + unsigned int protoCount = readVarInt(data, size, offset); + protos.allocate(L, protoCount); + + for (unsigned int i = 0; i < protoCount; ++i) + { + Proto* p = luaF_newproto(L); + p->source = source; + p->bytecodeid = int(i); + + p->maxstacksize = read(data, size, offset); + p->numparams = read(data, size, offset); + p->nups = read(data, size, offset); + p->is_vararg = read(data, size, offset); + + if (version >= 4) + { + p->flags = read(data, size, offset); + + if (typesversion == 1) + { + uint32_t typesize = readVarInt(data, size, offset); + + if (typesize) + { + uint8_t* types = (uint8_t*)data + offset; + + LUAU_ASSERT(typesize == unsigned(2 + p->numparams)); + LUAU_ASSERT(types[0] == LBC_TYPE_FUNCTION); + LUAU_ASSERT(types[1] == p->numparams); + + // transform v1 into v2 format + int headersize = typesize > 127 ? 4 : 3; + + p->typeinfo = luaM_newarray(L, headersize + typesize, uint8_t, p->memcat); + p->sizetypeinfo = headersize + typesize; + + if (headersize == 4) + { + p->typeinfo[0] = (typesize & 127) | (1 << 7); + p->typeinfo[1] = typesize >> 7; + p->typeinfo[2] = 0; + p->typeinfo[3] = 0; + } + else + { + p->typeinfo[0] = uint8_t(typesize); + p->typeinfo[1] = 0; + p->typeinfo[2] = 0; + } + + memcpy(p->typeinfo + headersize, types, typesize); + } + + offset += typesize; + } + else if (typesversion == 2 || typesversion == 3) + { + uint32_t typesize = readVarInt(data, size, offset); + + if (typesize) + { + uint8_t* types = (uint8_t*)data + offset; + + p->typeinfo = luaM_newarray(L, typesize, uint8_t, p->memcat); + p->sizetypeinfo = typesize; + memcpy(p->typeinfo, types, typesize); + offset += typesize; + + if (typesversion == 3) + { + remapUserdataTypes((char*)(uint8_t*)p->typeinfo, p->sizetypeinfo, userdataRemapping, userdataTypeLimit); + } + } + } + } + + const int sizecode = readVarInt(data, size, offset); + p->code = luaM_newarray(L, sizecode, Instruction, p->memcat); + p->sizecode = sizecode; + + for (int j = 0; j < p->sizecode; ++j) + p->code[j] = read(data, size, offset); + + p->codeentry = p->code; + + const int sizek = readVarInt(data, size, offset); + p->k = luaM_newarray(L, sizek, TValue, p->memcat); + p->sizek = sizek; + + // Initialize the constants to nil to ensure they have a valid state + // in the event that some operation in the following loop fails with + // an exception. + for (int j = 0; j < p->sizek; ++j) + { + setnilvalue(&p->k[j]); + } + + for (int j = 0; j < p->sizek; ++j) + { + switch (read(data, size, offset)) + { + case LBC_CONSTANT_NIL: + // All constants have already been pre-initialized to nil + break; + + case LBC_CONSTANT_BOOLEAN: + { + uint8_t v = read(data, size, offset); + setbvalue(&p->k[j], v); + break; + } + + case LBC_CONSTANT_NUMBER: + { + double v = read(data, size, offset); + setnvalue(&p->k[j], v); + break; + } + + case LBC_CONSTANT_VECTOR: + { + float x = read(data, size, offset); + float y = read(data, size, offset); + float z = read(data, size, offset); + float w = read(data, size, offset); + (void)w; + setvvalue(&p->k[j], x, y, z, w); + break; + } + + case LBC_CONSTANT_STRING: + { + TString* v = readString(strings, data, size, offset); + setsvalue(L, &p->k[j], v); + break; + } + + case LBC_CONSTANT_IMPORT: + { + uint32_t iid = read(data, size, offset); + resolveImportSafe(L, envt, p->k, iid); + setobj(L, &p->k[j], L->top - 1); + L->top--; + break; + } + + case LBC_CONSTANT_TABLE: + { + int keys = readVarInt(data, size, offset); + LuaTable* h = luaH_new(L, 0, keys); + for (int i = 0; i < keys; ++i) + { + int key = readVarInt(data, size, offset); + TValue* val = luaH_set(L, h, &p->k[key]); + setnvalue(val, 0.0); + } + sethvalue(L, &p->k[j], h); + break; + } + + case LBC_CONSTANT_CLOSURE: + { + uint32_t fid = readVarInt(data, size, offset); + Closure* cl = luaF_newLclosure(L, protos[fid]->nups, envt, protos[fid]); + cl->preload = (cl->nupvalues > 0); + setclvalue(L, &p->k[j], cl); + break; + } + + default: + LUAU_ASSERT(!"Unexpected constant kind"); + } + } + + const int sizep = readVarInt(data, size, offset); + p->p = luaM_newarray(L, sizep, Proto*, p->memcat); + p->sizep = sizep; + + for (int j = 0; j < p->sizep; ++j) + { + uint32_t fid = readVarInt(data, size, offset); + p->p[j] = protos[fid]; + } + + p->linedefined = readVarInt(data, size, offset); + p->debugname = readString(strings, data, size, offset); + + uint8_t lineinfo = read(data, size, offset); + + if (lineinfo) + { + p->linegaplog2 = read(data, size, offset); + + int intervals = ((p->sizecode - 1) >> p->linegaplog2) + 1; + int absoffset = (p->sizecode + 3) & ~3; + + const int sizelineinfo = absoffset + intervals * sizeof(int); + p->lineinfo = luaM_newarray(L, sizelineinfo, uint8_t, p->memcat); + p->sizelineinfo = sizelineinfo; + + p->abslineinfo = (int*)(p->lineinfo + absoffset); + + uint8_t lastoffset = 0; + for (int j = 0; j < p->sizecode; ++j) + { + lastoffset += read(data, size, offset); + p->lineinfo[j] = lastoffset; + } + + int lastline = 0; + for (int j = 0; j < intervals; ++j) + { + lastline += read(data, size, offset); + p->abslineinfo[j] = lastline; + } + } + + uint8_t debuginfo = read(data, size, offset); + + if (debuginfo) + { + const int sizelocvars = readVarInt(data, size, offset); + p->locvars = luaM_newarray(L, sizelocvars, LocVar, p->memcat); + p->sizelocvars = sizelocvars; + + for (int j = 0; j < p->sizelocvars; ++j) + { + p->locvars[j].varname = readString(strings, data, size, offset); + p->locvars[j].startpc = readVarInt(data, size, offset); + p->locvars[j].endpc = readVarInt(data, size, offset); + p->locvars[j].reg = read(data, size, offset); + } + + const int sizeupvalues = readVarInt(data, size, offset); + LUAU_ASSERT(sizeupvalues == p->nups); + + p->upvalues = luaM_newarray(L, sizeupvalues, TString*, p->memcat); + p->sizeupvalues = sizeupvalues; + + for (int j = 0; j < p->sizeupvalues; ++j) + { + p->upvalues[j] = readString(strings, data, size, offset); + } + } + + protos[i] = p; + } + + // "main" proto is pushed to Lua stack + uint32_t mainid = readVarInt(data, size, offset); + Proto* main = protos[mainid]; + + luaC_threadbarrier(L); + + Closure* cl = luaF_newLclosure(L, 0, envt, main); + setclvalue(L, L->top, cl); + incr_top(L); + + return 0; +} + +int luau_load_DEPRECATED(lua_State* L, const char* chunkname, const char* data, size_t size, int env) { size_t offset = 0; @@ -592,3 +964,54 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size return 0; } + +int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size, int env) +{ + if (!FFlag::LuauLoadNoOomThrow) + return luau_load_DEPRECATED(L, chunkname, data, size, env); + + // we will allocate a fair amount of memory so check GC before we do + luaC_checkGC(L); + + // pause GC for the duration of deserialization - some objects we're creating aren't rooted + const ScopedSetGCThreshold pauseGC{L->global, SIZE_MAX}; + + struct LoadContext + { + TempBuffer strings; + TempBuffer protos; + const char* chunkname; + const char* data; + size_t size; + int env; + + int result; + + static void run(lua_State* L, void* ud) + { + LoadContext* ctx = (LoadContext*)ud; + + ctx->result = loadsafe(L, ctx->strings, ctx->protos, ctx->chunkname, ctx->data, ctx->size, ctx->env); + } + } ctx = { + {}, + {}, + chunkname, + data, + size, + env, + }; + + int status = luaD_rawrunprotected(L, &LoadContext::run, &ctx); + + // load can either succeed or get an OOM error, any other errors should be handled internally + LUAU_ASSERT(status == LUA_OK || status == LUA_ERRMEM); + + if (status == LUA_ERRMEM) + { + lua_pushstring(L, LUA_MEMERRMSG); // out-of-memory error message doesn't require an allocation + return 1; + } + + return ctx.result; +} diff --git a/fuzz/CMakeLists.txt b/fuzz/CMakeLists.txt index be40b811..150c3a17 100644 --- a/fuzz/CMakeLists.txt +++ b/fuzz/CMakeLists.txt @@ -85,8 +85,8 @@ set(LUAU_PB_SOURCES ${LUAU_PB_DIR}/luau.pb.cc ${LUAU_PB_DIR}/luau.pb.h) add_custom_command( OUTPUT ${LUAU_PB_SOURCES} COMMAND ${CMAKE_COMMAND} -E make_directory ${LUAU_PB_DIR} - COMMAND $ ${CMAKE_CURRENT_SOURCE_DIR}/luau.proto --proto_path=${CMAKE_CURRENT_SOURCE_DIR} --cpp_out=${LUAU_PB_DIR} - DEPENDS protobuf::protoc ${CMAKE_CURRENT_SOURCE_DIR}/luau.proto + COMMAND ${protobuf_PROTOC} ${CMAKE_CURRENT_SOURCE_DIR}/luau.proto --proto_path=${CMAKE_CURRENT_SOURCE_DIR} --cpp_out=${LUAU_PB_DIR} + DEPENDS ${protobuf_PROTOC} ${CMAKE_CURRENT_SOURCE_DIR}/luau.proto ) add_executable(Luau.Fuzz.Proto) diff --git a/tests/AstJsonEncoder.test.cpp b/tests/AstJsonEncoder.test.cpp index 306481e8..41d35d11 100644 --- a/tests/AstJsonEncoder.test.cpp +++ b/tests/AstJsonEncoder.test.cpp @@ -11,7 +11,6 @@ using namespace Luau; -LUAU_FASTFLAG(LuauFixFunctionWithAttributesStartLocation) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) struct JsonEncoderFixture @@ -460,9 +459,7 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstAttr") AstStat* expr = expectParseStatement("@checked function a(b) return c end"); std::string_view expected = - FFlag::LuauFixFunctionWithAttributesStartLocation - ? R"({"type":"AstStatFunction","location":"0,0 - 0,35","name":{"type":"AstExprGlobal","location":"0,18 - 0,19","global":"a"},"func":{"type":"AstExprFunction","location":"0,0 - 0,35","attributes":[{"type":"AstAttr","location":"0,0 - 0,8","name":"checked"}],"generics":[],"genericPacks":[],"args":[{"luauType":null,"name":"b","type":"AstLocal","location":"0,20 - 0,21"}],"vararg":false,"varargLocation":"0,0 - 0,0","body":{"type":"AstStatBlock","location":"0,22 - 0,32","hasEnd":true,"body":[{"type":"AstStatReturn","location":"0,23 - 0,31","list":[{"type":"AstExprGlobal","location":"0,30 - 0,31","global":"c"}]}]},"functionDepth":1,"debugname":"a"}})" - : R"({"type":"AstStatFunction","location":"0,9 - 0,35","name":{"type":"AstExprGlobal","location":"0,18 - 0,19","global":"a"},"func":{"type":"AstExprFunction","location":"0,9 - 0,35","attributes":[{"type":"AstAttr","location":"0,0 - 0,8","name":"checked"}],"generics":[],"genericPacks":[],"args":[{"luauType":null,"name":"b","type":"AstLocal","location":"0,20 - 0,21"}],"vararg":false,"varargLocation":"0,0 - 0,0","body":{"type":"AstStatBlock","location":"0,22 - 0,32","hasEnd":true,"body":[{"type":"AstStatReturn","location":"0,23 - 0,31","list":[{"type":"AstExprGlobal","location":"0,30 - 0,31","global":"c"}]}]},"functionDepth":1,"debugname":"a"}})"; + R"({"type":"AstStatFunction","location":"0,0 - 0,35","name":{"type":"AstExprGlobal","location":"0,18 - 0,19","global":"a"},"func":{"type":"AstExprFunction","location":"0,0 - 0,35","attributes":[{"type":"AstAttr","location":"0,0 - 0,8","name":"checked"}],"generics":[],"genericPacks":[],"args":[{"luauType":null,"name":"b","type":"AstLocal","location":"0,20 - 0,21"}],"vararg":false,"varargLocation":"0,0 - 0,0","body":{"type":"AstStatBlock","location":"0,22 - 0,32","hasEnd":true,"body":[{"type":"AstStatReturn","location":"0,23 - 0,31","list":[{"type":"AstExprGlobal","location":"0,30 - 0,31","global":"c"}]}]},"functionDepth":1,"debugname":"a"}})"; CHECK(toJson(expr) == expected); } diff --git a/tests/Autocomplete.test.cpp b/tests/Autocomplete.test.cpp index 3d1750e6..4ef72223 100644 --- a/tests/Autocomplete.test.cpp +++ b/tests/Autocomplete.test.cpp @@ -19,10 +19,7 @@ LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2) LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel) LUAU_FASTINT(LuauTypeInferRecursionLimit) - -LUAU_FASTFLAG(LuauAutocompleteUnionCopyPreviousSeen) -LUAU_FASTFLAG(LuauUserTypeFunTypecheck) -LUAU_FASTFLAG(LuauTypeFunResultInAutocomplete) +LUAU_FASTFLAG(LuauNonReentrantGeneralization3) using namespace Luau; @@ -3121,6 +3118,20 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_string_singletons") CHECK_EQ(ac.context, AutocompleteContext::String); } +TEST_CASE_FIXTURE(ACFixture, "string_singleton_as_table_key_iso") +{ + + check(R"( + type Direction = "up" | "down" + local b: {[Direction]: boolean} = {["@2"] = true} + )"); + + auto ac = autocomplete('2'); + + CHECK(ac.entryMap.count("up")); + CHECK(ac.entryMap.count("down")); +} + TEST_CASE_FIXTURE(ACFixture, "string_singleton_as_table_key") { check(R"( @@ -4431,7 +4442,6 @@ local x = 1 + result. TEST_CASE_FIXTURE(ACExternTypeFixture, "ac_dont_overflow_on_recursive_union") { - ScopedFastFlag _{FFlag::LuauAutocompleteUnionCopyPreviousSeen, true}; check(R"( local table1: {ChildClass} = {} local table2 = {} @@ -4443,17 +4453,24 @@ TEST_CASE_FIXTURE(ACExternTypeFixture, "ac_dont_overflow_on_recursive_union") )"); auto ac = autocomplete('1'); - // RIDE-11517: This should *really* be the members of `ChildClass`, but - // would previously stack overflow. - CHECK(ac.entryMap.empty()); + + if (FFlag::LuauSolverV2 && FFlag::LuauNonReentrantGeneralization3) + { + // This `if` statement is because `LuauNonReentrantGeneralization3` + // sets some flags + CHECK(ac.entryMap.count("BaseMethod") > 0); + CHECK(ac.entryMap.count("Method") > 0); + } + else + { + // Otherwise, we don't infer anything for `value`, which is _fine_. + CHECK(ac.entryMap.empty()); + } + } TEST_CASE_FIXTURE(ACBuiltinsFixture, "type_function_has_types_definitions") { - // Needs new global initialization in the Fixture, but can't place the flag inside the base Fixture - if (!FFlag::LuauUserTypeFunTypecheck) - return; - ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; check(R"( @@ -4468,10 +4485,6 @@ end TEST_CASE_FIXTURE(ACBuiltinsFixture, "type_function_private_scope") { - // Needs new global initialization in the Fixture, but can't place the flag inside the base Fixture - if (!FFlag::LuauUserTypeFunTypecheck) - return; - ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; // Global scope polution by the embedder has no effect @@ -4504,7 +4517,6 @@ this@2 TEST_CASE_FIXTURE(ACBuiltinsFixture, "type_function_eval_in_autocomplete") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag luauTypeFunResultInAutocomplete{FFlag::LuauTypeFunResultInAutocomplete, true}; check(R"( type function foo(x) diff --git a/tests/Compiler.test.cpp b/tests/Compiler.test.cpp index 56ba1a4f..d20041ae 100644 --- a/tests/Compiler.test.cpp +++ b/tests/Compiler.test.cpp @@ -23,6 +23,7 @@ LUAU_FASTINT(LuauCompileInlineThresholdMaxBoost) LUAU_FASTINT(LuauCompileLoopUnrollThreshold) LUAU_FASTINT(LuauCompileLoopUnrollThresholdMaxBoost) LUAU_FASTINT(LuauRecursionLimit) +LUAU_FASTFLAG(LuauCompileFixTypeFunctionSkip) using namespace Luau; @@ -2971,6 +2972,33 @@ TEST_CASE("TypeFunction") CHECK_NOTHROW(Luau::compileOrThrow(bcb, "type function a() return types.any end", options, parseOptions)); } +TEST_CASE("NoTypeFunctionsInBytecode") +{ + ScopedFastFlag luauCompileFixTypeFunctionSkip{FFlag::LuauCompileFixTypeFunctionSkip, true}; + + Luau::BytecodeBuilder bcb; + bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code); + Luau::compileOrThrow(bcb, R"( +type function a() return types.any end +function b() return 2 end +return b() +)"); + + CHECK_EQ("\n" + bcb.dumpEverything(), R"( +Function 0 (b): +LOADN R0 2 +RETURN R0 1 + +Function 1 (??): +DUPCLOSURE R0 K0 ['b'] +SETGLOBAL R0 K1 ['b'] +GETGLOBAL R0 K1 ['b'] +CALL R0 0 -1 +RETURN R0 -1 + +)"); +} + TEST_CASE("DebugLineInfo") { Luau::BytecodeBuilder bcb; diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index 7c0ebf2f..597d8a63 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -31,10 +31,15 @@ extern int optimizationLevel; void luaC_fullgc(lua_State* L); void luaC_validate(lua_State* L); +// internal functions, declared in lvm.h - not exposed via lua.h +void luau_callhook(lua_State* L, lua_Hook hook, void* userdata); + LUAU_FASTFLAG(DebugLuauAbortingChecks) LUAU_FASTINT(CodegenHeuristicsInstructionLimit) LUAU_DYNAMIC_FASTFLAG(LuauStringFormatFixC) LUAU_FASTFLAG(LuauYieldableContinuations) +LUAU_FASTFLAG(LuauCurrentLineBounds) +LUAU_FASTFLAG(LuauLoadNoOomThrow) static lua_CompileOptions defaultOptions() { @@ -1424,6 +1429,119 @@ TEST_CASE("Debugger") CHECK(stephits > 100); // note; this will depend on number of instructions which can vary, so we just make sure the callback gets hit often } +TEST_CASE("InterruptInspection") +{ + ScopedFastFlag luauCurrentLineBounds{FFlag::LuauCurrentLineBounds, true}; + + static bool skipbreak = false; + + runConformance( + "basic.luau", + [](lua_State* L) + { + lua_Callbacks* cb = lua_callbacks(L); + + cb->interrupt = [](lua_State* L, int gc) + { + if (gc >= 0) + return; + + if (!lua_isyieldable(L)) + return; + + if (!skipbreak) + lua_break(L); + + skipbreak = !skipbreak; + }; + }, + [](lua_State* L) + { + // Debug info can be retrieved from every location + lua_Debug ar = {}; + CHECK(lua_getinfo(L, 0, "nsl", &ar)); + + // Simulating a hook being called from the original break location + luau_callhook( + L, + [](lua_State* L, lua_Debug* ar) + { + CHECK(lua_getinfo(L, 0, "nsl", ar)); + }, + nullptr + ); + }, + nullptr, + nullptr, + /* skipCodegen */ true + ); +} + +TEST_CASE("InterruptErrorInspection") +{ + ScopedFastFlag luauCurrentLineBounds{FFlag::LuauCurrentLineBounds, true}; + + // for easy access in no-capture lambda + static int target = 0; + static int step = 0; + + std::string source = R"( +function fib(n) + return n < 2 and 1 or fib(n - 1) + fib(n - 2) +end + +fib(5) +)"; + + for (target = 0; target < 20; target++) + { + step = 0; + + StateRef globalState(luaL_newstate(), lua_close); + lua_State* L = globalState.get(); + + luaL_openlibs(L); + luaL_sandbox(L); + luaL_sandboxthread(L); + + size_t bytecodeSize = 0; + char* bytecode = luau_compile(source.data(), source.size(), nullptr, &bytecodeSize); + int result = luau_load(L, "=InterruptErrorInspection", bytecode, bytecodeSize, 0); + free(bytecode); + + REQUIRE(result == LUA_OK); + + lua_Callbacks* cb = lua_callbacks(L); + + cb->interrupt = [](lua_State* L, int gc) + { + if (gc >= 0) + return; + + if (step == target) + luaL_error(L, "test"); + + step++; + }; + + lua_resume(L, nullptr, 0); + + // Debug info can be retrieved from every location + lua_Debug ar = {}; + CHECK(lua_getinfo(L, 0, "nsl", &ar)); + + // Simulating a hook being called from the original break location + luau_callhook( + L, + [](lua_State* L, lua_Debug* ar) + { + CHECK(lua_getinfo(L, 0, "nsl", ar)); + }, + nullptr + ); + } +} + TEST_CASE("NDebugGetUpValue") { lua_CompileOptions copts = defaultOptions(); @@ -2495,6 +2613,46 @@ TEST_CASE("UserdataApi") CHECK(dtorhits == 42); } +// provide alignment of 16 for userdata objects with size of 16 and up as long as the Luau allocation functions supports it +TEST_CASE("UserdataAlignment") +{ + const auto testAllocate = [](void* ud, void* ptr, size_t osize, size_t nsize) -> void* + { + if (nsize == 0) + { + ::operator delete(ptr, std::align_val_t(16)); + return nullptr; + } + else if (osize == 0) + { + return ::operator new(nsize, std::align_val_t(16)); + } + + // resize is unreachable in this test and is omitted + return nullptr; + }; + + StateRef globalState(lua_newstate(testAllocate, nullptr), lua_close); + lua_State* L = globalState.get(); + + for (int size = 16; size <= 4096; size += 4) + { + for (int i = 0; i < 10; i++) + { + void* data = lua_newuserdata(L, size); + LUAU_ASSERT(uintptr_t(data) % 16 == 0); + lua_pop(L, 1); + } + + for (int i = 0; i < 10; i++) + { + void* data = lua_newuserdatadtor(L, size, [](void*) {}); + LUAU_ASSERT(uintptr_t(data) % 16 == 0); + lua_pop(L, 1); + } + } +} + TEST_CASE("LightuserdataApi") { StateRef globalState(luaL_newstate(), lua_close); @@ -3016,6 +3174,8 @@ TEST_CASE("HugeFunction") TEST_CASE("HugeFunctionLoadFailure") { + ScopedFastFlag luauLoadNoOomThrow{FFlag::LuauLoadNoOomThrow, true}; + // This test case verifies that if an out-of-memory error occurs inside of // luau_load, we are not left with any GC objects in inconsistent states // that would cause issues during garbage collection. @@ -3066,15 +3226,11 @@ TEST_CASE("HugeFunctionLoadFailure") luaL_sandbox(L); luaL_sandboxthread(L); - try - { - luau_load(L, "=HugeFunction", bytecode, bytecodeSize, 0); - REQUIRE(false); // The luau_load should fail with an exception - } - catch (const std::exception& ex) - { - REQUIRE(strcmp(ex.what(), "lua_exception: not enough memory") == 0); - } + int status = luau_load(L, "=HugeFunction", bytecode, bytecodeSize, 0); + REQUIRE(status == 1); + + const char* error = lua_tostring(L, -1); + CHECK(strcmp(error, "not enough memory") == 0); luaC_fullgc(L); } @@ -3219,7 +3375,7 @@ TEST_CASE("NativeAttribute") local function subHelper(z) return (x+y-z) end - return subHelper + return subHelper end)R"; StateRef globalState(luaL_newstate(), lua_close); diff --git a/tests/DataFlowGraph.test.cpp b/tests/DataFlowGraph.test.cpp index cbdfc6df..30f36cec 100644 --- a/tests/DataFlowGraph.test.cpp +++ b/tests/DataFlowGraph.test.cpp @@ -14,6 +14,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType) +LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops) struct DataFlowGraphFixture { @@ -46,6 +47,17 @@ struct DataFlowGraphFixture REQUIRE(node); return graph->getDef(node); } + + void checkOperands(const Phi* phi, std::vector operands) + { + Set operandSet{nullptr}; + for (auto o : operands) + operandSet.insert(o.get()); + CHECK(phi->operands.size() == operandSet.size()); + for (auto o : phi->operands) + CHECK(operandSet.contains(o.get())); + + } }; TEST_SUITE_BEGIN("DataFlowGraphBuilder"); @@ -119,6 +131,8 @@ TEST_CASE_FIXTURE(DataFlowGraphFixture, "phi") TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_not_owned_by_while") { + ScopedFastFlag _{FFlag::LuauDfgAllowUpdatesInLoops, true}; + dfg(R"( local x @@ -133,8 +147,9 @@ TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_not_owned_by_while") DefId x1 = getDef(); // x = true DefId x2 = getDef(); // local y = x - CHECK(x0 == x1); - CHECK(x1 == x2); + auto phi = get(x2); + REQUIRE(phi); + checkOperands(phi, {x0, x1}); } TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_owned_by_while") @@ -157,6 +172,8 @@ TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_owned_by_while") TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_not_owned_by_repeat") { + ScopedFastFlag _{FFlag::LuauDfgAllowUpdatesInLoops, true}; + dfg(R"( local x @@ -171,7 +188,7 @@ TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_not_owned_by_repeat") DefId x1 = getDef(); // x = true DefId x2 = getDef(); // local y = x - CHECK(x0 == x1); + CHECK(x0 != x1); CHECK(x1 == x2); } @@ -195,6 +212,8 @@ TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_owned_by_repeat") TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_not_owned_by_for") { + ScopedFastFlag _{FFlag::LuauDfgAllowUpdatesInLoops, true}; + dfg(R"( local x @@ -209,8 +228,9 @@ TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_not_owned_by_for") DefId x1 = getDef(); // x = true DefId x2 = getDef(); // local y = x - CHECK(x0 == x1); - CHECK(x1 == x2); + auto phi = get(x2); + REQUIRE(phi); + checkOperands(phi, {x0, x1}); } TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_owned_by_for") @@ -233,6 +253,8 @@ TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_owned_by_for") TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_not_owned_by_for_in") { + ScopedFastFlag _{FFlag::LuauDfgAllowUpdatesInLoops, true}; + dfg(R"( local x @@ -247,8 +269,9 @@ TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_not_owned_by_for_in") DefId x1 = getDef(); // x = true DefId x2 = getDef(); // local y = x - CHECK(x0 == x1); - CHECK(x1 == x2); + auto phi = get(x2); + REQUIRE(phi); + checkOperands(phi, {x0, x1}); } TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_owned_by_for_in") @@ -271,6 +294,8 @@ TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_owned_by_for_in") TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_preexisting_property_not_owned_by_while") { + ScopedFastFlag _{FFlag::LuauDfgAllowUpdatesInLoops, true}; + dfg(R"( local t = {} t.x = 5 @@ -286,8 +311,9 @@ TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_preexisting_property_not_owned_b DefId x2 = getDef(); // t.x = true DefId x3 = getDef(); // local y = t.x - CHECK(x1 == x2); - CHECK(x2 == x3); + auto phi = get(x3); + REQUIRE(phi); + checkOperands(phi, {x1, x2}); } TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_non_preexisting_property_not_owned_by_while") diff --git a/tests/FragmentAutocomplete.test.cpp b/tests/FragmentAutocomplete.test.cpp index f022d1c9..05967407 100644 --- a/tests/FragmentAutocomplete.test.cpp +++ b/tests/FragmentAutocomplete.test.cpp @@ -33,7 +33,6 @@ LUAU_FASTFLAG(LuauClonedTableAndFunctionTypesMustHaveScopes) LUAU_FASTFLAG(LuauDisableNewSolverAssertsInMixedMode) LUAU_FASTFLAG(LuauCloneTypeAliasBindings) LUAU_FASTFLAG(LuauDoNotClonePersistentBindings) -LUAU_FASTFLAG(LuauUserTypeFunTypecheck) LUAU_FASTFLAG(LuauBetterScopeSelection) LUAU_FASTFLAG(LuauBlockDiffFragmentSelection) LUAU_FASTFLAG(LuauFragmentAcMemoryLeak) @@ -3065,8 +3064,6 @@ z = a.P.E TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "user_defined_type_function_local") { - ScopedFastFlag luauUserTypeFunTypecheck{FFlag::LuauUserTypeFunTypecheck, true}; - const std::string source = R"(--!strict type function foo(x: type): type if x.tag == "singleton" then diff --git a/tests/Frontend.test.cpp b/tests/Frontend.test.cpp index 50e3fe8d..788481b7 100644 --- a/tests/Frontend.test.cpp +++ b/tests/Frontend.test.cpp @@ -17,6 +17,7 @@ LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(DebugLuauFreezeArena) LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAG(LuauNewNonStrictVisitTypes2) +LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck) namespace { @@ -876,6 +877,8 @@ TEST_CASE_FIXTURE(FrontendFixture, "discard_type_graphs") TEST_CASE_FIXTURE(FrontendFixture, "it_should_be_safe_to_stringify_errors_when_full_type_graph_is_discarded") { + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; + Frontend fe{&fileResolver, &configResolver, {false}}; fileResolver.source["Module/A"] = R"( @@ -892,13 +895,9 @@ TEST_CASE_FIXTURE(FrontendFixture, "it_should_be_safe_to_stringify_errors_when_f // It could segfault, or you could see weird type names like the empty string or if (FFlag::LuauSolverV2) { - REQUIRE_EQ( - "Type\n\t" - "'{ count: string }'" - "\ncould not be converted into\n\t" - "'{ Count: number }'", - toString(result.errors[0]) - ); + CHECK_EQ( + "Table type '{ count: string }' not compatible with type '{ Count: number }' because the former is missing field 'Count'", + toString(result.errors[0])); } else REQUIRE_EQ( diff --git a/tests/Generalization.test.cpp b/tests/Generalization.test.cpp index f9797784..97b9d058 100644 --- a/tests/Generalization.test.cpp +++ b/tests/Generalization.test.cpp @@ -15,7 +15,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauNonReentrantGeneralization2) +LUAU_FASTFLAG(LuauNonReentrantGeneralization3) LUAU_FASTFLAG(DebugLuauForbidInternalTypes) LUAU_FASTFLAG(LuauTrackInferredFunctionTypeFromCall) @@ -226,7 +226,7 @@ TEST_CASE_FIXTURE(GeneralizationFixture, "('a) -> 'a") TEST_CASE_FIXTURE(GeneralizationFixture, "(t1, (t1 <: 'b)) -> () where t1 = ('a <: (t1 <: 'b) & {number} & {number})") { - ScopedFastFlag sff{FFlag::LuauNonReentrantGeneralization2, true}; + ScopedFastFlag sff{FFlag::LuauNonReentrantGeneralization3, true}; TableType tt; tt.indexer = TableIndexer{builtinTypes.numberType, builtinTypes.numberType}; @@ -260,7 +260,7 @@ TEST_CASE_FIXTURE(GeneralizationFixture, "(('a <: number | string)) -> string?") TEST_CASE_FIXTURE(GeneralizationFixture, "(('a <: {'b})) -> ()") { - ScopedFastFlag sff{FFlag::LuauNonReentrantGeneralization2, true}; + ScopedFastFlag sff{FFlag::LuauNonReentrantGeneralization3, true}; auto [aTy, aFree] = freshType(); auto [bTy, bFree] = freshType(); diff --git a/tests/InferPolarity.test.cpp b/tests/InferPolarity.test.cpp index 8b1bc040..e87a6389 100644 --- a/tests/InferPolarity.test.cpp +++ b/tests/InferPolarity.test.cpp @@ -8,13 +8,13 @@ using namespace Luau; -LUAU_FASTFLAG(LuauNonReentrantGeneralization2); +LUAU_FASTFLAG(LuauNonReentrantGeneralization3); TEST_SUITE_BEGIN("InferPolarity"); TEST_CASE_FIXTURE(Fixture, "T where T = { m: (a) -> T }") { - ScopedFastFlag sff{FFlag::LuauNonReentrantGeneralization2, true}; + ScopedFastFlag sff{FFlag::LuauNonReentrantGeneralization3, true}; TypeArena arena; ScopePtr globalScope = std::make_shared(builtinTypes->anyTypePack); diff --git a/tests/Linter.test.cpp b/tests/Linter.test.cpp index ac32d827..1817767b 100644 --- a/tests/Linter.test.cpp +++ b/tests/Linter.test.cpp @@ -10,7 +10,7 @@ LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LintRedundantNativeAttribute); LUAU_FASTFLAG(LuauDeprecatedAttribute); -LUAU_FASTFLAG(LuauNonReentrantGeneralization2); +LUAU_FASTFLAG(LuauNonReentrantGeneralization3); using namespace Luau; @@ -1942,7 +1942,7 @@ print(foo:bar(2.0)) TEST_CASE_FIXTURE(BuiltinsFixture, "TableOperations") { // FIXME: For now this flag causes a stack overflow on Windows. - ScopedFastFlag _{FFlag::LuauNonReentrantGeneralization2, false}; + ScopedFastFlag _{FFlag::LuauNonReentrantGeneralization3, false}; LintResult result = lint(R"( local t = {} diff --git a/tests/NonStrictTypeChecker.test.cpp b/tests/NonStrictTypeChecker.test.cpp index 55eacb5b..cfa4050f 100644 --- a/tests/NonStrictTypeChecker.test.cpp +++ b/tests/NonStrictTypeChecker.test.cpp @@ -15,8 +15,6 @@ #include "doctest.h" #include -LUAU_FASTFLAG(LuauNewNonStrictWarnOnUnknownGlobals) -LUAU_FASTFLAG(LuauNonStrictVisitorImprovements) LUAU_FASTFLAG(LuauNewNonStrictVisitTypes2) using namespace Luau; @@ -362,8 +360,6 @@ end TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "function_def_sequencing_errors_2") { - ScopedFastFlag luauNonStrictVisitorImprovements{FFlag::LuauNonStrictVisitorImprovements, true}; - CheckResult result = checkNonStrict(R"( local t = {function(x) abs(x) @@ -510,8 +506,6 @@ foo.bar("hi") TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "exprgroup_is_checked") { - ScopedFastFlag sff{FFlag::LuauNonStrictVisitorImprovements, true}; - CheckResult result = checkNonStrict(R"( local foo = (abs("foo")) )"); @@ -527,8 +521,6 @@ TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "exprgroup_is_checked") TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "binop_is_checked") { - ScopedFastFlag sff{FFlag::LuauNonStrictVisitorImprovements, true}; - CheckResult result = checkNonStrict(R"( local foo = 4 + abs("foo") )"); @@ -653,8 +645,6 @@ TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "nonstrict_method_calls") TEST_CASE_FIXTURE(Fixture, "unknown_globals_in_non_strict") { - ScopedFastFlag flags[] = {{FFlag::LuauNonStrictVisitorImprovements, true}, {FFlag::LuauNewNonStrictWarnOnUnknownGlobals, true}}; - CheckResult result = check(Mode::Nonstrict, R"( foo = 5 local wrong1 = foob diff --git a/tests/Normalize.test.cpp b/tests/Normalize.test.cpp index a86f2554..60cbb3a1 100644 --- a/tests/Normalize.test.cpp +++ b/tests/Normalize.test.cpp @@ -15,10 +15,14 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauNormalizeIntersectionLimit) LUAU_FASTINT(LuauNormalizeUnionLimit) LUAU_FASTFLAG(DebugLuauGreedyGeneralization) -LUAU_FASTFLAG(LuauNormalizationCatchMetatableCycles) -LUAU_FASTFLAG(LuauSubtypingEnableReasoningLimit) -LUAU_FASTFLAG(LuauTypePackDetectCycles) -LUAU_FASTFLAG(LuauNonReentrantGeneralization2) +LUAU_FASTFLAG(LuauNonReentrantGeneralization3) +LUAU_FASTFLAG(LuauRefineWaitForBlockedTypesInTarget) +LUAU_FASTFLAG(LuauSimplifyOutOfLine) +LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect) +LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2) +LUAU_FASTFLAG(LuauSubtypeGenericsAndNegations) +LUAU_FASTFLAG(LuauNoMoreInjectiveTypeFunctions) +LUAU_FASTFLAG(LuauSubtypeGenericsAndNegations) using namespace Luau; @@ -1068,19 +1072,6 @@ TEST_CASE_FIXTURE(NormalizeFixture, "free_type_and_not_truthy") CHECK("'a & (false?)" == toString(result)); } -TEST_CASE_FIXTURE(NormalizeFixture, "normalize_recursive_metatable") -{ - ScopedFastFlag sff[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauNormalizationCatchMetatableCycles, true}}; - - TypeId root = arena.addType(BlockedType{}); - TypeId emptyTable = arena.addType(TableType(TableState::Sealed, {})); - TypeId metatable = arena.addType(MetatableType{emptyTable, root}); - emplaceType(asMutable(root), metatable); - auto normalized = normalizer.normalize(root); - REQUIRE(normalized); - CHECK_EQ("t1 where t1 = { @metatable t1, { } }", toString(normalizer.typeFromNormal(*normalized))); -} - TEST_CASE_FIXTURE(BuiltinsFixture, "normalizer_should_be_able_to_detect_cyclic_tables_and_not_stack_overflow") { if (!FFlag::LuauSolverV2) @@ -1186,12 +1177,20 @@ end )"); } +#if 0 TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_limit_function_intersection_complexity") { ScopedFastInt luauTypeInferRecursionLimit{FInt::LuauTypeInferRecursionLimit, 80}; ScopedFastInt luauNormalizeIntersectionLimit{FInt::LuauNormalizeIntersectionLimit, 50}; ScopedFastInt luauNormalizeUnionLimit{FInt::LuauNormalizeUnionLimit, 20}; + ScopedFastFlag _[] = { + {FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2, true}, + {FFlag::DebugLuauGreedyGeneralization, true}, + {FFlag::LuauSubtypeGenericsAndNegations, true}, + {FFlag::LuauNoMoreInjectiveTypeFunctions, true} + }; + CheckResult result = check(R"( function _(_).readu32(l0) return ({[_(_(_))]=_,[_(if _ then _)]=_,n0=_,})[_],nil @@ -1202,13 +1201,21 @@ _(_)[_(n32)] %= _(_(_)) LUAU_REQUIRE_ERRORS(result); } -#if !(defined(_WIN32) && !(defined(_M_X64) || defined(_M_ARM64))) TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_propagate_normalization_failures") { ScopedFastInt luauNormalizeIntersectionLimit{FInt::LuauNormalizeIntersectionLimit, 50}; ScopedFastInt luauNormalizeUnionLimit{FInt::LuauNormalizeUnionLimit, 20}; - ScopedFastFlag luauSubtypingEnableReasoningLimit{FFlag::LuauSubtypingEnableReasoningLimit, true}; - ScopedFastFlag luauTurnOffNonreentrantGeneralization{FFlag::LuauNonReentrantGeneralization2, false}; + + ScopedFastFlag _[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauOptimizeFalsyAndTruthyIntersect, true}, + {FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2, true}, + {FFlag::LuauSimplifyOutOfLine, true}, + {FFlag::LuauNonReentrantGeneralization3, false}, + {FFlag::DebugLuauGreedyGeneralization, true}, + {FFlag::LuauNoMoreInjectiveTypeFunctions, true}, + {FFlag::LuauSubtypeGenericsAndNegations, true}, + }; CheckResult result = check(R"( function _(_,"").readu32(l0) @@ -1223,7 +1230,7 @@ _().readu32 %= _(_(_(_),_)) TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_flatten_type_pack_cycle") { - ScopedFastFlag sff[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauTypePackDetectCycles, true}}; + ScopedFastFlag sff[] = {{FFlag::LuauSolverV2, true}}; // Note: if this stops throwing an exception, it means we fixed cycle construction and can replace with a regular check CHECK_THROWS_AS( @@ -1243,15 +1250,19 @@ do end #if 0 TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_union_type_pack_cycle") { - // FIXME? This test code happens not to ICE with eager generalization - // enabled. This could either be because the problem is fixed, or because - // another bug is obscuring the problem. - if (FFlag::DebugLuauGreedyGeneralization) - return; + ScopedFastFlag sff[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauRefineWaitForBlockedTypesInTarget, true}, + {FFlag::LuauSimplifyOutOfLine, true}, + {FFlag::LuauSubtypeGenericsAndNegations, true}, + {FFlag::LuauNoMoreInjectiveTypeFunctions, true}, + {FFlag::LuauOptimizeFalsyAndTruthyIntersect, true}, + {FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2, true}, + {FFlag::DebugLuauGreedyGeneralization, true} + }; + ScopedFastInt sfi{FInt::LuauTypeInferRecursionLimit, 0}; - ScopedFastFlag sff[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauTypePackDetectCycles, true}}; - - // Note: if this stops throwing an exception, it means we fixed cycle construction and can replace with a regular check + // FIXME CLI-153131: This is constructing a cyclic type pack CHECK_THROWS_AS( check(R"( function _(_).n0(l32,...) diff --git a/tests/Parser.test.cpp b/tests/Parser.test.cpp index 96a69a62..0a374269 100644 --- a/tests/Parser.test.cpp +++ b/tests/Parser.test.cpp @@ -19,9 +19,7 @@ LUAU_FASTINT(LuauParseErrorLimit) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) LUAU_FASTFLAG(LuauParseStringIndexer) -LUAU_FASTFLAG(LuauFixFunctionWithAttributesStartLocation) LUAU_FASTFLAG(LuauDeclareExternType) -LUAU_FASTFLAG(LuauStoreCSTData2) LUAU_DYNAMIC_FASTFLAG(DebugLuauReportReturnTypeVariadicWithTypeSuffix) // Clip with DebugLuauReportReturnTypeVariadicWithTypeSuffix @@ -2872,8 +2870,6 @@ TEST_CASE_FIXTURE(Fixture, "do_block_end_location_is_after_end_token") TEST_CASE_FIXTURE(Fixture, "function_start_locations_are_before_attributes") { - ScopedFastFlag _{FFlag::LuauFixFunctionWithAttributesStartLocation, true}; - AstStatBlock* stat = parse(R"( @native function globalFunction() @@ -2906,8 +2902,6 @@ TEST_CASE_FIXTURE(Fixture, "function_start_locations_are_before_attributes") TEST_CASE_FIXTURE(Fixture, "for_loop_with_single_var_has_comma_positions_of_size_zero") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; - ParseOptions parseOptions; parseOptions.storeCstData = true; diff --git a/tests/RequireByString.test.cpp b/tests/RequireByString.test.cpp index a9c2ea93..ad6b9fe2 100644 --- a/tests/RequireByString.test.cpp +++ b/tests/RequireByString.test.cpp @@ -368,13 +368,34 @@ TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireInitLua") assertOutputContainsAll({"true", "result from init.lua"}); } -TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireSubmoduleUsingSelf") +TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireSubmoduleUsingSelfIndirectly") { std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/nested_module_requirer"; runProtectedRequire(path); assertOutputContainsAll({"true", "result from submodule"}); } +TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireSubmoduleUsingSelfDirectly") +{ + std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/nested"; + runProtectedRequire(path); + assertOutputContainsAll({"true", "result from submodule"}); +} + +TEST_CASE_FIXTURE(ReplWithPathFixture, "CannotRequireInitLuauDirectly") +{ + std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/nested/init"; + runProtectedRequire(path); + assertOutputContainsAll({"false", "could not resolve child component \"init\""}); +} + +TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireNestedInits") +{ + std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/nested_inits_requirer"; + runProtectedRequire(path); + assertOutputContainsAll({"true", "result from nested_inits/init", "required into module"}); +} + TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireWithFileAmbiguity") { std::string ambiguousPath = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/ambiguous_file_requirer"; @@ -639,6 +660,10 @@ TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireFromLuauBinary") getLuauDirectory(PathType::Absolute) + "/tests/require/without_config/dependency.luau", getLuauDirectory(PathType::Relative) + "/tests/require/without_config/module.luau", getLuauDirectory(PathType::Absolute) + "/tests/require/without_config/module.luau", + getLuauDirectory(PathType::Relative) + "/tests/require/without_config/nested/init.luau", + getLuauDirectory(PathType::Absolute) + "/tests/require/without_config/nested/init.luau", + getLuauDirectory(PathType::Relative) + "/tests/require/with_config/src/submodule/init.luau", + getLuauDirectory(PathType::Absolute) + "/tests/require/with_config/src/submodule/init.luau", }; for (const std::string& path : paths) diff --git a/tests/ToString.test.cpp b/tests/ToString.test.cpp index 3cd344e7..501d2a7a 100644 --- a/tests/ToString.test.cpp +++ b/tests/ToString.test.cpp @@ -14,6 +14,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauRecursiveTypeParameterRestriction) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauAttributeSyntax) +LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck) TEST_SUITE_BEGIN("ToString"); @@ -864,38 +865,40 @@ TEST_CASE_FIXTURE(Fixture, "tostring_unsee_ttv_if_array") TEST_CASE_FIXTURE(Fixture, "tostring_error_mismatch") { + ScopedFastFlag _ {FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; + CheckResult result = check(R"( --!strict - function f1() : {a : number, b : string, c : { d : number}} - return { a = 1, b = "b", c = {d = "d"}} + function f1(t: {a : number, b: string, c: {d: string}}) : {a : number, b : string, c : { d : number}} + return t end )"); std::string expected; if (FFlag::LuauSolverV2) - expected = - "Type pack '{ a: number, b: string, c: { d: string } }' could not be converted into '{ a: number, b: string, c: { d: number } }'; \n" - "this is because in the 1st entry in the type pack, accessing `c.d` results in `string` in the former type and `number` in the latter " - "type, and `string` is not exactly `number`"; + expected = "Type\n\t" + "'{ a: number, b: string, c: { d: string } }'\n" + "could not be converted into\n\t" + "'{ a: number, b: string, c: { d: number } }'; \n" + "this is because accessing `c.d` results in `string` in the former type and `number` in the latter " + "type, and `string` is not exactly `number`"; else - expected = R"(Type - '{ a: number, b: string, c: { d: string } }' -could not be converted into - '{| a: number, b: string, c: {| d: number |} |}' -caused by: - Property 'c' is not compatible. -Type - '{ d: string }' -could not be converted into - '{| d: number |}' -caused by: - Property 'd' is not compatible. -Type 'string' could not be converted into 'number' in an invariant context)"; + expected = "Type\n\t" + "'{| a: number, b: string, c: {| d: string |} |}'\n" + "could not be converted into\n\t" + "'{| a: number, b: string, c: {| d: number |} |}'\n" + "caused by:\n " + "Property 'c' is not compatible.\n" + "Type\n\t" + "'{| d: string |}'\n" + "could not be converted into\n\t" + "'{| d: number |}'\n" + "caused by:\n " + "Property 'd' is not compatible.\n" + "Type 'string' could not be converted into 'number' in an invariant context"; LUAU_REQUIRE_ERROR_COUNT(1, result); - std::string actual = toString(result.errors[0]); - CHECK(expected == actual); } diff --git a/tests/Transpiler.test.cpp b/tests/Transpiler.test.cpp index 9c3f14b1..c587fa7c 100644 --- a/tests/Transpiler.test.cpp +++ b/tests/Transpiler.test.cpp @@ -12,7 +12,6 @@ using namespace Luau; -LUAU_FASTFLAG(LuauStoreCSTData2) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) LUAU_FASTFLAG(LuauStoreLocalAnnotationColonPositions) @@ -48,7 +47,6 @@ TEST_CASE("string_literals_containing_utf8") TEST_CASE("if_stmt_spaces_around_tokens") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; const std::string one = R"( if This then Once() end)"; CHECK_EQ(one, transpile(one).code); @@ -97,31 +95,15 @@ TEST_CASE("elseif_chains_indent_sensibly") TEST_CASE("strips_type_annotations") { const std::string code = R"( local s: string= 'hello there' )"; - if (FFlag::LuauStoreCSTData2) - { - const std::string expected = R"( local s = 'hello there' )"; - CHECK_EQ(expected, transpile(code).code); - } - else - { - const std::string expected = R"( local s = 'hello there' )"; - CHECK_EQ(expected, transpile(code).code); - } + const std::string expected = R"( local s = 'hello there' )"; + CHECK_EQ(expected, transpile(code).code); } TEST_CASE("strips_type_assertion_expressions") { const std::string code = R"( local s= some_function() :: any+ something_else() :: number )"; - if (FFlag::LuauStoreCSTData2) - { - const std::string expected = R"( local s= some_function() + something_else() )"; - CHECK_EQ(expected, transpile(code).code); - } - else - { - const std::string expected = R"( local s= some_function() + something_else() )"; - CHECK_EQ(expected, transpile(code).code); - } + const std::string expected = R"( local s= some_function() + something_else() )"; + CHECK_EQ(expected, transpile(code).code); } TEST_CASE("function_taking_ellipsis") @@ -148,7 +130,6 @@ TEST_CASE("for_loop") TEST_CASE("for_loop_spaces_around_tokens") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; const std::string one = R"( for index = 1, 10 do call(index) end )"; CHECK_EQ(one, transpile(one).code); @@ -173,7 +154,6 @@ TEST_CASE("for_in_loop") TEST_CASE("for_in_loop_spaces_around_tokens") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; const std::string one = R"( for k, v in ipairs(x) do end )"; CHECK_EQ(one, transpile(one).code); @@ -192,7 +172,6 @@ TEST_CASE("for_in_loop_spaces_around_tokens") TEST_CASE("for_in_single_variable") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; const std::string one = R"( for key in pairs(x) do end )"; CHECK_EQ(one, transpile(one).code); } @@ -205,7 +184,6 @@ TEST_CASE("while_loop") TEST_CASE("while_loop_spaces_around_tokens") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; const std::string one = R"( while f(x) do print() end )"; CHECK_EQ(one, transpile(one).code); @@ -227,7 +205,6 @@ TEST_CASE("repeat_until_loop") TEST_CASE("repeat_until_loop_condition_on_new_line") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; const std::string code = R"( repeat print() @@ -259,7 +236,6 @@ TEST_CASE("local_assignment") TEST_CASE("local_assignment_spaces_around_tokens") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; const std::string one = R"( local x = 1 )"; CHECK_EQ(one, transpile(one).code); @@ -293,7 +269,6 @@ TEST_CASE("local_function") TEST_CASE("local_function_spaces_around_tokens") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; const std::string one = R"( local function p(o, m, ...) end )"; CHECK_EQ(one, transpile(one).code); @@ -312,7 +287,6 @@ TEST_CASE("function") TEST_CASE("function_spaces_around_tokens") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; const std::string two = R"( function p(o, m, ...) end )"; CHECK_EQ(two, transpile(two).code); @@ -341,7 +315,6 @@ TEST_CASE("function_spaces_around_tokens") TEST_CASE("function_with_types_spaces_around_tokens") { ScopedFastFlag sffs[] = { - {FFlag::LuauStoreCSTData2, true}, {FFlag::LuauStoreReturnTypesAsPackOnAst, true}, {FFlag::LuauStoreLocalAnnotationColonPositions, true}, }; @@ -402,7 +375,7 @@ TEST_CASE("function_with_types_spaces_around_tokens") TEST_CASE("returns_spaces_around_tokens") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + const std::string one = R"( return 1 )"; CHECK_EQ(one, transpile(one).code); @@ -415,7 +388,7 @@ TEST_CASE("returns_spaces_around_tokens") TEST_CASE_FIXTURE(Fixture, "type_alias_spaces_around_tokens") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + std::string code = R"( type Foo = string )"; CHECK_EQ(code, transpile(code, {}, true).code); @@ -464,7 +437,7 @@ TEST_CASE_FIXTURE(Fixture, "type_alias_spaces_around_tokens") TEST_CASE_FIXTURE(Fixture, "type_alias_with_defaults_spaces_around_tokens") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + std::string code = R"( type Foo = string )"; CHECK_EQ(code, transpile(code, {}, true).code); @@ -525,7 +498,7 @@ TEST_CASE("table_literal_closing_brace_at_correct_position") TEST_CASE("table_literal_with_semicolon_separators") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + const std::string code = R"( local t = { x = 1; y = 2 } )"; @@ -535,7 +508,7 @@ TEST_CASE("table_literal_with_semicolon_separators") TEST_CASE("table_literal_with_trailing_separators") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + const std::string code = R"( local t = { x = 1, y = 2, } )"; @@ -545,7 +518,7 @@ TEST_CASE("table_literal_with_trailing_separators") TEST_CASE("table_literal_with_spaces_around_separator") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + const std::string code = R"( local t = { x = 1 , y = 2 } )"; @@ -555,7 +528,7 @@ TEST_CASE("table_literal_with_spaces_around_separator") TEST_CASE("table_literal_with_spaces_around_equals") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + const std::string code = R"( local t = { x = 1 } )"; @@ -565,7 +538,7 @@ TEST_CASE("table_literal_with_spaces_around_equals") TEST_CASE("table_literal_multiline_with_indexers") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + const std::string code = R"( local t = { ["my first value"] = "x"; @@ -593,15 +566,7 @@ TEST_CASE("spaces_between_keywords_even_if_it_pushes_the_line_estimation_off") // Luau::Parser doesn't exactly preserve the string representation of numbers in Lua, so we can find ourselves // falling out of sync with the original code. We need to push keywords out so that there's at least one space between them. const std::string code = R"( if math.abs(raySlope) < .01 then return 0 end )"; - if (FFlag::LuauStoreCSTData2) - { - CHECK_EQ(code, transpile(code).code); - } - else - { - const std::string expected = R"( if math.abs(raySlope) < 0.01 then return 0 end)"; - CHECK_EQ(expected, transpile(code).code); - } + CHECK_EQ(code, transpile(code).code); } TEST_CASE("numbers") @@ -613,34 +578,26 @@ TEST_CASE("numbers") TEST_CASE("infinity") { const std::string code = R"( local a = 1e500 local b = 1e400 )"; - if (FFlag::LuauStoreCSTData2) - { - CHECK_EQ(code, transpile(code).code); - } - else - { - const std::string expected = R"( local a = 1e500 local b = 1e500 )"; - CHECK_EQ(expected, transpile(code).code); - } + CHECK_EQ(code, transpile(code).code); } TEST_CASE("numbers_with_separators") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + const std::string code = R"( local a = 123_456_789 )"; CHECK_EQ(code, transpile(code).code); } TEST_CASE("hexadecimal_numbers") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + const std::string code = R"( local a = 0xFFFF )"; CHECK_EQ(code, transpile(code).code); } TEST_CASE("binary_numbers") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + const std::string code = R"( local a = 0b0101 )"; CHECK_EQ(code, transpile(code).code); } @@ -653,28 +610,28 @@ TEST_CASE("single_quoted_strings") TEST_CASE("double_quoted_strings") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + const std::string code = R"( local a = "hello world" )"; CHECK_EQ(code, transpile(code).code); } TEST_CASE("simple_interp_string") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + const std::string code = R"( local a = `hello world` )"; CHECK_EQ(code, transpile(code).code); } TEST_CASE("raw_strings") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + const std::string code = R"( local a = [[ hello world ]] )"; CHECK_EQ(code, transpile(code).code); } TEST_CASE("raw_strings_with_blocks") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + const std::string code = R"( local a = [==[ hello world ]==] )"; CHECK_EQ(code, transpile(code).code); } @@ -693,7 +650,7 @@ TEST_CASE("escaped_strings_2") TEST_CASE("escaped_strings_newline") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + const std::string code = R"( print("foo \ bar") @@ -703,14 +660,14 @@ TEST_CASE("escaped_strings_newline") TEST_CASE("escaped_strings_raw") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + const std::string code = R"( local x = [=[\v<((do|load)file|require)\s*\(?['"]\zs[^'"]+\ze['"]]=] )"; CHECK_EQ(code, transpile(code).code); } TEST_CASE("position_correctly_updated_when_writing_multiline_string") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + const std::string code = R"( call([[ testing @@ -756,56 +713,56 @@ TEST_CASE("function_call_parentheses_multiple_args_no_space") TEST_CASE("function_call_parentheses_multiple_args_space_before_commas") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + const std::string code = R"( call(arg1 ,arg3 ,arg3) )"; CHECK_EQ(code, transpile(code).code); } TEST_CASE("function_call_spaces_before_parentheses") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + const std::string code = R"( call () )"; CHECK_EQ(code, transpile(code).code); } TEST_CASE("function_call_spaces_within_parentheses") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + const std::string code = R"( call( ) )"; CHECK_EQ(code, transpile(code).code); } TEST_CASE("function_call_string_double_quotes") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + const std::string code = R"( call "string" )"; CHECK_EQ(code, transpile(code).code); } TEST_CASE("function_call_string_single_quotes") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + const std::string code = R"( call 'string' )"; CHECK_EQ(code, transpile(code).code); } TEST_CASE("function_call_string_no_space") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + const std::string code = R"( call'string' )"; CHECK_EQ(code, transpile(code).code); } TEST_CASE("function_call_table_literal") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + const std::string code = R"( call { x = 1 } )"; CHECK_EQ(code, transpile(code).code); } TEST_CASE("function_call_table_literal_no_space") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + const std::string code = R"( call{x=1} )"; CHECK_EQ(code, transpile(code).code); } @@ -850,7 +807,7 @@ TEST_CASE("emit_a_do_block_in_cases_of_potentially_ambiguous_syntax") TEST_CASE_FIXTURE(Fixture, "parentheses_multiline") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + std::string code = R"( local test = ( x @@ -862,9 +819,6 @@ local test = ( TEST_CASE_FIXTURE(Fixture, "stmt_semicolon") { - ScopedFastFlag flags[] = { - {FFlag::LuauStoreCSTData2, true}, - }; std::string code = R"( local test = 1; )"; CHECK_EQ(code, transpile(code, {}, true).code); @@ -874,7 +828,6 @@ TEST_CASE_FIXTURE(Fixture, "stmt_semicolon") TEST_CASE_FIXTURE(Fixture, "do_block_ending_with_semicolon") { - ScopedFastFlag sff{FFlag::LuauStoreCSTData2, true}; std::string code = R"( do return; @@ -885,9 +838,6 @@ TEST_CASE_FIXTURE(Fixture, "do_block_ending_with_semicolon") TEST_CASE_FIXTURE(Fixture, "if_stmt_semicolon") { - ScopedFastFlag flags[] = { - {FFlag::LuauStoreCSTData2, true}, - }; std::string code = R"( if init then x = string.sub(x, utf8.offset(x, init)); @@ -898,9 +848,6 @@ TEST_CASE_FIXTURE(Fixture, "if_stmt_semicolon") TEST_CASE_FIXTURE(Fixture, "if_stmt_semicolon_2") { - ScopedFastFlag flags[] = { - {FFlag::LuauStoreCSTData2, true}, - }; std::string code = R"( if (t < 1) then return c/2*t*t + b end; )"; @@ -909,9 +856,6 @@ TEST_CASE_FIXTURE(Fixture, "if_stmt_semicolon_2") TEST_CASE_FIXTURE(Fixture, "for_loop_stmt_semicolon") { - ScopedFastFlag flags[] = { - {FFlag::LuauStoreCSTData2, true}, - }; std::string code = R"( for i,v in ... do end; @@ -921,9 +865,6 @@ TEST_CASE_FIXTURE(Fixture, "for_loop_stmt_semicolon") TEST_CASE_FIXTURE(Fixture, "while_do_semicolon") { - ScopedFastFlag flags[] = { - {FFlag::LuauStoreCSTData2, true}, - }; std::string code = R"( while true do end; @@ -933,9 +874,6 @@ TEST_CASE_FIXTURE(Fixture, "while_do_semicolon") TEST_CASE_FIXTURE(Fixture, "function_definition_semicolon") { - ScopedFastFlag flags[] = { - {FFlag::LuauStoreCSTData2, true}, - }; std::string code = R"( function foo() end; @@ -1013,16 +951,7 @@ TEST_CASE("a_table_key_can_be_the_empty_string") TEST_CASE("always_emit_a_space_after_local_keyword") { std::string code = "do local aZZZZ = Workspace.P1.Shape local bZZZZ = Enum.PartType.Cylinder end"; - - if (FFlag::LuauStoreCSTData2) - { - CHECK_EQ(code, transpile(code).code); - } - else - { - std::string expected = "do local aZZZZ = Workspace.P1 .Shape local bZZZZ= Enum.PartType.Cylinder end"; - CHECK_EQ(expected, transpile(code).code); - } + CHECK_EQ(code, transpile(code).code); } TEST_CASE_FIXTURE(Fixture, "types_should_not_be_considered_cyclic_if_they_are_not_recursive") @@ -1059,23 +988,13 @@ TEST_CASE_FIXTURE(Fixture, "type_lists_should_be_emitted_correctly") end )"; - std::string expected = FFlag::LuauStoreCSTData2 ? R"( + std::string expected = R"( local a:(a:string,b:number,...string)->(string,...number)=function(a:string,b:number,...:string): (string,...number) end local b:(...string)->(...number)=function(...:string): ...number end - local c:()->()=function(): () - end - )" - : R"( - local a:(string,number,...string)->(string,...number)=function(a:string,b:number,...:string): (string,...number) - end - - local b:(...string)->(...number)=function(...:string): ...number - end - local c:()->()=function(): () end )"; @@ -1115,7 +1034,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_type_assertion") TEST_CASE_FIXTURE(Fixture, "type_assertion_spaces_around_tokens") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + std::string code = "local a = 5 :: number"; CHECK_EQ(code, transpile(code, {}, true).code); @@ -1132,7 +1051,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_if_then_else") TEST_CASE_FIXTURE(Fixture, "transpile_if_then_else_multiple_conditions") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + std::string code = "local a = if 1 then 2 elseif 3 then 4 else 5"; CHECK_EQ(code, transpile(code).code); @@ -1140,7 +1059,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_if_then_else_multiple_conditions") TEST_CASE_FIXTURE(Fixture, "transpile_if_then_else_multiple_conditions_2") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + std::string code = R"( local x = if yes then nil @@ -1156,7 +1075,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_if_then_else_multiple_conditions_2") TEST_CASE_FIXTURE(Fixture, "if_then_else_spaces_around_tokens") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + std::string code = "local a = if 1 then 2 else 3"; CHECK_EQ(code, transpile(code).code); @@ -1193,7 +1112,7 @@ TEST_CASE_FIXTURE(Fixture, "if_then_else_spaces_around_tokens") TEST_CASE_FIXTURE(Fixture, "if_then_else_spaces_between_else_if") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + std::string code = R"( return if a then "was a" else @@ -1221,7 +1140,7 @@ local a: Import.Type TEST_CASE_FIXTURE(Fixture, "transpile_type_reference_spaces_around_tokens") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + std::string code = R"( local _: Foo.Type )"; CHECK_EQ(code, transpile(code, {}, true).code); @@ -1250,7 +1169,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_type_reference_spaces_around_tokens") TEST_CASE_FIXTURE(Fixture, "transpile_type_annotation_spaces_around_tokens") { ScopedFastFlag sffs[] = { - {FFlag::LuauStoreCSTData2, true}, {FFlag::LuauStoreLocalAnnotationColonPositions, true}, }; std::string code = R"( local _: Type )"; @@ -1272,7 +1190,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_type_annotation_spaces_around_tokens") TEST_CASE_FIXTURE(Fixture, "transpile_for_loop_annotation_spaces_around_tokens") { ScopedFastFlag sffs[] = { - {FFlag::LuauStoreCSTData2, true}, {FFlag::LuauStoreLocalAnnotationColonPositions, true}, }; std::string code = R"( for i: number = 1, 10 do end )"; @@ -1313,7 +1230,7 @@ local b: Packed<(number, string)> TEST_CASE_FIXTURE(Fixture, "type_packs_spaces_around_tokens") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + std::string code = R"( type _ = Packed< T...> )"; CHECK_EQ(code, transpile(code, {}, true).code); @@ -1371,11 +1288,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_union_type_nested_2") TEST_CASE_FIXTURE(Fixture, "transpile_union_type_nested_3") { std::string code = "local a: nil | (string & number)"; - - if (FFlag::LuauStoreCSTData2) - CHECK_EQ(code, transpile(code, {}, true).code); - else - CHECK_EQ("local a: (string & number)?", transpile(code, {}, true).code); + CHECK_EQ(code, transpile(code, {}, true).code); } TEST_CASE_FIXTURE(Fixture, "transpile_intersection_type_nested") @@ -1395,7 +1308,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_intersection_type_nested_2") TEST_CASE_FIXTURE(Fixture, "transpile_intersection_type_with_function") { ScopedFastFlag flags[] = { - {FFlag::LuauStoreCSTData2, true}, {FFlag::LuauStoreReturnTypesAsPackOnAst, true}, }; std::string code = "type FnB = () -> U... & T"; @@ -1405,9 +1317,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_intersection_type_with_function") TEST_CASE_FIXTURE(Fixture, "transpile_leading_union_pipe") { - ScopedFastFlag flags[] = { - {FFlag::LuauStoreCSTData2, true}, - }; std::string code = "local a: | string | number"; CHECK_EQ(code, transpile(code, {}, true).code); @@ -1417,9 +1326,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_leading_union_pipe") TEST_CASE_FIXTURE(Fixture, "transpile_union_spaces_around_tokens") { - ScopedFastFlag flags[] = { - {FFlag::LuauStoreCSTData2, true}, - }; std::string code = "local a: string | number"; CHECK_EQ(code, transpile(code, {}, true).code); @@ -1429,9 +1335,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_union_spaces_around_tokens") TEST_CASE_FIXTURE(Fixture, "transpile_leading_intersection_ampersand") { - ScopedFastFlag flags[] = { - {FFlag::LuauStoreCSTData2, true}, - }; std::string code = "local a: & string & number"; CHECK_EQ(code, transpile(code, {}, true).code); @@ -1441,9 +1344,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_leading_intersection_ampersand") TEST_CASE_FIXTURE(Fixture, "transpile_intersection_spaces_around_tokens") { - ScopedFastFlag flags[] = { - {FFlag::LuauStoreCSTData2, true}, - }; std::string code = "local a: string & number"; CHECK_EQ(code, transpile(code, {}, true).code); @@ -1453,9 +1353,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_intersection_spaces_around_tokens") TEST_CASE_FIXTURE(Fixture, "transpile_mixed_union_intersection") { - ScopedFastFlag flags[] = { - {FFlag::LuauStoreCSTData2, true}, - }; std::string code = "local a: string | (Foo & Bar)"; CHECK_EQ(code, transpile(code, {}, true).code); @@ -1480,9 +1377,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_mixed_union_intersection") TEST_CASE_FIXTURE(Fixture, "transpile_preserve_union_optional_style") { - ScopedFastFlag flags[] = { - {FFlag::LuauStoreCSTData2, true}, - }; std::string code = "local a: string | nil"; CHECK_EQ(code, transpile(code, {}, true).code); @@ -1514,7 +1408,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_varargs") TEST_CASE_FIXTURE(Fixture, "index_name_spaces_around_tokens") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + std::string one = "local _ = a.name"; CHECK_EQ(one, transpile(one, {}, true).code); @@ -1527,7 +1421,7 @@ TEST_CASE_FIXTURE(Fixture, "index_name_spaces_around_tokens") TEST_CASE_FIXTURE(Fixture, "index_name_ends_with_digit") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + std::string code = "sparkles.Color = Color3.new()"; CHECK_EQ(code, transpile(code, {}, true).code); } @@ -1541,7 +1435,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_index_expr") TEST_CASE_FIXTURE(Fixture, "index_expr_spaces_around_tokens") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + std::string one = "local _ = a[2]"; CHECK_EQ(one, transpile(one, {}, true).code); @@ -1585,7 +1479,7 @@ local _ = # e TEST_CASE_FIXTURE(Fixture, "binary_spaces_around_tokens") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + std::string code = R"( local _ = 1+1 local _ = 1 +1 @@ -1627,7 +1521,7 @@ a ..= ' - result' TEST_CASE_FIXTURE(Fixture, "compound_assignment_spaces_around_tokens") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + std::string one = R"( a += 1 )"; CHECK_EQ(one, transpile(one, {}, true).code); @@ -1644,7 +1538,7 @@ TEST_CASE_FIXTURE(Fixture, "transpile_assign_multiple") TEST_CASE_FIXTURE(Fixture, "transpile_assign_spaces_around_tokens") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + std::string one = "a = 1"; CHECK_EQ(one, transpile(one).code); @@ -1680,11 +1574,7 @@ local f: (T, S...)->(number) = foo TEST_CASE_FIXTURE(Fixture, "transpile_union_reverse") { std::string code = "local a: nil | number"; - - if (FFlag::LuauStoreCSTData2) - CHECK_EQ(code, transpile(code, {}, true).code); - else - CHECK_EQ("local a: number?", transpile(code, {}, true).code); + CHECK_EQ(code, transpile(code, {}, true).code); } TEST_CASE_FIXTURE(Fixture, "transpile_for_in_multiple") @@ -1799,9 +1689,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_for_in_multiple_types") TEST_CASE_FIXTURE(Fixture, "transpile_string_interp") { - ScopedFastFlag fflags[] = { - {FFlag::LuauStoreCSTData2, true}, - }; std::string code = R"( local _ = `hello {name}` )"; CHECK_EQ(code, transpile(code, {}, true).code); @@ -1809,9 +1696,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_string_interp") TEST_CASE_FIXTURE(Fixture, "transpile_string_interp_multiline") { - ScopedFastFlag fflags[] = { - {FFlag::LuauStoreCSTData2, true}, - }; std::string code = R"( local _ = `hello { name }!` )"; @@ -1821,9 +1705,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_string_interp_multiline") TEST_CASE_FIXTURE(Fixture, "transpile_string_interp_on_new_line") { - ScopedFastFlag fflags[] = { - {FFlag::LuauStoreCSTData2, true}, - }; std::string code = R"( error( `a {b} c` @@ -1835,7 +1716,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_string_interp_on_new_line") TEST_CASE_FIXTURE(Fixture, "transpile_string_interp_multiline_escape") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; std::string code = R"( local _ = `hello \ world!` )"; @@ -1844,9 +1724,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_string_interp_multiline_escape") TEST_CASE_FIXTURE(Fixture, "transpile_string_literal_escape") { - ScopedFastFlag fflags[] = { - {FFlag::LuauStoreCSTData2, true}, - }; std::string code = R"( local _ = ` bracket = \{, backtick = \` = {'ok'} ` )"; CHECK_EQ(code, transpile(code, {}, true).code); @@ -1861,7 +1738,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_type_functions") TEST_CASE_FIXTURE(Fixture, "transpile_type_functions_spaces_around_tokens") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; std::string code = R"( type function foo() end )"; CHECK_EQ(code, transpile(code, {}, true).code); @@ -1877,7 +1753,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_type_functions_spaces_around_tokens") TEST_CASE_FIXTURE(Fixture, "transpile_typeof_spaces_around_tokens") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; std::string code = R"( type X = typeof(x) )"; CHECK_EQ(code, transpile(code, {}, true).code); @@ -1902,14 +1777,14 @@ TEST_CASE("transpile_single_quoted_string_types") TEST_CASE("transpile_double_quoted_string_types") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + const std::string code = R"( type a = "hello world" )"; CHECK_EQ(code, transpile(code, {}, true).code); } TEST_CASE("transpile_raw_string_types") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + std::string code = R"( type a = [[ hello world ]] )"; CHECK_EQ(code, transpile(code, {}, true).code); @@ -1919,14 +1794,14 @@ TEST_CASE("transpile_raw_string_types") TEST_CASE("transpile_escaped_string_types") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + const std::string code = R"( type a = "\\b\\t\\n\\\\" )"; CHECK_EQ(code, transpile(code, {}, true).code); } TEST_CASE("transpile_type_table_semicolon_separators") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + const std::string code = R"( type Foo = { bar: number; @@ -1938,7 +1813,7 @@ TEST_CASE("transpile_type_table_semicolon_separators") TEST_CASE("transpile_type_table_access_modifiers") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + std::string code = R"( type Foo = { read bar: number, @@ -1959,7 +1834,7 @@ TEST_CASE("transpile_type_table_access_modifiers") TEST_CASE("transpile_type_table_spaces_between_tokens") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + std::string code = R"( type Foo = { bar: number, } )"; CHECK_EQ(code, transpile(code, {}, true).code); @@ -2002,7 +1877,7 @@ TEST_CASE("transpile_type_table_spaces_between_tokens") TEST_CASE("transpile_type_table_preserve_original_indexer_style") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + std::string code = R"( type Foo = { [number]: string @@ -2018,7 +1893,7 @@ TEST_CASE("transpile_type_table_preserve_original_indexer_style") TEST_CASE("transpile_type_table_preserve_indexer_location") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + std::string code = R"( type Foo = { [number]: string, @@ -2047,7 +1922,7 @@ TEST_CASE("transpile_type_table_preserve_indexer_location") TEST_CASE("transpile_type_table_preserve_property_definition_style") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + std::string code = R"( type Foo = { ["$$typeof1"]: string, @@ -2059,7 +1934,7 @@ TEST_CASE("transpile_type_table_preserve_property_definition_style") TEST_CASE("transpile_type_table_string_properties_spaces_between_tokens") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + std::string code = R"( type Foo = { [ "$$typeof1"]: string, @@ -2071,9 +1946,6 @@ TEST_CASE("transpile_type_table_string_properties_spaces_between_tokens") TEST_CASE("transpile_types_preserve_parentheses_style") { - ScopedFastFlag flags[] = { - {FFlag::LuauStoreCSTData2, true}, - }; std::string code = R"( type Foo = number )"; CHECK_EQ(code, transpile(code, {}, true).code); @@ -2113,7 +1985,6 @@ end TEST_CASE("transpile_type_function_unnamed_arguments") { ScopedFastFlag flags[] = { - {FFlag::LuauStoreCSTData2, true}, {FFlag::LuauStoreReturnTypesAsPackOnAst, true}, }; std::string code = R"( type Foo = () -> () )"; @@ -2150,7 +2021,6 @@ TEST_CASE("transpile_type_function_unnamed_arguments") TEST_CASE("transpile_type_function_named_arguments") { ScopedFastFlag flags[] = { - {FFlag::LuauStoreCSTData2, true}, {FFlag::LuauStoreReturnTypesAsPackOnAst, true}, }; std::string code = R"( type Foo = (x: string) -> () )"; @@ -2181,7 +2051,6 @@ TEST_CASE("transpile_type_function_named_arguments") TEST_CASE("transpile_type_function_generics") { ScopedFastFlag flags[] = { - {FFlag::LuauStoreCSTData2, true}, {FFlag::LuauStoreReturnTypesAsPackOnAst, true}, }; std::string code = R"( type Foo = () -> () )"; @@ -2218,7 +2087,6 @@ TEST_CASE("transpile_type_function_generics") TEST_CASE("transpile_type_function_return_types") { ScopedFastFlag fflags[] = { - {FFlag::LuauStoreCSTData2, true}, {FFlag::LuauStoreReturnTypesAsPackOnAst, true}, }; std::string code = R"( type Foo = () -> () )"; @@ -2272,7 +2140,7 @@ TEST_CASE("fuzzer_nil_optional") TEST_CASE("transpile_function_attributes") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + std::string code = R"( @native function foo() diff --git a/tests/TypeFunction.test.cpp b/tests/TypeFunction.test.cpp index 1a0c3273..e379d351 100644 --- a/tests/TypeFunction.test.cpp +++ b/tests/TypeFunction.test.cpp @@ -13,16 +13,12 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauIndexTypeFunctionImprovements) LUAU_DYNAMIC_FASTINT(LuauTypeFamilyApplicationCartesianProductLimit) -LUAU_FASTFLAG(LuauIndexTypeFunctionFunctionMetamethods) -LUAU_FASTFLAG(LuauMetatableTypeFunctions) -LUAU_FASTFLAG(LuauMetatablesHaveLength) LUAU_FASTFLAG(DebugLuauGreedyGeneralization) -LUAU_FASTFLAG(LuauIndexAnyIsAny) -LUAU_FASTFLAG(LuauNewTypeFunReductionChecks2) LUAU_FASTFLAG(LuauHasPropProperBlock) -LUAU_FASTFLAG(LuauFixCyclicIndexInIndexer) +LUAU_FASTFLAG(LuauSimplifyOutOfLine) +LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck) +LUAU_FASTFLAG(LuauErrorSuppressionTypeFunctionArgs) struct TypeFunctionFixture : Fixture { @@ -150,8 +146,6 @@ TEST_CASE_FIXTURE(TypeFunctionFixture, "unsolvable_function") if (!FFlag::LuauSolverV2) return; - ScopedFastFlag luauNewTypeFunReductionChecks2{FFlag::LuauNewTypeFunReductionChecks2, true}; - CheckResult result = check(R"( local impossible: (Swap) -> Swap> local a = impossible(123) @@ -165,6 +159,8 @@ TEST_CASE_FIXTURE(TypeFunctionFixture, "unsolvable_function") TEST_CASE_FIXTURE(TypeFunctionFixture, "table_internal_functions") { + ScopedFastFlag _{FFlag::LuauSimplifyOutOfLine, true}; + if (!FFlag::LuauSolverV2) return; @@ -178,9 +174,8 @@ TEST_CASE_FIXTURE(TypeFunctionFixture, "table_internal_functions") LUAU_REQUIRE_ERROR_COUNT(1, result); CHECK(toString(requireType("a")) == "{string}"); CHECK(toString(requireType("b")) == "{number}"); - // FIXME: table types are constructing a trivial union here. - CHECK(toString(requireType("c")) == "{Swap}"); - CHECK(toString(result.errors[0]) == "Type function instance Swap is uninhabited"); + CHECK(toString(requireType("c")) == "{Swap}"); + CHECK(toString(result.errors[0]) == "Type function instance Swap is uninhabited"); } TEST_CASE_FIXTURE(TypeFunctionFixture, "function_internal_functions") @@ -355,6 +350,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_function_works") if (!FFlag::LuauSolverV2) return; + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; + CheckResult result = check(R"( type MyObject = { x: number, y: number, z: number } type KeysOfMyObject = keyof @@ -365,10 +362,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_function_works") LUAU_REQUIRE_ERROR_COUNT(1, result); - TypePackMismatch* tpm = get(result.errors[0]); - REQUIRE(tpm); - CHECK_EQ("\"x\" | \"y\"", toString(tpm->wantedTp)); - CHECK_EQ("\"x\" | \"y\" | \"z\"", toString(tpm->givenTp)); + TypeMismatch* tm = get(result.errors[0]); + REQUIRE(tm); + CHECK_EQ("\"x\" | \"y\"", toString(tm->wantedType)); + CHECK_EQ("\"x\" | \"y\" | \"z\"", toString(tm->givenType)); } TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_function_works_with_metatables") @@ -376,6 +373,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_function_works_with_metatables") if (!FFlag::LuauSolverV2) return; + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; + CheckResult result = check(R"( local metatable = { __index = {w = 1} } local obj = setmetatable({x = 1, y = 2, z = 3}, metatable) @@ -388,10 +387,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_function_works_with_metatables") LUAU_REQUIRE_ERROR_COUNT(1, result); - TypePackMismatch* tpm = get(result.errors[0]); - REQUIRE(tpm); - CHECK_EQ("\"x\" | \"y\" | \"z\"", toString(tpm->wantedTp)); - CHECK_EQ("\"w\" | \"x\" | \"y\" | \"z\"", toString(tpm->givenTp)); + TypeMismatch* tm = get(result.errors[0]); + REQUIRE(tm); + CHECK_EQ("\"x\" | \"y\" | \"z\"", toString(tm->wantedType)); + CHECK_EQ("\"w\" | \"x\" | \"y\" | \"z\"", toString(tm->givenType)); } TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_single_entry_no_uniontype") @@ -433,6 +432,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_function_errors_if_it_has_nontabl TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_function_string_indexer") { + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; + if (!FFlag::LuauSolverV2) return; @@ -448,15 +449,15 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_function_string_indexer") LUAU_REQUIRE_ERROR_COUNT(2, result); - TypePackMismatch* tpm = get(result.errors[0]); - REQUIRE(tpm); - CHECK_EQ("\"z\"", toString(tpm->wantedTp)); - CHECK_EQ("string", toString(tpm->givenTp)); + TypeMismatch* tm = get(result.errors[0]); + REQUIRE(tm); + CHECK_EQ("\"z\"", toString(tm->wantedType)); + CHECK_EQ("string", toString(tm->givenType)); - tpm = get(result.errors[1]); - REQUIRE(tpm); - CHECK_EQ("\"z\"", toString(tpm->wantedTp)); - CHECK_EQ("\"x\" | \"y\" | \"z\"", toString(tpm->givenTp)); + tm = get(result.errors[1]); + REQUIRE(tm); + CHECK_EQ("\"z\"", toString(tm->wantedType)); + CHECK_EQ("\"x\" | \"y\" | \"z\"", toString(tm->givenType)); } TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_function_common_subset_if_union_of_differing_tables") @@ -464,6 +465,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_function_common_subset_if_union_o if (!FFlag::LuauSolverV2) return; + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; + CheckResult result = check(R"( type MyObject = { x: number, y: number, z: number } type MyOtherObject = { w: number, y: number, z: number } @@ -474,10 +477,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_function_common_subset_if_union_o LUAU_REQUIRE_ERROR_COUNT(1, result); - TypePackMismatch* tpm = get(result.errors[0]); - REQUIRE(tpm); - CHECK_EQ("\"z\"", toString(tpm->wantedTp)); - CHECK_EQ("\"y\" | \"z\"", toString(tpm->givenTp)); + TypeMismatch* tm = get(result.errors[0]); + REQUIRE(tm); + CHECK_EQ("\"z\"", toString(tm->wantedType)); + CHECK_EQ("\"y\" | \"z\"", toString(tm->givenType)); } TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_function_never_for_empty_table") @@ -500,6 +503,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_function_works") if (!FFlag::LuauSolverV2) return; + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; + CheckResult result = check(R"( type MyObject = { x: number, y: number, z: number } type KeysOfMyObject = rawkeyof @@ -510,10 +515,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_function_works") LUAU_REQUIRE_ERROR_COUNT(1, result); - TypePackMismatch* tpm = get(result.errors[0]); - REQUIRE(tpm); - CHECK_EQ("\"x\" | \"y\"", toString(tpm->wantedTp)); - CHECK_EQ("\"x\" | \"y\" | \"z\"", toString(tpm->givenTp)); + TypeMismatch* tm = get(result.errors[0]); + REQUIRE(tm); + CHECK_EQ("\"x\" | \"y\"", toString(tm->wantedType)); + CHECK_EQ("\"x\" | \"y\" | \"z\"", toString(tm->givenType)); } TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_function_ignores_metatables") @@ -521,6 +526,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_function_ignores_metatables") if (!FFlag::LuauSolverV2) return; + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; + CheckResult result = check(R"( local metatable = { __index = {w = 1} } local obj = setmetatable({x = 1, y = 2, z = 3}, metatable) @@ -533,10 +540,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_function_ignores_metatables") LUAU_REQUIRE_ERROR_COUNT(1, result); - TypePackMismatch* tpm = get(result.errors[0]); - REQUIRE(tpm); - CHECK_EQ("\"x\" | \"y\"", toString(tpm->wantedTp)); - CHECK_EQ("\"x\" | \"y\" | \"z\"", toString(tpm->givenTp)); + TypeMismatch* tm = get(result.errors[0]); + REQUIRE(tm); + CHECK_EQ("\"x\" | \"y\"", toString(tm->wantedType)); + CHECK_EQ("\"x\" | \"y\" | \"z\"", toString(tm->givenType)); } TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_function_errors_if_it_has_nontable_part") @@ -562,6 +569,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_function_common_subset_if_unio if (!FFlag::LuauSolverV2) return; + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; + CheckResult result = check(R"( type MyObject = { x: number, y: number, z: number } type MyOtherObject = { w: number, y: number, z: number } @@ -572,10 +581,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_function_common_subset_if_unio LUAU_REQUIRE_ERROR_COUNT(1, result); - TypePackMismatch* tpm = get(result.errors[0]); - REQUIRE(tpm); - CHECK_EQ("\"z\"", toString(tpm->wantedTp)); - CHECK_EQ("\"y\" | \"z\"", toString(tpm->givenTp)); + TypeMismatch* tm = get(result.errors[0]); + REQUIRE(tm); + CHECK_EQ("\"z\"", toString(tm->wantedType)); + CHECK_EQ("\"y\" | \"z\"", toString(tm->givenType)); } TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_function_never_for_empty_table") @@ -598,6 +607,8 @@ TEST_CASE_FIXTURE(ExternTypeFixture, "keyof_type_function_works_on_extern_types" if (!FFlag::LuauSolverV2) return; + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; + CheckResult result = check(R"( type KeysOfMyObject = keyof @@ -607,10 +618,10 @@ TEST_CASE_FIXTURE(ExternTypeFixture, "keyof_type_function_works_on_extern_types" LUAU_REQUIRE_ERROR_COUNT(1, result); - TypePackMismatch* tpm = get(result.errors[0]); - REQUIRE(tpm); - CHECK_EQ("\"BaseMethod\"", toString(tpm->wantedTp)); - CHECK_EQ("\"BaseField\" | \"BaseMethod\" | \"Touched\"", toString(tpm->givenTp)); + TypeMismatch* tm = get(result.errors[0]); + REQUIRE(tm); + CHECK_EQ("\"BaseMethod\"", toString(tm->wantedType)); + CHECK_EQ("\"BaseField\" | \"BaseMethod\" | \"Touched\"", toString(tm->givenType)); } TEST_CASE_FIXTURE(ExternTypeFixture, "keyof_type_function_errors_if_it_has_nonclass_part") @@ -915,8 +926,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "index_of_any_is_any") if (!FFlag::LuauSolverV2) return; - ScopedFastFlag sff{FFlag::LuauIndexAnyIsAny, true}; - CheckResult result = check(R"( type T = index )"); @@ -930,8 +939,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "index_should_not_crash_on_cyclic_stuff") if (!FFlag::LuauSolverV2) return; - ScopedFastFlag sff{FFlag::LuauFixCyclicIndexInIndexer, true}; - CheckResult result = check(R"( local PlayerData = {} @@ -951,8 +958,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "index_should_not_crash_on_cyclic_stuff2") if (!FFlag::LuauSolverV2) return; - ScopedFastFlag sff{FFlag::LuauFixCyclicIndexInIndexer, true}; - CheckResult result = check(R"( local PlayerData = {} @@ -974,8 +979,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "index_should_not_crash_on_cyclic_stuff3") if (!FFlag::LuauSolverV2) return; - ScopedFastFlag sff{FFlag::LuauFixCyclicIndexInIndexer, true}; - CheckResult result = check(R"( local PlayerData = { Coins = 0, @@ -1003,6 +1006,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_works") if (!FFlag::LuauSolverV2) return; + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; + CheckResult result = check(R"( type MyObject = {a: string, b: number, c: boolean} type IdxAType = index @@ -1015,10 +1020,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_works") LUAU_REQUIRE_ERROR_COUNT(1, result); - TypePackMismatch* tpm = get(result.errors[0]); - REQUIRE(tpm); - CHECK_EQ("boolean", toString(tpm->wantedTp)); - CHECK_EQ("string", toString(tpm->givenTp)); + TypeMismatch* tm = get(result.errors[0]); + REQUIRE(tm); + CHECK_EQ("boolean", toString(tm->wantedType)); + CHECK_EQ("string", toString(tm->givenType)); } TEST_CASE_FIXTURE(BuiltinsFixture, "index_wait_for_pending_no_crash") @@ -1064,8 +1069,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cyclic_metatable_should_not_crash_index") if (!FFlag::LuauSolverV2) return; - ScopedFastFlag sff{FFlag::LuauIndexTypeFunctionImprovements, true}; - // t :: t1 where t1 = {metatable {__index: t1, __tostring: (t1) -> string}} CheckResult result = check(R"( local mt = {} @@ -1127,11 +1130,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_works_on_function_metame if (!FFlag::LuauSolverV2) return; - ScopedFastFlag sff[]{ - {FFlag::LuauIndexTypeFunctionFunctionMetamethods, true}, - {FFlag::LuauIndexTypeFunctionImprovements, true}, - }; - CheckResult result = check(R"( type Foo = {x: string} local t = {} @@ -1155,11 +1153,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_works_on_function_metame if (!FFlag::LuauSolverV2) return; - ScopedFastFlag sff[]{ - {FFlag::LuauIndexTypeFunctionFunctionMetamethods, true}, - {FFlag::LuauIndexTypeFunctionImprovements, true}, - }; - CheckResult result = check(R"( type Foo = {x: string} local t = {} @@ -1187,9 +1180,17 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_errors_w_var_indexer") type errType1 = index )"); - LUAU_REQUIRE_ERROR_COUNT(2, result); - CHECK(toString(result.errors[0]) == "Second argument to index is not a valid index type"); - CHECK(toString(result.errors[1]) == "Unknown type 'key'"); + if (FFlag::LuauErrorSuppressionTypeFunctionArgs) + { + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK(toString(result.errors[0]) == "Unknown type 'key'"); + } + else + { + LUAU_REQUIRE_ERROR_COUNT(2, result); + CHECK(toString(result.errors[0]) == "Second argument to index is not a valid index type"); + CHECK(toString(result.errors[1]) == "Unknown type 'key'"); + } } TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_works_w_union_type_indexer") @@ -1306,6 +1307,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "rawget_type_function_works") if (!FFlag::LuauSolverV2) return; + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; + CheckResult result = check(R"( type MyObject = {a: string, b: number, c: boolean} type RawAType = rawget @@ -1317,10 +1320,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "rawget_type_function_works") LUAU_REQUIRE_ERROR_COUNT(1, result); - TypePackMismatch* tpm = get(result.errors[0]); - REQUIRE(tpm); - CHECK_EQ("boolean", toString(tpm->wantedTp)); - CHECK_EQ("string", toString(tpm->givenTp)); + TypeMismatch* tm = get(result.errors[0]); + REQUIRE(tm); + CHECK_EQ("boolean", toString(tm->wantedType)); + CHECK_EQ("string", toString(tm->givenType)); } TEST_CASE_FIXTURE(BuiltinsFixture, "rawget_type_function_works_w_array") @@ -1348,9 +1351,18 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "rawget_type_function_errors_w_var_indexer") type errType1 = rawget )"); - LUAU_REQUIRE_ERROR_COUNT(2, result); - CHECK(toString(result.errors[0]) == "Second argument to rawget is not a valid index type"); - CHECK(toString(result.errors[1]) == "Unknown type 'key'"); + + if (FFlag::LuauErrorSuppressionTypeFunctionArgs) + { + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK(toString(result.errors[0]) == "Unknown type 'key'"); + } + else + { + LUAU_REQUIRE_ERROR_COUNT(2, result); + CHECK(toString(result.errors[0]) == "Second argument to rawget is not a valid index type"); + CHECK(toString(result.errors[1]) == "Unknown type 'key'"); + } } TEST_CASE_FIXTURE(BuiltinsFixture, "rawget_type_function_works_w_union_type_indexer") @@ -1437,7 +1449,7 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_len_type_function_follow") TEST_CASE_FIXTURE(BuiltinsFixture, "setmetatable_type_function_assigns_correct_metatable") { - if (!FFlag::LuauSolverV2 || !FFlag::LuauMetatableTypeFunctions) + if (!FFlag::LuauSolverV2) return; CheckResult result = check(R"( @@ -1455,7 +1467,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "setmetatable_type_function_assigns_correct_m TEST_CASE_FIXTURE(BuiltinsFixture, "setmetatable_type_function_assigns_correct_metatable_2") { - if (!FFlag::LuauSolverV2 || !FFlag::LuauMetatableTypeFunctions) + if (!FFlag::LuauSolverV2) return; CheckResult result = check(R"( @@ -1479,7 +1491,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "setmetatable_type_function_assigns_correct_m TEST_CASE_FIXTURE(BuiltinsFixture, "setmetatable_type_function_errors_on_metatable_with_metatable_metamethod") { - if (!FFlag::LuauSolverV2 || !FFlag::LuauMetatableTypeFunctions) + if (!FFlag::LuauSolverV2) return; CheckResult result = check(R"( @@ -1499,7 +1511,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "setmetatable_type_function_errors_on_metatab TEST_CASE_FIXTURE(BuiltinsFixture, "setmetatable_type_function_errors_on_invalid_set") { - if (!FFlag::LuauSolverV2 || !FFlag::LuauMetatableTypeFunctions) + if (!FFlag::LuauSolverV2) return; CheckResult result = check(R"( @@ -1511,7 +1523,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "setmetatable_type_function_errors_on_invalid TEST_CASE_FIXTURE(BuiltinsFixture, "setmetatable_type_function_errors_on_nontable_metatable") { - if (!FFlag::LuauSolverV2 || !FFlag::LuauMetatableTypeFunctions) + if (!FFlag::LuauSolverV2) return; CheckResult result = check(R"( @@ -1523,7 +1535,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "setmetatable_type_function_errors_on_nontabl TEST_CASE_FIXTURE(BuiltinsFixture, "getmetatable_type_function_returns_nil_if_no_metatable") { - if (!FFlag::LuauSolverV2 || !FFlag::LuauMetatableTypeFunctions) + if (!FFlag::LuauSolverV2) return; CheckResult result = check(R"( @@ -1550,7 +1562,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "getmetatable_type_function_returns_nil_if_no TEST_CASE_FIXTURE(BuiltinsFixture, "getmetatable_returns_correct_metatable") { - if (!FFlag::LuauSolverV2 || !FFlag::LuauMetatableTypeFunctions) + if (!FFlag::LuauSolverV2) return; CheckResult result = check(R"( @@ -1566,7 +1578,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "getmetatable_returns_correct_metatable") TEST_CASE_FIXTURE(BuiltinsFixture, "getmetatable_returns_correct_metatable_for_union") { - if (!FFlag::LuauSolverV2 || !FFlag::LuauMetatableTypeFunctions) + if (!FFlag::LuauSolverV2) return; CheckResult result = check(R"( @@ -1591,7 +1603,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "getmetatable_returns_correct_metatable_for_u TEST_CASE_FIXTURE(BuiltinsFixture, "getmetatable_returns_correct_metatable_for_string") { - if (!FFlag::LuauSolverV2 || !FFlag::LuauMetatableTypeFunctions) + if (!FFlag::LuauSolverV2) return; CheckResult result = check(R"( @@ -1612,7 +1624,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "getmetatable_returns_correct_metatable_for_s TEST_CASE_FIXTURE(BuiltinsFixture, "getmetatable_respects_metatable_metamethod") { - if (!FFlag::LuauSolverV2 || !FFlag::LuauMetatableTypeFunctions) + if (!FFlag::LuauSolverV2) return; CheckResult result = check(R"( @@ -1631,8 +1643,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_function_correct_cycle_check") if (!FFlag::LuauSolverV2) return; - ScopedFastFlag luauNewTypeFunReductionChecks2{FFlag::LuauNewTypeFunReductionChecks2, true}; - CheckResult result = check(R"( type foo = { a: add, b : add } )"); @@ -1643,7 +1653,6 @@ type foo = { a: add, b : add } TEST_CASE_FIXTURE(BuiltinsFixture, "len_typefun_on_metatable") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag luauMetatablesHaveLength{FFlag::LuauMetatablesHaveLength, true}; CheckResult result = check(R"( local t = setmetatable({}, { __mode = "v" }) @@ -1675,6 +1684,29 @@ print(test.a) CHECK("Type 'add' does not have key 'a'" == toString(result.errors[1])); } +TEST_CASE_FIXTURE(BuiltinsFixture, "error_suppression_should_work_on_type_functions") +{ + if (!FFlag::LuauSolverV2) + return; + + ScopedFastFlag errorSuppressionTypeFunctionArgs{FFlag::LuauErrorSuppressionTypeFunctionArgs, true}; + + CheckResult result = check(R"( + local Colours = { + Red = 1, + Blue = 2, + Green = 3, + Taupe = 4, + } + + -- namespace mixup here, Colours isn't a type, it's a normal identifier + export type Colour = keyof + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK("Unknown type 'Colours'" == toString(result.errors[0])); +} + struct TFFixture { TypeArena arena_; diff --git a/tests/TypeFunction.user.test.cpp b/tests/TypeFunction.user.test.cpp index a2d9b12c..db470126 100644 --- a/tests/TypeFunction.user.test.cpp +++ b/tests/TypeFunction.user.test.cpp @@ -9,10 +9,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(DebugLuauEqSatSimplification) -LUAU_FASTFLAG(LuauTypeFunReadWriteParents) -LUAU_FASTFLAG(LuauUserTypeFunTypecheck) -LUAU_FASTFLAG(LuauNewTypeFunReductionChecks2) -LUAU_FASTFLAG(LuauNoTypeFunctionsNamedTypeOf) +LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck) TEST_SUITE_BEGIN("UserDefinedTypeFunctionTests"); @@ -352,6 +349,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_strsingleton_methods_work") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_union_serialization_works") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; CheckResult result = check(R"( type function serialize_union(arg) @@ -363,14 +361,15 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_union_serialization_works") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - TypePackMismatch* tpm = get(result.errors[0]); - REQUIRE(tpm); - CHECK(toString(tpm->givenTp) == "boolean | number | string"); + TypeMismatch* tm = get(result.errors[0]); + REQUIRE(tm); + CHECK(toString(tm->givenType) == "boolean | number | string"); } TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_optional_works") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; CheckResult result = check(R"( type function numberhuh() @@ -381,14 +380,15 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_optional_works") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - TypePackMismatch* tpm = get(result.errors[0]); - REQUIRE(tpm); - CHECK(toString(tpm->givenTp) == "number?"); + TypeMismatch* tm = get(result.errors[0]); + REQUIRE(tm); + CHECK(toString(tm->givenType) == "number?"); } TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_optional_works_on_unions") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; CheckResult result = check(R"( type function foobar() @@ -400,14 +400,15 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_optional_works_on_unions") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - TypePackMismatch* tpm = get(result.errors[0]); - REQUIRE(tpm); - CHECK(toString(tpm->givenTp) == "(boolean | number | string)?"); + TypeMismatch* tm = get(result.errors[0]); + REQUIRE(tm); + CHECK(toString(tm->givenType) == "(boolean | number | string)?"); } TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_union_methods_work") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; CheckResult result = check(R"( type function getunion() @@ -428,14 +429,15 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_union_methods_work") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - TypePackMismatch* tpm = get(result.errors[0]); - REQUIRE(tpm); - CHECK(toString(tpm->givenTp) == "boolean | number | string"); + TypeMismatch* tm = get(result.errors[0]); + REQUIRE(tm); + CHECK(toString(tm->givenType) == "boolean | number | string"); } TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_intersection_serialization_works") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; CheckResult result = check(R"( type function serialize_intersection(arg) @@ -447,14 +449,15 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_intersection_serialization_works") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - TypePackMismatch* tpm = get(result.errors[0]); - REQUIRE(tpm); - CHECK(toString(tpm->givenTp) == "{ boolean: boolean, number: number } & { boolean: boolean, string: string }"); + TypeMismatch* tm = get(result.errors[0]); + REQUIRE(tm); + CHECK(toString(tm->givenType) == "{ boolean: boolean, number: number } & { boolean: boolean, string: string }"); } TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_intersection_methods_work") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; CheckResult result = check(R"( type function getintersection() @@ -481,14 +484,15 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_intersection_methods_work") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - TypePackMismatch* tpm = get(result.errors[0]); - REQUIRE(tpm); - CHECK(toString(tpm->givenTp) == "{ boolean: boolean, number: number } & { boolean: boolean, string: string }"); + TypeMismatch* tm = get(result.errors[0]); + REQUIRE(tm); + CHECK(toString(tm->givenType) == "{ boolean: boolean, number: number } & { boolean: boolean, string: string }"); } TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_negation_methods_work") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; CheckResult result = check(R"( type function getnegation() @@ -505,9 +509,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_negation_methods_work") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - TypePackMismatch* tpm = get(result.errors[0]); - REQUIRE(tpm); - CHECK(toString(tpm->givenTp) == "~string"); + TypeMismatch* tm = get(result.errors[0]); + REQUIRE(tm); + CHECK(toString(tm->givenType) == "~string"); } TEST_CASE_FIXTURE(ExternTypeFixture, "udtf_negation_inner") @@ -537,6 +541,7 @@ local function notok(idx: fail): never return idx end TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_table_serialization_works") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; CheckResult result = check(R"( type function serialize_table(arg) @@ -548,14 +553,15 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_table_serialization_works") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - TypePackMismatch* tpm = get(result.errors[0]); - REQUIRE(tpm); - CHECK(toString(tpm->givenTp) == "{ [string]: number, boolean: boolean, number: number }"); + TypeMismatch* tm = get(result.errors[0]); + REQUIRE(tm); + CHECK(toString(tm->givenType) == "{ [string]: number, boolean: boolean, number: number }"); } TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_table_methods_work") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; CheckResult result = check(R"( type function gettable() @@ -586,14 +592,15 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_table_methods_work") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - TypePackMismatch* tpm = get(result.errors[0]); - REQUIRE(tpm); - CHECK(toString(tpm->givenTp) == "{ [boolean]: string, number: string }"); + TypeMismatch* tm = get(result.errors[0]); + REQUIRE(tm); + CHECK(toString(tm->givenType) == "{ [boolean]: string, number: string }"); } TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_metatable_methods_work") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; CheckResult result = check(R"( type function getmetatable() @@ -618,9 +625,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_metatable_methods_work") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - TypePackMismatch* tpm = get(result.errors[0]); - REQUIRE(tpm); - CHECK(toString(tpm->givenTp) == "{boolean}"); + TypeMismatch* tm = get(result.errors[0]); + REQUIRE(tm); + CHECK(toString(tm->givenType) == "{boolean}"); } TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_function_serialization_works") @@ -641,6 +648,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_function_serialization_works") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_function_methods_work") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; CheckResult result = check(R"( type function getfunction() @@ -665,9 +673,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_function_methods_work") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - TypePackMismatch* tpm = get(result.errors[0]); - REQUIRE(tpm); - CHECK(toString(tpm->givenTp) == "(string, number) -> (...boolean)"); + TypeMismatch* tm = get(result.errors[0]); + REQUIRE(tm); + CHECK(toString(tm->givenType) == "(string, number) -> (...boolean)"); } TEST_CASE_FIXTURE(ExternTypeFixture, "udtf_class_serialization_works") @@ -701,7 +709,7 @@ TEST_CASE_FIXTURE(ExternTypeFixture, "udtf_class_serialization_works2") TEST_CASE_FIXTURE(ExternTypeFixture, "udtf_class_methods_works") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; CheckResult result = check(R"( type function getclass(arg) @@ -715,14 +723,15 @@ TEST_CASE_FIXTURE(ExternTypeFixture, "udtf_class_methods_works") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - TypePackMismatch* tpm = get(result.errors[0]); - REQUIRE(tpm); - CHECK(toString(tpm->givenTp) == "{ BaseField: number, read BaseMethod: (BaseClass, number) -> (), read Touched: Connection }"); + TypeMismatch* tm = get(result.errors[0]); + REQUIRE(tm); + CHECK(toString(tm->givenType) == "{ BaseField: number, read BaseMethod: (BaseClass, number) -> (), read Touched: Connection }"); } TEST_CASE_FIXTURE(ExternTypeFixture, "write_of_readonly_is_nil") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; CheckResult result = check(R"( type function getclass(arg) @@ -741,14 +750,15 @@ TEST_CASE_FIXTURE(ExternTypeFixture, "write_of_readonly_is_nil") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - TypePackMismatch* tpm = get(result.errors[0]); - REQUIRE(tpm); - CHECK(toString(tpm->givenTp) == "false"); + TypeMismatch* tm = get(result.errors[0]); + REQUIRE(tm); + CHECK(toString(tm->givenType) == "false"); } TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_check_mutability") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; CheckResult result = check(R"( type function checkmut() @@ -772,14 +782,15 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_check_mutability") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - TypePackMismatch* tpm = get(result.errors[0]); - REQUIRE(tpm); - CHECK(toString(tpm->givenTp) == "{ @metatable {boolean}, { } }"); + TypeMismatch* tm = get(result.errors[0]); + REQUIRE(tm); + CHECK(toString(tm->givenType) == "{ @metatable {boolean}, { } }"); } TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_copy_works") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; CheckResult result = check(R"( type function getcopy() @@ -804,9 +815,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_copy_works") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - TypePackMismatch* tpm = get(result.errors[0]); - REQUIRE(tpm); - CHECK(toString(tpm->givenTp) == "{ @metatable { [number]: boolean, string: number }, { } }"); + TypeMismatch* tm = get(result.errors[0]); + REQUIRE(tm); + CHECK(toString(tm->givenType) == "{ @metatable { [number]: boolean, string: number }, { } }"); } TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_simple_cyclic_serialization_works") @@ -963,6 +974,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_function_type_cant_call_get_props") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_calling_each_other") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; CheckResult result = check(R"( type function foo() @@ -975,14 +987,15 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_calling_each_other") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - TypePackMismatch* tpm = get(result.errors[0]); - REQUIRE(tpm); - CHECK(toString(tpm->givenTp) == "\"hi\""); + TypeMismatch* tm = get(result.errors[0]); + REQUIRE(tm); + CHECK(toString(tm->givenType) == "\"hi\""); } TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_calling_each_other_2") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; CheckResult result = check(R"( type function first(arg) @@ -998,9 +1011,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_calling_each_other_2") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - TypePackMismatch* tpm = get(result.errors[0]); - REQUIRE(tpm); - CHECK(toString(tpm->givenTp) == "\"hi\""); + TypeMismatch* tm = get(result.errors[0]); + REQUIRE(tm); + CHECK(toString(tm->givenType) == "\"hi\""); } TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_calling_each_other_3") @@ -1027,22 +1040,15 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_calling_each_other_3") end )"); - if (FFlag::LuauUserTypeFunTypecheck) - { - LUAU_REQUIRE_ERROR_COUNT(5, result); - CHECK(toString(result.errors[0]) == R"(Unknown global 'fourth')"); - CHECK(toString(result.errors[1]) == R"('third' type function errored at runtime: [string "first"]:4: attempt to call a nil value)"); - } - else - { - LUAU_REQUIRE_ERROR_COUNT(4, result); - CHECK(toString(result.errors[0]) == R"('third' type function errored at runtime: [string "first"]:4: attempt to call a nil value)"); - } + LUAU_REQUIRE_ERROR_COUNT(5, result); + CHECK(toString(result.errors[0]) == R"(Unknown global 'fourth')"); + CHECK(toString(result.errors[1]) == R"('third' type function errored at runtime: [string "first"]:4: attempt to call a nil value)"); } TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_calling_each_other_unordered") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; CheckResult result = check(R"( type function bar() @@ -1055,9 +1061,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_calling_each_other_unordered") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - TypePackMismatch* tpm = get(result.errors[0]); - REQUIRE(tpm); - CHECK(toString(tpm->givenTp) == "\"hi\""); + TypeMismatch* tm = get(result.errors[0]); + REQUIRE(tm); + CHECK(toString(tm->givenType) == "\"hi\""); } TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_no_shared_state") @@ -1081,21 +1087,11 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_no_shared_state") local function ok2(idx: bar<'y'>): nil return idx end )"); - if (FFlag::LuauUserTypeFunTypecheck) - { - // We are only checking first errors, others are mostly duplicates - LUAU_REQUIRE_ERROR_COUNT(9, result); - CHECK(toString(result.errors[0]) == R"(Unknown global 'glob')"); - CHECK(toString(result.errors[1]) == R"('bar' type function errored at runtime: [string "foo"]:4: attempt to modify a readonly table)"); - CHECK(toString(result.errors[2]) == R"(Type function instance bar<"x"> is uninhabited)"); - } - else - { - // We are only checking first errors, others are mostly duplicates - LUAU_REQUIRE_ERROR_COUNT(8, result); - CHECK(toString(result.errors[0]) == R"('bar' type function errored at runtime: [string "foo"]:4: attempt to modify a readonly table)"); - CHECK(toString(result.errors[1]) == R"(Type function instance bar<"x"> is uninhabited)"); - } + // We are only checking first errors, others are mostly duplicates + LUAU_REQUIRE_ERROR_COUNT(9, result); + CHECK(toString(result.errors[0]) == R"(Unknown global 'glob')"); + CHECK(toString(result.errors[1]) == R"('bar' type function errored at runtime: [string "foo"]:4: attempt to modify a readonly table)"); + CHECK(toString(result.errors[2]) == R"(Type function instance bar<"x"> is uninhabited)"); } TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_math_reset") @@ -1115,6 +1111,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_math_reset") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_optionify") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; CheckResult result = check(R"( type function optionify(tbl) @@ -1135,9 +1132,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_optionify") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - TypePackMismatch* tpm = get(result.errors[0]); - REQUIRE(tpm); - CHECK(toString(tpm->givenTp) == "{ age: number?, alive: boolean?, name: string? }"); + TypeMismatch* tm = get(result.errors[0]); + REQUIRE(tm); + CHECK(toString(tm->givenType) == "{ age: number?, alive: boolean?, name: string? }"); } TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_calling_illegal_global") @@ -1154,28 +1151,19 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_calling_illegal_global") local function ok(idx: illegal): nil return idx end )"); - if (FFlag::LuauUserTypeFunTypecheck) - { - // We are only checking first errors, others are mostly duplicates - LUAU_REQUIRE_ERROR_COUNT(5, result); - CHECK(toString(result.errors[0]) == R"(Unknown global 'gcinfo')"); - CHECK( - toString(result.errors[1]) == - R"('illegal' type function errored at runtime: [string "illegal"]:3: this function is not supported in type functions)" - ); - } - else - { - LUAU_REQUIRE_ERROR_COUNT(4, result); // There are 2 type function uninhabited error, 2 user defined type function error - UserDefinedTypeFunctionError* e = get(result.errors[0]); - REQUIRE(e); - CHECK(e->message == "'illegal' type function errored at runtime: [string \"illegal\"]:3: this function is not supported in type functions"); - } + // We are only checking first errors, others are mostly duplicates + LUAU_REQUIRE_ERROR_COUNT(5, result); + CHECK(toString(result.errors[0]) == R"(Unknown global 'gcinfo')"); + CHECK( + toString(result.errors[1]) == + R"('illegal' type function errored at runtime: [string "illegal"]:3: this function is not supported in type functions)" + ); } TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_recursion_and_gc") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; CheckResult result = check(R"( type function foo(tbl) @@ -1193,8 +1181,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_recursion_and_gc") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - TypePackMismatch* tpm = get(result.errors[0]); - REQUIRE(tpm); + TypeMismatch* tm = get(result.errors[0]); + REQUIRE(tm); } TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_recovery_no_upvalues") @@ -1237,6 +1225,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_follow") TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_strip_indexer") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; CheckResult result = check(R"( type function stripindexer(tbl) @@ -1253,9 +1242,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_strip_indexer") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - TypePackMismatch* tpm = get(result.errors[0]); - REQUIRE(tpm); - CHECK(toString(tpm->givenTp) == "{ foo: string }"); + TypeMismatch* tm = get(result.errors[0]); + REQUIRE(tm); + CHECK(toString(tm->givenType) == "{ foo: string }"); } TEST_CASE_FIXTURE(BuiltinsFixture, "no_type_methods_on_types") @@ -1322,7 +1311,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "no_eq_field") TEST_CASE_FIXTURE(BuiltinsFixture, "tag_field") { - ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauTableLiteralSubtypeSpecificCheck, true}, + }; CheckResult result = check(R"( type function test(x) @@ -1336,21 +1328,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tag_field") LUAU_REQUIRE_ERROR_COUNT(3, result); - CHECK( - toString(result.errors[0]) == - "Type pack '\"number\"' could not be converted into 'never'; \n" - R"(this is because the 1st entry in the type pack is `"number"` in the former type and `never` in the latter type, and `"number"` is not a subtype of `never`)" - ); - CHECK( - toString(result.errors[1]) == - "Type pack '\"string\"' could not be converted into 'never'; \n" - R"(this is because the 1st entry in the type pack is `"string"` in the former type and `never` in the latter type, and `"string"` is not a subtype of `never`)" - ); - CHECK( - toString(result.errors[2]) == - "Type pack '\"table\"' could not be converted into 'never'; \n" - R"(this is because the 1st entry in the type pack is `"table"` in the former type and `never` in the latter type, and `"table"` is not a subtype of `never`)" - ); + CHECK(toString(result.errors[0]) == "Type '\"number\"' could not be converted into 'never'"); + CHECK(toString(result.errors[1]) == "Type '\"string\"' could not be converted into 'never'"); + CHECK(toString(result.errors[2]) == "Type '\"table\"' could not be converted into 'never'"); } TEST_CASE_FIXTURE(BuiltinsFixture, "metatable_serialization") @@ -1817,7 +1797,7 @@ end type test = (U...) -> (U...) -local function ok(idx: pass): (T, T) -> (T) return idx end +local function ok(idx: pass): (T, T) -> (T, T) return idx end )"); LUAU_REQUIRE_NO_ERRORS(result); @@ -2074,7 +2054,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_print_tab_char_fix") TEST_CASE_FIXTURE(ExternTypeFixture, "udtf_class_parent_ops") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag readWriteParents{FFlag::LuauTypeFunReadWriteParents, true}; CheckResult result = check(R"( type function readparentof(arg) @@ -2094,10 +2073,6 @@ TEST_CASE_FIXTURE(ExternTypeFixture, "udtf_class_parent_ops") TEST_CASE_FIXTURE(BuiltinsFixture, "typecheck_success") { - // Needs new global initialization in the Fixture, but can't place the flag inside the base Fixture - if (!FFlag::LuauUserTypeFunTypecheck) - return; - ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; CheckResult result = check(R"( @@ -2111,10 +2086,6 @@ end TEST_CASE_FIXTURE(BuiltinsFixture, "typecheck_failure") { - // Needs new global initialization in the Fixture, but can't place the flag inside the base Fixture - if (!FFlag::LuauUserTypeFunTypecheck) - return; - ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; CheckResult result = check(R"( @@ -2129,7 +2100,6 @@ end TEST_CASE_FIXTURE(BuiltinsFixture, "outer_generics_irreducible") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag luauNewTypeFunReductionChecks2{FFlag::LuauNewTypeFunReductionChecks2, true}; CheckResult result = check(R"( type function func(t) @@ -2209,8 +2179,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "irreducible_pending_expansions") if (!FFlag::LuauSolverV2) return; - ScopedFastFlag luauNewTypeFunReductionChecks2{FFlag::LuauNewTypeFunReductionChecks2, true}; - CheckResult result = check(R"( type function foo(t) return types.unionof(t, types.singleton(nil)) @@ -2230,8 +2198,6 @@ local x: wrap<{a: number}> = { a = 2 } TEST_CASE_FIXTURE(Fixture, "typeof_is_not_a_valid_type_function_name") { ScopedFastFlag _{FFlag::LuauSolverV2, true}; - ScopedFastFlag udtf{FFlag::LuauUserTypeFunTypecheck, true}; - ScopedFastFlag noTypeOfTypeFunctions{FFlag::LuauNoTypeFunctionsNamedTypeOf, true}; CheckResult result = check(R"( type function typeof(t) diff --git a/tests/TypeInfer.aliases.test.cpp b/tests/TypeInfer.aliases.test.cpp index 2e8b26dd..c6d084dc 100644 --- a/tests/TypeInfer.aliases.test.cpp +++ b/tests/TypeInfer.aliases.test.cpp @@ -10,10 +10,11 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauFixInfiniteRecursionInNormalization) LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations) LUAU_FASTFLAG(LuauNewNonStrictVisitTypes2) LUAU_FASTFLAG(LuauGuardAgainstMalformedTypeAliasExpansion) +LUAU_FASTFLAG(LuauSkipMalformedTypeAliases) +LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck) TEST_SUITE_BEGIN("TypeAliases"); @@ -210,7 +211,9 @@ TEST_CASE_FIXTURE(Fixture, "generic_aliases") { ScopedFastFlag sff[] = { {FFlag::LuauSolverV2, true}, + {FFlag::LuauTableLiteralSubtypeSpecificCheck, true}, }; + CheckResult result = check(R"( type T = { v: a } local x: T = { v = 123 } @@ -219,18 +222,15 @@ TEST_CASE_FIXTURE(Fixture, "generic_aliases") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expected = - "Type '{ v: string }' could not be converted into 'T'; \n" - "this is because accessing `v` results in `string` in the former type and `number` in the latter type, and " - "`string` is not exactly `number`"; - CHECK(result.errors[0].location == Location{{4, 31}, {4, 44}}); - CHECK_EQ(expected, toString(result.errors[0])); + CHECK(result.errors[0].location == Location{{4, 37}, {4, 42}}); + CHECK_EQ("Type 'string' could not be converted into 'number'", toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "dependent_generic_aliases") { ScopedFastFlag sff[] = { {FFlag::LuauSolverV2, true}, + {FFlag::LuauTableLiteralSubtypeSpecificCheck, true}, }; CheckResult result = check(R"( @@ -241,13 +241,8 @@ TEST_CASE_FIXTURE(Fixture, "dependent_generic_aliases") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expected = - "Type '{ t: { v: string } }' could not be converted into 'U'; \n" - "this is because accessing `t.v` results in `string` in the former type and `number` in the latter type, and `string` is not exactly " - "`number`"; - - CHECK(result.errors[0].location == Location{{4, 31}, {4, 52}}); - CHECK_EQ(expected, toString(result.errors[0])); + CHECK(result.errors[0].location == Location{{4, 43}, {4, 48}}); + CHECK_EQ("Type 'string' could not be converted into 'number'", toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "mutually_recursive_generic_aliases") @@ -1201,7 +1196,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gh1632_no_infinite_recursion_in_normalizatio { ScopedFastFlag flags[] = { {FFlag::LuauSolverV2, true}, - {FFlag::LuauFixInfiniteRecursionInNormalization, true}, }; CheckResult result = check(R"( @@ -1274,5 +1268,35 @@ TEST_CASE_FIXTURE(Fixture, "fuzzer_cursed_type_aliases") )")); } +TEST_CASE_FIXTURE(Fixture, "type_alias_dont_crash_on_bad_name") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauSkipMalformedTypeAliases, true}, + }; + + CheckResult result = check(R"( + type typeof = typeof(nil :: any) + )"); + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK(get(result.errors[0])); +} + +TEST_CASE_FIXTURE(Fixture, "type_alias_dont_crash_on_duplicate_with_typeof") +{ + // NOTE: This pattern looks quite silly, but it's pretty common in the old + // solver in: + // + // type Foo = typeof(setmetatable({} :: SomeType, {} :: SomeMetatableType)) + // + ScopedFastFlag _{FFlag::LuauSkipMalformedTypeAliases, true}; + CheckResult result = check(R"( + type A = typeof(nil :: any) + type A = typeof(nil :: any) + )"); + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK(get(result.errors[0])); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.anyerror.test.cpp b/tests/TypeInfer.anyerror.test.cpp index 8694a479..cff2e1f4 100644 --- a/tests/TypeInfer.anyerror.test.cpp +++ b/tests/TypeInfer.anyerror.test.cpp @@ -14,7 +14,8 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2); -LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions); +LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions) +LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops) TEST_SUITE_BEGIN("TypeInferAnyError"); @@ -343,6 +344,8 @@ TEST_CASE_FIXTURE(Fixture, "chain_calling_error_type_yields_error") TEST_CASE_FIXTURE(BuiltinsFixture, "replace_every_free_type_when_unifying_a_complex_function_with_any") { + ScopedFastFlag _{FFlag::LuauDfgAllowUpdatesInLoops, true}; + CheckResult result = check(R"( local a: any local b @@ -352,11 +355,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "replace_every_free_type_when_unifying_a_comp )"); LUAU_REQUIRE_NO_ERRORS(result); + CHECK_EQ("any", toString(requireType("b"))); - if (FFlag::LuauSolverV2) - CHECK_EQ("any?", toString(requireType("b"))); - else - CHECK_EQ("any", toString(requireType("b"))); } TEST_CASE_FIXTURE(Fixture, "call_to_any_yields_any") diff --git a/tests/TypeInfer.definitions.test.cpp b/tests/TypeInfer.definitions.test.cpp index 962c360a..7fbee26d 100644 --- a/tests/TypeInfer.definitions.test.cpp +++ b/tests/TypeInfer.definitions.test.cpp @@ -10,6 +10,7 @@ using namespace Luau; LUAU_FASTINT(LuauTypeInferRecursionLimit) +LUAU_FASTFLAG(LuauSimplifyOutOfLine) TEST_SUITE_BEGIN("DefinitionTests"); @@ -555,7 +556,10 @@ TEST_CASE_FIXTURE(Fixture, "recursive_redefinition_reduces_rightfully") TEST_CASE_FIXTURE(BuiltinsFixture, "cli_142285_reduce_minted_union_func") { - ScopedFastFlag _{FFlag::LuauSolverV2, true}; + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauSimplifyOutOfLine, true}, + }; CheckResult result = check(R"( local function middle(a: number, b: number): number @@ -575,7 +579,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cli_142285_reduce_minted_union_func") return nil end )"); - LUAU_REQUIRE_ERROR_COUNT(3, result); + LUAU_REQUIRE_ERROR_COUNT(2, result); // There are three errors in the above snippet, but they should all be where // clause needed errors. for (const auto& e : result.errors) diff --git a/tests/TypeInfer.functions.test.cpp b/tests/TypeInfer.functions.test.cpp index 128b9b4f..9ba1a055 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -22,13 +22,14 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTINT(LuauTarjanChildLimit) LUAU_FASTFLAG(DebugLuauEqSatSimplification) -LUAU_FASTFLAG(LuauUngeneralizedTypesForRecursiveFunctions) LUAU_FASTFLAG(DebugLuauGreedyGeneralization) LUAU_FASTFLAG(LuauArityMismatchOnUndersaturatedUnknownArguments) LUAU_FASTFLAG(LuauHasPropProperBlock) LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect) LUAU_FASTFLAG(LuauFormatUseLastPosition) LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType) +LUAU_FASTFLAG(LuauSimplifyOutOfLine) +LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck) TEST_SUITE_BEGIN("TypeInferFunctions"); @@ -77,6 +78,8 @@ TEST_CASE_FIXTURE(Fixture, "tc_function") TEST_CASE_FIXTURE(Fixture, "check_function_bodies") { + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; + CheckResult result = check(R"( function myFunction(): number local a = 0 @@ -89,10 +92,10 @@ TEST_CASE_FIXTURE(Fixture, "check_function_bodies") if (FFlag::LuauSolverV2) { - const TypePackMismatch* tm = get(result.errors[0]); + const TypeMismatch* tm = get(result.errors[0]); REQUIRE_MESSAGE(tm, "Expected TypeMismatch but got " << result.errors[0]); - CHECK(toString(tm->wantedTp) == "number"); - CHECK(toString(tm->givenTp) == "boolean"); + CHECK(toString(tm->wantedType) == "number"); + CHECK(toString(tm->givenType) == "boolean"); } else { @@ -1485,6 +1488,8 @@ local a: TableWithFunc = { x = 3, y = 4, f = function(a, b) return a + b end } TEST_CASE_FIXTURE(Fixture, "infer_return_value_type") { + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; + CheckResult result = check(R"( local function f(): {string|number} return {1, "b", 3} @@ -1503,11 +1508,7 @@ local function i(): ...{string|number} end )"); - // `h` regresses in the new solver, the return type is not being pushed into the body. - if (FFlag::LuauSolverV2) - LUAU_REQUIRE_ERROR_COUNT(1, result); - else - LUAU_REQUIRE_NO_ERRORS(result); + LUAU_REQUIRE_NO_ERRORS(result); } TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_arg_count") @@ -1693,7 +1694,14 @@ t.f = function(x) end )"); - if (FFlag::LuauSolverV2) + if (FFlag::DebugLuauGreedyGeneralization && FFlag::LuauSolverV2) + { + // FIXME CLI-151985 + LUAU_CHECK_ERROR_COUNT(3, result); + LUAU_CHECK_ERROR(result, ConstraintSolvingIncompleteError); + LUAU_CHECK_ERROR(result, WhereClauseNeeded); // x2 + } + else if (FFlag::LuauSolverV2) { LUAU_REQUIRE_ERROR_COUNT(2, result); CHECK_EQ( @@ -1771,7 +1779,14 @@ t.f = function(x) end )"); - if (FFlag::LuauSolverV2) + if (FFlag::DebugLuauGreedyGeneralization && FFlag::LuauSolverV2) + { + // FIXME CLI-151985 + LUAU_CHECK_ERROR_COUNT(2, result); + LUAU_CHECK_ERROR(result, ConstraintSolvingIncompleteError); + LUAU_CHECK_ERROR(result, WhereClauseNeeded); + } + else if (FFlag::LuauSolverV2) { LUAU_REQUIRE_ERROR_COUNT(1, result); CHECK_EQ( @@ -2422,6 +2437,8 @@ TEST_CASE_FIXTURE(Fixture, "generic_packs_are_not_variadic") TEST_CASE_FIXTURE(BuiltinsFixture, "num_is_solved_before_num_or_str") { + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; + CheckResult result = check(R"( function num() return 5 @@ -2438,20 +2455,14 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "num_is_solved_before_num_or_str") LUAU_REQUIRE_ERROR_COUNT(1, result); - if (FFlag::LuauSolverV2) - CHECK( - "Type pack 'string' could not be converted into 'number'; \n" - "this is because the 1st entry in the type pack is `string` in the former type and `number` in the latter type, and `string` is not a " - "subtype of `number`" == toString(result.errors.at(0)) - ); - else - CHECK_EQ("Type 'string' could not be converted into 'number'", toString(result.errors[0])); - + CHECK_EQ("Type 'string' could not be converted into 'number'", toString(result.errors[0])); CHECK_EQ("() -> number", toString(requireType("num_or_str"))); } TEST_CASE_FIXTURE(BuiltinsFixture, "num_is_solved_after_num_or_str") { + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; + CheckResult result = check(R"( local function num_or_str() if math.random() > 0.5 then @@ -2467,14 +2478,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "num_is_solved_after_num_or_str") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - if (FFlag::LuauSolverV2) - CHECK( - "Type pack 'string' could not be converted into 'number'; \n" - "this is because the 1st entry in the type pack is `string` in the former type and `number` in the latter type, and `string` is not a " - "subtype of `number`" == toString(result.errors.at(0)) - ); - else - CHECK_EQ("Type 'string' could not be converted into 'number'", toString(result.errors[0])); + CHECK_EQ("Type 'string' could not be converted into 'number'", toString(result.errors[0])); CHECK_EQ("() -> number", toString(requireType("num_or_str"))); } @@ -2914,7 +2918,11 @@ TEST_CASE_FIXTURE(Fixture, "fuzzer_missing_follow_in_ast_stat_fun") TEST_CASE_FIXTURE(Fixture, "unifier_should_not_bind_free_types") { - ScopedFastFlag _{FFlag::LuauOptimizeFalsyAndTruthyIntersect, true}; + ScopedFastFlag sffs[] = { + {FFlag::LuauSimplifyOutOfLine, true}, + {FFlag::LuauTableLiteralSubtypeSpecificCheck, true}, + {FFlag::LuauOptimizeFalsyAndTruthyIntersect, true}, + }; CheckResult result = check(R"( function foo(player) @@ -2933,21 +2941,35 @@ TEST_CASE_FIXTURE(Fixture, "unifier_should_not_bind_free_types") { // The new solver should ideally be able to do better here, but this is no worse than the old solver. - LUAU_REQUIRE_ERROR_COUNT(2, result); - - auto tm1 = get(result.errors[0]); - REQUIRE(tm1); - CHECK(toString(tm1->wantedTp) == "string"); - CHECK(toString(tm1->givenTp) == "boolean"); - - auto tm2 = get(result.errors[1]); - REQUIRE(tm2); - CHECK(toString(tm2->wantedTp) == "string"); - if (FFlag::DebugLuauGreedyGeneralization) - CHECK(toString(tm2->givenTp) == "unknown & ~(false?)"); + { + LUAU_REQUIRE_ERROR_COUNT(2, result); + auto tm1 = get(result.errors[0]); + REQUIRE(tm1); + CHECK(toString(tm1->wantedType) == "string"); + CHECK(toString(tm1->givenType) == "boolean"); + auto tm2 = get(result.errors[1]); + REQUIRE(tm2); + CHECK(toString(tm2->wantedType) == "string"); + CHECK(toString(tm2->givenType) == "unknown & ~(false?)"); + } else - CHECK(toString(tm2->givenTp) == "~(false?)"); + { + // Unfortunately, this example forces constraints a _ton_, meaning + // that we can easily end up being unable to solve constraints. This + // gets better with greedy generalization. + LUAU_REQUIRE_ERROR_COUNT(3, result); + + auto tm1 = get(result.errors[1]); + REQUIRE(tm1); + CHECK(toString(tm1->wantedType) == "string"); + CHECK(toString(tm1->givenType) == "boolean"); + + auto tm2 = get(result.errors[2]); + REQUIRE(tm2); + CHECK(toString(tm2->wantedType) == "string"); + CHECK(toString(tm2->givenType) == "~(false?)"); + } } else { @@ -3085,7 +3107,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "coroutine_wrap_result_call") TEST_CASE_FIXTURE(Fixture, "recursive_function_calls_should_not_use_the_generalized_type") { ScopedFastFlag crashOnForce{FFlag::DebugLuauAssertOnForcedConstraint, true}; - ScopedFastFlag sff{FFlag::LuauUngeneralizedTypesForRecursiveFunctions, true}; CheckResult result = check(R"( --!strict diff --git a/tests/TypeInfer.generics.test.cpp b/tests/TypeInfer.generics.test.cpp index e90a33a2..d899201c 100644 --- a/tests/TypeInfer.generics.test.cpp +++ b/tests/TypeInfer.generics.test.cpp @@ -13,7 +13,8 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions) -LUAU_FASTFLAG(LuauIntersectNotNil) +LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2) +LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck) using namespace Luau; @@ -834,19 +835,13 @@ function clone(dict: {[X]:Y}): {[X]:Y} end )"); - if (FFlag::LuauSolverV2 && FFlag::LuauAddCallConstraintForIterableFunctions && !FFlag::LuauIntersectNotNil) - { - LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK(get(result.errors.at(0))); - } - else - { - LUAU_REQUIRE_NO_ERRORS(result); - } + LUAU_REQUIRE_NO_ERRORS(result); } TEST_CASE_FIXTURE(Fixture, "generic_functions_should_be_memory_safe") { + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; + CheckResult result = check(R"( --!strict -- At one point this produced a UAF @@ -860,17 +855,17 @@ y.a.c = y if (FFlag::LuauSolverV2) { - LUAU_REQUIRE_ERROR_COUNT(1, result); - auto mismatch = get(result.errors.at(0)); - CHECK(mismatch); - CHECK_EQ(toString(mismatch->givenType), "{ a: { c: T?, d: number }, b: number }"); - CHECK_EQ(toString(mismatch->wantedType), "T"); - std::string reason = - "\nthis is because \n\t" - " * accessing `a.d` results in `number` in the former type and `string` in the latter type, and `number` is not exactly " - "`string`\n\t" - " * accessing `b` results in `number` in the former type and `string` in the latter type, and `number` is not exactly `string`"; - CHECK_EQ(mismatch->reason, reason); + LUAU_REQUIRE_ERROR_COUNT(2, result); + auto mismatch1 = get(result.errors[0]); + auto mismatch2 = get(result.errors[1]); + REQUIRE(mismatch1); + REQUIRE(mismatch2); + CHECK_EQ(result.errors[0].location, Location{{7, 42}, {7, 43}}); + CHECK_EQ(toString(mismatch1->givenType), "number"); + CHECK_EQ(toString(mismatch1->wantedType), "string"); + CHECK_EQ(result.errors[1].location, Location{{7, 51}, {7, 53}}); + CHECK_EQ(toString(mismatch2->givenType), "number"); + CHECK_EQ(toString(mismatch2->wantedType), "string"); } else { @@ -888,8 +883,6 @@ Type 'number' could not be converted into 'string' in an invariant context)"; TEST_CASE_FIXTURE(Fixture, "generic_type_pack_unification1") { - DOES_NOT_PASS_NEW_SOLVER_GUARD(); - CheckResult result = check(R"( --!strict type Dispatcher = { @@ -908,8 +901,6 @@ local TheDispatcher: Dispatcher = { TEST_CASE_FIXTURE(Fixture, "generic_type_pack_unification2") { - DOES_NOT_PASS_NEW_SOLVER_GUARD(); - CheckResult result = check(R"( --!strict type Dispatcher = { @@ -928,8 +919,6 @@ local TheDispatcher: Dispatcher = { TEST_CASE_FIXTURE(Fixture, "generic_type_pack_unification3") { - DOES_NOT_PASS_NEW_SOLVER_GUARD(); - CheckResult result = check(R"( --!strict type Dispatcher = { @@ -948,7 +937,7 @@ local TheDispatcher: Dispatcher = { TEST_CASE_FIXTURE(Fixture, "generic_argument_count_too_few") { - DOES_NOT_PASS_NEW_SOLVER_GUARD(); + ScopedFastFlag sff{FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2, true}; CheckResult result = check(R"( function test(a: number) @@ -962,12 +951,22 @@ wrapper(test) )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), R"(Argument count mismatch. Function 'wrapper' expects 2 arguments, but only 1 is specified)"); + if (FFlag::LuauSolverV2) + { + const CountMismatch* cm = get(result.errors[0]); + REQUIRE_MESSAGE(cm, "Expected CountMismatch but got " << result.errors[0]); + // TODO: CLI-152070 fix to expect 2 + CHECK_EQ(cm->expected, 1); + CHECK_EQ(cm->actual, 1); + CHECK_EQ(cm->context, CountMismatch::Arg); + } + else + CHECK_EQ(toString(result.errors[0]), R"(Argument count mismatch. Function 'wrapper' expects 2 arguments, but only 1 is specified)"); } TEST_CASE_FIXTURE(Fixture, "generic_argument_count_too_many") { - DOES_NOT_PASS_NEW_SOLVER_GUARD(); + ScopedFastFlag sff{FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2, true}; CheckResult result = check(R"( function test2(a: number, b: string) @@ -981,7 +980,76 @@ wrapper(test2, 1, "", 3) )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), R"(Argument count mismatch. Function 'wrapper' expects 3 arguments, but 4 are specified)"); + if (FFlag::LuauSolverV2) + { + const CountMismatch* cm = get(result.errors[0]); + REQUIRE_MESSAGE(cm, "Expected CountMismatch but got " << result.errors[0]); + // TODO: CLI-152070 fix to expect 3 + CHECK_EQ(cm->expected, 1); + CHECK_EQ(cm->actual, 4); + CHECK_EQ(cm->context, CountMismatch::Arg); + } + else + CHECK_EQ(toString(result.errors[0]), R"(Argument count mismatch. Function 'wrapper' expects 3 arguments, but 4 are specified)"); +} + +TEST_CASE_FIXTURE(Fixture, "generic_argument_count_just_right") +{ + CheckResult result = check(R"( +function test2(a: number, b: string) + return 1 +end + +function wrapper(f: (A...) -> number, ...: A...) +end + +wrapper(test2, 1, "") + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(Fixture, "generic_argument_pack_type_inferred_from_return") +{ + CheckResult result = check(R"( +function test2(a: number) + return "hello" +end + +function wrapper(f: (number) -> A..., ...: A...) +end + +wrapper(test2, 1) + )"); + + if (FFlag::LuauSolverV2) + { + // TODO: CLI-152070 should expect a TypeMismatch, rather than not erroring + LUAU_REQUIRE_NO_ERRORS(result); + } + else + { + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK_EQ(toString(result.errors[0]), R"(Type 'number' could not be converted into 'string')"); + } +} + +TEST_CASE_FIXTURE(Fixture, "generic_argument_pack_type_inferred_from_return_no_error") +{ + ScopedFastFlag _{FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2, true}; + + CheckResult result = check(R"( +function test2(a: number) + return "hello" +end + +function wrapper(f: (number) -> A..., ...: A...) +end + +wrapper(test2, "hello") + )"); + + LUAU_REQUIRE_NO_ERRORS(result); } TEST_CASE_FIXTURE(Fixture, "generic_function") @@ -1607,7 +1675,7 @@ type Dispatch = (A) -> () type BasicStateAction = ((S) -> S) | S function updateReducer(reducer: (S, A) -> S, initialArg: I, init: ((I) -> S)?): (S, Dispatch) - return 1 :: any + return 1 :: any, 2 :: any end function basicStateReducer(state: S, action: BasicStateAction): S diff --git a/tests/TypeInfer.intersectionTypes.test.cpp b/tests/TypeInfer.intersectionTypes.test.cpp index adbe972f..41753dcb 100644 --- a/tests/TypeInfer.intersectionTypes.test.cpp +++ b/tests/TypeInfer.intersectionTypes.test.cpp @@ -11,6 +11,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauNarrowIntersectionNevers) +LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck) TEST_SUITE_BEGIN("IntersectionTypes"); @@ -455,6 +456,8 @@ Type 'number' could not be converted into 'X')"; TEST_CASE_FIXTURE(Fixture, "error_detailed_intersection_all") { + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; + CheckResult result = check(R"( type X = { x: number } type Y = { y: number } @@ -470,18 +473,14 @@ end if (FFlag::LuauSolverV2) { - const std::string expected = - "Type pack " - "'X & Y & Z'" - " could not be converted into " - "'number'; \n" - "this is because \n\t" - " * in the 1st entry in the type pack has the 1st component of the intersection as `X` and the 1st entry in the " - "type pack is `number`, and `X` is not a subtype of `number`\n\t" - " * in the 1st entry in the type pack has the 2nd component of the intersection as `Y` and the 1st entry in the " - "type pack is `number`, and `Y` is not a subtype of `number`\n\t" - " * in the 1st entry in the type pack has the 3rd component of the intersection as `Z` and the 1st entry in the " - "type pack is `number`, and `Z` is not a subtype of `number`"; + const std::string expected = "Type " + "'X & Y & Z'" + " could not be converted into " + "'number'; \n" + "this is because \n\t" + " * the 1st component of the intersection is `X`, which is not a subtype of `number`\n\t" + " * the 2nd component of the intersection is `Y`, which is not a subtype of `number`\n\t" + " * the 3rd component of the intersection is `Z`, which is not a subtype of `number`"; CHECK_EQ(expected, toString(result.errors[0])); } else diff --git a/tests/TypeInfer.loops.test.cpp b/tests/TypeInfer.loops.test.cpp index 6a7e150f..b9cfc056 100644 --- a/tests/TypeInfer.loops.test.cpp +++ b/tests/TypeInfer.loops.test.cpp @@ -15,8 +15,10 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauStatForInFix) LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions) +LUAU_FASTFLAG(LuauSimplifyOutOfLine) +LUAU_FASTFLAG(LuauDfgIfBlocksShouldRespectControlFlow) +LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops) TEST_SUITE_BEGIN("TypeInferLoops"); @@ -184,7 +186,12 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop_with_next") } TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop_with_next_and_multiple_elements") { - ScopedFastFlag _{FFlag::LuauAddCallConstraintForIterableFunctions, true}; + ScopedFastFlag sffs[] = { + {FFlag::LuauAddCallConstraintForIterableFunctions, true}, + {FFlag::LuauSimplifyOutOfLine, true}, + {FFlag::LuauDfgAllowUpdatesInLoops, true}, + }; + CheckResult result = check(R"( local n local s @@ -200,10 +207,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop_with_next_and_multiple_elements" if (FFlag::LuauSolverV2) { CHECK("number?" == toString(requireType("n"))); - // TODO: CLI-150066 fix these redundant unions - CHECK("(string | string)?" == toString(requireType("s"))); + CHECK("string?" == toString(requireType("s"))); CHECK_EQ("number", toString(requireTypeAtPosition({6, 18}))); - CHECK_EQ("string | string", toString(requireTypeAtPosition({6, 21}))); + CHECK_EQ("string", toString(requireTypeAtPosition({6, 21}))); } else { @@ -1111,6 +1117,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "dcr_iteration_on_never_gives_never") if (!FFlag::LuauSolverV2) return; + ScopedFastFlag _{FFlag::LuauDfgAllowUpdatesInLoops, true}; + CheckResult result = check(R"( local iter: never local ans @@ -1122,7 +1130,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "dcr_iteration_on_never_gives_never") LUAU_REQUIRE_NO_ERRORS(result); if (FFlag::LuauSolverV2) - CHECK("never?" == toString(requireType("ans"))); // CLI-114134 egraph simplification. Should just be nil. + CHECK("nil" == toString(requireType("ans"))); else CHECK(toString(requireType("ans")) == "never"); } @@ -1310,8 +1318,6 @@ end TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_surprising_iterator") { - ScopedFastFlag luauStatForInFix{FFlag::LuauStatForInFix, true}; - CheckResult result = check(R"( function broken(): (...() -> ()) return function() end, function() end @@ -1333,4 +1339,210 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_require") )")); } +TEST_CASE_FIXTURE(Fixture, "oss_1480") +{ + ScopedFastFlag _{FFlag::LuauDfgAllowUpdatesInLoops, true}; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + type Part = { Parent: Part? } + type Instance = Part + + local part = {} :: Part + + local currentParent: Instance? = part.Parent + while currentParent ~= nil do + currentParent = currentParent.Parent + end + )")); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1413") +{ + ScopedFastFlag _{FFlag::LuauDfgAllowUpdatesInLoops, true}; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + local function KahanSum(values: {number}): number + local sum: number = 0 + local compensator: number = 0 + for _, value in values do + local y = value - compensator + local t = sum + y + compensator = (t - sum) - y + sum = t + end + return sum + end + )")); + + LUAU_REQUIRE_NO_ERRORS(check(R"( + local function HistogramString(values: {number}) + local histogram = {} + values = table.clone(values) + table.sort(values) + + local count = #values + local range = (count - 1) + + local digitIndex = range // 2 + 1 + while digitIndex < count and values[digitIndex] == 0 do + digitIndex = count - ((count - digitIndex) // 2) + end + end + )")); + + LUAU_REQUIRE_NO_ERRORS(check(R"( + local function fun1() + local foo = 1 + local bar = foo - foo + foo + while false do + foo = bar + end + end + local function fun2() + local foo = 1 + while false do + local bar = foo - foo + foo + foo = bar + end + end + )")); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "while_loop_error_in_body") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauDfgAllowUpdatesInLoops, true}, + }; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + local function foo() + local x = "" + while math.random () > 0.5 do + x = nil + error("why did you make x nil tho") + end + return x + end + )")); + + CHECK_EQ("() -> string", toString(requireType("foo"))); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "while_loop_assign_different_type") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauDfgAllowUpdatesInLoops, true}, + }; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + local function takesString(_: string) end + local function takesNil(_: nil) end + local function foo() + local x = "" + takesString(x) + while math.random () > 0.5 do + x = nil + takesNil(x) + end + return x + end + )")); + + CHECK_EQ("() -> string?", toString(requireType("foo"))); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "repeat_loop_assignment") +{ + ScopedFastFlag _{FFlag::LuauDfgAllowUpdatesInLoops, true}; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + local x = nil + repeat + x = 42 + until math.random() > 0.5 + local y = x + )")); + + CHECK_EQ("number", toString(requireType("y"))); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "repeat_loop_assignment_with_break") +{ + ScopedFastFlag _{FFlag::LuauDfgAllowUpdatesInLoops, true}; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + local x = nil + repeat + x = 42 + until math.random() > 0.5 + local y = x + )")); + + CHECK_EQ("number", toString(requireType("y"))); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "repeat_unconditionally_fires_error") +{ + ScopedFastFlag _{FFlag::LuauDfgAllowUpdatesInLoops, true}; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + local x = nil + repeat + x = 42 + until true + -- `x` should unconditionally be `number` here as the assignment + -- above will _always_ run. + local y = x + )")); + + CHECK_EQ("number", toString(requireType("y"))); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "repeat_is_linearish") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauDfgIfBlocksShouldRespectControlFlow, true}, + {FFlag::LuauDfgAllowUpdatesInLoops, true}, + }; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + local x = nil + if math.random () > 0.5 then + x = "" + repeat + error("spooky scary error") + until true + end + -- The repeat in the above branch unconditionally fires the error, so + -- this should _always_ be `nil` + local y = x + )")); + + CHECK_EQ("nil", toString(requireType("y"))); + +} + +TEST_CASE_FIXTURE(Fixture, "ensure_local_in_loop_does_not_escape") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauDfgAllowUpdatesInLoops, true}, + }; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + local x = 42 + repeat + local x = "" + until true + -- The local inside the loop should have no effect on the local + -- outside the loop. + local y = x + )")); + + CHECK_EQ("number", toString(requireType("y"))); + +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.modules.test.cpp b/tests/TypeInfer.modules.test.cpp index 4f15091b..55dd27ed 100644 --- a/tests/TypeInfer.modules.test.cpp +++ b/tests/TypeInfer.modules.test.cpp @@ -15,6 +15,7 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions) LUAU_FASTFLAG(DebugLuauGreedyGeneralization) LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect) +LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2) using namespace Luau; @@ -831,4 +832,30 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cycles_dont_make_everything_any") CHECK("module" == toString(frontend.moduleResolver.getModule("game/B")->returnType)); } +TEST_CASE_FIXTURE(BuiltinsFixture, "cross_module_function_mutation") +{ + ScopedFastFlag _[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2, true}}; + + fileResolver.source["game/A"] = R"( +function test2(a: number, b: string) + return 1 +end + +return test2 + )"; + + fileResolver.source["game/B"] = R"( +function wrapper(f: (A...) -> number, ...: A...) +end + +local test2 = require(game.A) + +return wrapper(test2, 1, "") + )"; + + CheckResult result = frontend.check("game/B"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.operators.test.cpp b/tests/TypeInfer.operators.test.cpp index 837d4218..61fe9dc9 100644 --- a/tests/TypeInfer.operators.test.cpp +++ b/tests/TypeInfer.operators.test.cpp @@ -55,7 +55,7 @@ TEST_CASE_FIXTURE(Fixture, "or_joins_types_with_no_extras") { // FIXME: Regression. CHECK("(string & ~(false?)) | number" == toString(*requireType("s"))); - CHECK("number | string | string" == toString(*requireType("y"))); + CHECK("number | string" == toString(*requireType("y"))); } else { diff --git a/tests/TypeInfer.provisional.test.cpp b/tests/TypeInfer.provisional.test.cpp index b31476f9..b6cc4493 100644 --- a/tests/TypeInfer.provisional.test.cpp +++ b/tests/TypeInfer.provisional.test.cpp @@ -13,12 +13,12 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(DebugLuauEqSatSimplification) -LUAU_FASTFLAG(LuauStoreCSTData2) LUAU_FASTINT(LuauNormalizeCacheLimit) LUAU_FASTINT(LuauTarjanChildLimit) LUAU_FASTINT(LuauTypeInferIterationLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferTypePackLoopLimit) +LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops) TEST_SUITE_BEGIN("ProvisionalTests"); @@ -48,7 +48,7 @@ TEST_CASE_FIXTURE(Fixture, "typeguard_inference_incomplete") end )"; - const std::string expected = FFlag::LuauStoreCSTData2 ? R"( + const std::string expected = R"( function f(a:{fn:()->(a,b...)}): () if type(a) == 'boolean' then local a1:boolean=a @@ -56,18 +56,9 @@ TEST_CASE_FIXTURE(Fixture, "typeguard_inference_incomplete") local a2:{fn:()->(a,b...)}=a end end - )" - : R"( - function f(a:{fn:()->(a,b...)}): () - if type(a) == 'boolean'then - local a1:boolean=a - elseif a.fn()then - local a2:{fn:()->(a,b...)}=a - end - end )"; - const std::string expectedWithNewSolver = FFlag::LuauStoreCSTData2 ? R"( + const std::string expectedWithNewSolver = R"( function f(a:{fn:()->(unknown,...unknown)}): () if type(a) == 'boolean' then local a1:{fn:()->(unknown,...unknown)}&boolean=a @@ -75,18 +66,9 @@ TEST_CASE_FIXTURE(Fixture, "typeguard_inference_incomplete") local a2:{fn:()->(unknown,...unknown)}&(class|function|nil|number|string|thread|buffer|table)=a end end - )" - : R"( - function f(a:{fn:()->(unknown,...unknown)}): () - if type(a) == 'boolean'then - local a1:{fn:()->(unknown,...unknown)}&boolean=a - elseif a.fn()then - local a2:{fn:()->(unknown,...unknown)}&(class|function|nil|number|string|thread|buffer|table)=a - end - end )"; - const std::string expectedWithEqSat = FFlag::LuauStoreCSTData2 ? R"( + const std::string expectedWithEqSat = R"( function f(a:{fn:()->(unknown,...unknown)}): () if type(a) == 'boolean' then local a1:{fn:()->(unknown,...unknown)}&boolean=a @@ -94,15 +76,6 @@ TEST_CASE_FIXTURE(Fixture, "typeguard_inference_incomplete") local a2:{fn:()->(unknown,...unknown)}&negate=a end end - )" - : R"( - function f(a:{fn:()->(unknown,...unknown)}): () - if type(a) == 'boolean'then - local a1:{fn:()->(unknown,...unknown)}&boolean=a - elseif a.fn()then - local a2:{fn:()->(unknown,...unknown)}&negate=a - end - end )"; if (FFlag::LuauSolverV2 && !FFlag::DebugLuauEqSatSimplification) @@ -1358,4 +1331,21 @@ TEST_CASE_FIXTURE(Fixture, "we_cannot_infer_functions_that_return_inconsistently #endif } +TEST_CASE_FIXTURE(Fixture, "loop_unsoundness") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauDfgAllowUpdatesInLoops, true}, + }; + // This is a tactical unsoundness we're introducing to resolve issues around + // cyclic types. You can see that if this loop were to run more than once, + // we'd error as we'd try to call a number. + LUAU_REQUIRE_NO_ERRORS(check(R"( + local f = function () return 42 end + while true do + f = f() + end + )")); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.refinements.test.cpp b/tests/TypeInfer.refinements.test.cpp index a1b04daa..065f243a 100644 --- a/tests/TypeInfer.refinements.test.cpp +++ b/tests/TypeInfer.refinements.test.cpp @@ -10,10 +10,8 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(DebugLuauEqSatSimplification) -LUAU_FASTFLAG(LuauIntersectNotNil) LUAU_FASTFLAG(DebugLuauGreedyGeneralization) LUAU_FASTFLAG(LuauFunctionCallsAreNotNilable) -LUAU_FASTFLAG(LuauSimplyRefineNotNil) LUAU_FASTFLAG(LuauWeakNilRefinementType) LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions) LUAU_FASTFLAG(LuauSimplificationTableExternType) @@ -741,7 +739,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_narrow_to_vector") TEST_CASE_FIXTURE(BuiltinsFixture, "nonoptional_type_can_narrow_to_nil_if_sense_is_true") { ScopedFastFlag sffs[] = { - {FFlag::LuauSimplyRefineNotNil, true}, {FFlag::LuauWeakNilRefinementType, true}, }; @@ -2515,8 +2512,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "remove_recursive_upper_bound_when_generalizi TEST_CASE_FIXTURE(BuiltinsFixture, "nonnil_refinement_on_generic") { - ScopedFastFlag sff{FFlag::LuauIntersectNotNil, true}; - CheckResult result = check(R"( local function printOptional(item: T?, printer: (T) -> string): string if item ~= nil then @@ -2536,8 +2531,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "nonnil_refinement_on_generic") TEST_CASE_FIXTURE(BuiltinsFixture, "truthy_refinement_on_generic") { - ScopedFastFlag sff{FFlag::LuauIntersectNotNil, true}; - CheckResult result = check(R"( local function printOptional(item: T?, printer: (T) -> string): string if item then diff --git a/tests/TypeInfer.singletons.test.cpp b/tests/TypeInfer.singletons.test.cpp index 32c522e0..f6d97217 100644 --- a/tests/TypeInfer.singletons.test.cpp +++ b/tests/TypeInfer.singletons.test.cpp @@ -7,7 +7,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauPropagateExpectedTypesForCalls) +LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck) TEST_SUITE_BEGIN("TypeSingletons"); @@ -154,8 +154,6 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_function_call_with_singletons") TEST_CASE_FIXTURE(Fixture, "overloaded_function_resolution_singleton_parameters") { - ScopedFastFlag sff{FFlag::LuauPropagateExpectedTypesForCalls, true}; - CheckResult result = check(R"( type A = ("A") -> string type B = ("B") -> number @@ -378,6 +376,11 @@ TEST_CASE_FIXTURE(Fixture, "indexer_can_be_union_of_singletons") TEST_CASE_FIXTURE(Fixture, "table_properties_type_error_escapes") { + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauTableLiteralSubtypeSpecificCheck, true}, + }; + CheckResult result = check(R"( --!strict local x: { ["<>"] : number } @@ -386,23 +389,14 @@ TEST_CASE_FIXTURE(Fixture, "table_properties_type_error_escapes") LUAU_REQUIRE_ERROR_COUNT(1, result); - if (FFlag::LuauSolverV2) - { - const std::string expected = "Type\n\t" - "'{ [\"\\n\"]: number }'" - "\ncould not be converted into\n\t" - "'{ [\"<>\"]: number }'"; - CHECK(expected == toString(result.errors[0])); - } - else - CHECK_EQ( - R"(Table type '{ ["\n"]: number }' not compatible with type '{| ["<>"]: number |}' because the former is missing field '<>')", - toString(result.errors[0]) - ); + const std::string expected = R"(Table type '{ ["\n"]: number }' not compatible with type '{ ["<>"]: number }' because the former is missing field '<>')"; + CHECK(expected == toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "error_detailed_tagged_union_mismatch_string") { + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; + CheckResult result = check(R"( type Cat = { tag: 'cat', catfood: string } type Dog = { tag: 'dog', dogfood: string } @@ -413,7 +407,9 @@ local a: Animal = { tag = 'cat', cafood = 'something' } LUAU_REQUIRE_ERROR_COUNT(1, result); if (FFlag::LuauSolverV2) - CHECK("Type '{ cafood: string, tag: \"cat\" }' could not be converted into 'Cat | Dog'" == toString(result.errors[0])); + // NOTE: This error is not great, it might be more helpful to indicate + // that tag _could_ have type 'cat'. + CHECK("Type '{ cafood: string, tag: string }' could not be converted into 'Cat | Dog'" == toString(result.errors[0])); else { const std::string expected = R"(Type 'a' could not be converted into 'Cat | Dog' diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index 446ad606..1e7521df 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -22,12 +22,14 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering) LUAU_FASTFLAG(DebugLuauGreedyGeneralization) -LUAU_FASTFLAG(LuauNonReentrantGeneralization2) +LUAU_FASTFLAG(LuauNonReentrantGeneralization3) LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint) LUAU_FASTFLAG(LuauBidirectionalInferenceElideAssert) LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect) LUAU_FASTFLAG(LuauTypeCheckerStricterIndexCheck) LUAU_FASTFLAG(LuauReportSubtypingErrors) +LUAU_FASTFLAG(LuauSimplifyOutOfLine) +LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck) TEST_SUITE_BEGIN("TableTests"); @@ -698,7 +700,7 @@ TEST_CASE_FIXTURE(Fixture, "indexers_get_quantified_too") LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::LuauSolverV2 && FFlag::LuauNonReentrantGeneralization2) + if (FFlag::LuauSolverV2 && FFlag::LuauNonReentrantGeneralization3) CHECK("({a}) -> ()" == toString(requireType("swap"))); else if (FFlag::LuauSolverV2) CHECK("({unknown}) -> ()" == toString(requireType("swap"))); @@ -758,6 +760,8 @@ TEST_CASE_FIXTURE(Fixture, "indexers_quantification_2") TEST_CASE_FIXTURE(Fixture, "infer_indexer_from_array_like_table") { + ScopedFastFlag _{FFlag::LuauSimplifyOutOfLine, true}; + CheckResult result = check(R"( local t = {"one", "two", "three"} )"); @@ -771,14 +775,7 @@ TEST_CASE_FIXTURE(Fixture, "infer_indexer_from_array_like_table") const TableIndexer& indexer = *ttv->indexer; CHECK_EQ(*builtinTypes->numberType, *indexer.indexType); - - if (FFlag::LuauSolverV2) - { - // CLI-114134 - Use egraphs to simplify types - CHECK("string | string | string" == toString(indexer.indexResultType)); - } - else - CHECK_EQ(*builtinTypes->stringType, *indexer.indexResultType); + CHECK_EQ(*builtinTypes->stringType, *indexer.indexResultType); } TEST_CASE_FIXTURE(Fixture, "infer_indexer_from_value_property_in_literal") @@ -914,6 +911,8 @@ TEST_CASE_FIXTURE(Fixture, "array_factory_function") TEST_CASE_FIXTURE(Fixture, "sealed_table_indexers_must_unify") { + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; + CheckResult result = check(R"( function f(a: {number}): {string} return a @@ -924,12 +923,11 @@ TEST_CASE_FIXTURE(Fixture, "sealed_table_indexers_must_unify") if (FFlag::LuauSolverV2) { - CHECK( - "Type pack '{number}' could not be converted into '{string}'; \n" - "this is because in the 1st entry in the type pack, the result of indexing is `number` in the former type and `string` in the latter " - "type, " - "and `number` is not exactly `string`" == toString(result.errors[0]) - ); + std::string expected = "Type '{number}' could not be converted into '{string}'; \n" + "this is because the result of indexing is `number` in the former type and `string` in the latter type, " + "and `number` is not exactly `string`"; + auto actual = toString(result.errors[0]); + CHECK_EQ(expected, actual); } else CHECK_MESSAGE(nullptr != get(result.errors[0]), "Expected a TypeMismatch but got " << result.errors[0]); @@ -1697,6 +1695,8 @@ TEST_CASE_FIXTURE(Fixture, "right_table_missing_key2") TEST_CASE_FIXTURE(Fixture, "casting_unsealed_tables_with_props_into_table_with_indexer") { + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; + CheckResult result = check(R"( type StringToStringMap = { [string]: string } local rt: StringToStringMap = { ["foo"] = 1 } @@ -1710,8 +1710,9 @@ TEST_CASE_FIXTURE(Fixture, "casting_unsealed_tables_with_props_into_table_with_i if (FFlag::LuauSolverV2) { - CHECK_EQ("{ [string]: string }", toString(tm->wantedType, o)); - CHECK_EQ("{ [string]: number }", toString(tm->givenType, o)); + CHECK_EQ(result.errors[0].location, Location{{2, 50}, {2, 51}}); + CHECK_EQ("string", toString(tm->wantedType, o)); + CHECK_EQ("number", toString(tm->givenType, o)); } else { @@ -1798,6 +1799,8 @@ TEST_CASE_FIXTURE(Fixture, "casting_tables_with_props_into_table_with_indexer4") TEST_CASE_FIXTURE(Fixture, "table_subtyping_with_missing_props_dont_report_multiple_errors") { + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; + CheckResult result = check(R"( function f(vec1: {x: number}): {x: number, y: number, z: number} return vec1 @@ -1808,12 +1811,11 @@ TEST_CASE_FIXTURE(Fixture, "table_subtyping_with_missing_props_dont_report_multi if (FFlag::LuauSolverV2) { - CHECK_EQ( - "Type pack '{ x: number }' could not be converted into '{ x: number, y: number, z: number }'; \n" - "this is because the 1st entry in the type pack is `{ x: number }` in the former type and `{ x: number, y: number, z: number }` in the " - "latter type, and `{ x: number }` is not a subtype of `{ x: number, y: number, z: number }`", - toString(result.errors[0]) - ); + std::string expected = "Type\n\t" + "'{ x: number }'\n" + "could not be converted into\n\t" + "'{ x: number, y: number, z: number }'"; + CHECK_EQ(expected, toString(result.errors[0])); } else { @@ -1831,31 +1833,29 @@ TEST_CASE_FIXTURE(Fixture, "table_subtyping_with_missing_props_dont_report_multi TEST_CASE_FIXTURE(Fixture, "table_subtyping_with_missing_props_dont_report_multiple_errors2") { + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; + CheckResult result = check(R"( type MixedTable = {[number]: number, x: number} local t: MixedTable = {"fail"} )"); + LUAU_REQUIRE_ERROR_COUNT(2, result); + if (FFlag::LuauSolverV2) { - LUAU_REQUIRE_ERROR_COUNT(1, result); - TypeMismatch* tm = get(result.errors[0]); REQUIRE(tm); - CHECK("MixedTable" == toString(tm->wantedType)); - CHECK("{string}" == toString(tm->givenType)); + CHECK("number" == toString(tm->wantedType)); + CHECK("string" == toString(tm->givenType)); } - else - { - LUAU_REQUIRE_ERROR_COUNT(2, result); - MissingProperties* mp = get(result.errors[1]); - REQUIRE(mp); - CHECK_EQ(mp->context, MissingProperties::Missing); - REQUIRE_EQ(1, mp->properties.size()); - CHECK_EQ(mp->properties[0], "x"); - } + MissingProperties* mp = get(result.errors[1]); + REQUIRE(mp); + CHECK_EQ(mp->context, MissingProperties::Missing); + REQUIRE_EQ(1, mp->properties.size()); + CHECK_EQ(mp->properties[0], "x"); } TEST_CASE_FIXTURE(Fixture, "table_subtyping_with_extra_props_dont_report_multiple_errors") @@ -2044,7 +2044,10 @@ TEST_CASE_FIXTURE(Fixture, "key_setting_inference_given_nil_upper_bound") TEST_CASE_FIXTURE(Fixture, "explicit_nil_indexer") { - ScopedFastFlag _{FFlag::LuauSolverV2, true}; + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauTableLiteralSubtypeSpecificCheck, true}, + }; auto result = check(R"( local function _(t: { [string]: number? }): number @@ -2053,26 +2056,21 @@ TEST_CASE_FIXTURE(Fixture, "explicit_nil_indexer") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(result.errors[0].location, Location{{2, 12}, {2, 26}}); - CHECK(get(result.errors[0])); + CHECK_EQ(result.errors[0].location, Location{{2, 19}, {2, 26}}); + CHECK(get(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "ok_to_provide_a_subtype_during_construction") { + ScopedFastFlag _{FFlag::LuauSimplifyOutOfLine, true}; + CheckResult result = check(R"( local a: string | number = 1 local t = {a, 1} )"); LUAU_REQUIRE_NO_ERRORS(result); - - if (FFlag::LuauSolverV2) - { - // CLI-114134 Use egraphs to simplify types more consistently - CHECK("{number | number | string}" == toString(requireType("t"), {/*exhaustive*/ true})); - } - else - CHECK_EQ("{number | string}", toString(requireType("t"), {/*exhaustive*/ true})); + CHECK_EQ("{number | string}", toString(requireType("t"), {/*exhaustive*/ true})); } TEST_CASE_FIXTURE(Fixture, "reasonable_error_when_adding_a_nonexistent_property_to_an_array_like_table") @@ -2382,7 +2380,14 @@ TEST_CASE_FIXTURE(Fixture, "invariant_table_properties_means_instantiating_table local c : string = t.m("hi") )"); - if (FFlag::LuauSolverV2) + if (FFlag::DebugLuauGreedyGeneralization && FFlag::LuauSolverV2) + { + // FIXME CLI-151985 + LUAU_CHECK_ERROR_COUNT(2, result); + LUAU_CHECK_ERROR(result, ExplicitFunctionAnnotationRecommended); + LUAU_CHECK_ERROR(result, ConstraintSolvingIncompleteError); + } + else if (FFlag::LuauSolverV2) { LUAU_REQUIRE_ERROR_COUNT(1, result); @@ -3640,21 +3645,20 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "dont_leak_free_table_props") TEST_CASE_FIXTURE(Fixture, "mixed_tables_with_implicit_numbered_keys") { + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; CheckResult result = check(R"( local t: { [string]: number } = { 5, 6, 7 } )"); + LUAU_REQUIRE_ERROR_COUNT(3, result); if (FFlag::LuauSolverV2) { - std::string expected = - "Type '{number}' could not be converted into '{ [string]: number }'; \n" - "this is because the index type is `number` in the former type and `string` in the latter type, and `number` is not exactly `string`"; - CHECK(toString(result.errors[0]) == expected); + for (const auto& err : result.errors) + CHECK_EQ("Unexpected array-like table item: the indexer key type of this table is not `number`.", toString(err)); } else { LUAU_REQUIRE_ERROR_COUNT(3, result); - CHECK_EQ("Type 'number' could not be converted into 'string'", toString(result.errors[0])); CHECK_EQ("Type 'number' could not be converted into 'string'", toString(result.errors[1])); CHECK_EQ("Type 'number' could not be converted into 'string'", toString(result.errors[2])); @@ -3748,7 +3752,7 @@ TEST_CASE_FIXTURE(Fixture, "scalar_is_a_subtype_of_a_compatible_polymorphic_shap TEST_CASE_FIXTURE(Fixture, "scalar_is_not_a_subtype_of_a_compatible_polymorphic_shape_type") { - ScopedFastFlag sffs[] = {{FFlag::LuauNonReentrantGeneralization2, true}, {FFlag::LuauReportSubtypingErrors, true}}; + ScopedFastFlag sffs[] = {{FFlag::LuauNonReentrantGeneralization3, true}, {FFlag::LuauReportSubtypingErrors, true}}; CheckResult result = check(R"( local function f(s) @@ -4273,7 +4277,10 @@ TEST_CASE_FIXTURE(Fixture, "cyclic_shifted_tables") TEST_CASE_FIXTURE(Fixture, "cli_84607_missing_prop_in_array_or_dict") { - ScopedFastFlag sff{FFlag::LuauFixIndexerSubtypingOrdering, true}; + ScopedFastFlag sffs[] = { + {FFlag::LuauFixIndexerSubtypingOrdering, true}, + {FFlag::LuauTableLiteralSubtypeSpecificCheck, true}, + }; CheckResult result = check(R"( type Thing = { name: string, prop: boolean } @@ -4291,17 +4298,13 @@ TEST_CASE_FIXTURE(Fixture, "cli_84607_missing_prop_in_array_or_dict") if (FFlag::LuauSolverV2) { - const TypeMismatch* err1 = get(result.errors[0]); - REQUIRE_MESSAGE(err1, "Expected TypeMismatch but got " << result.errors[0]); - - CHECK("{Thing}" == toString(err1->wantedType)); - CHECK("{{ name: string }}" == toString(err1->givenType)); - - const TypeMismatch* err2 = get(result.errors[1]); - REQUIRE_MESSAGE(err2, "Expected TypeMismatch but got " << result.errors[1]); - - CHECK("{ [string]: Thing }" == toString(err2->wantedType)); - CHECK("{ [string]: { name: string } }" == toString(err2->givenType)); + for (const auto& err: result.errors) + { + const auto* error = get(err); + REQUIRE(error); + REQUIRE(error->properties.size() == 1); + CHECK_EQ("prop", error->properties[0]); + } } else { @@ -4345,7 +4348,10 @@ TEST_CASE_FIXTURE(Fixture, "simple_method_definition") TEST_CASE_FIXTURE(Fixture, "identify_all_problematic_table_fields") { - ScopedFastFlag sff_LuauSolverV2{FFlag::LuauSolverV2, true}; + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauTableLiteralSubtypeSpecificCheck, true}, + }; CheckResult result = check(R"( type T = { @@ -4361,15 +4367,25 @@ TEST_CASE_FIXTURE(Fixture, "identify_all_problematic_table_fields") } )"); - LUAU_REQUIRE_ERROR_COUNT(1, result); + LUAU_REQUIRE_ERROR_COUNT(3, result); - std::string expected = - "Type '{ a: string, b: boolean, c: number }' could not be converted into 'T'; \n" - "this is because \n\t" - " * accessing `a` results in `string` in the former type and `number` in the latter type, and `string` is not exactly `number`\n\t" - " * accessing `b` results in `boolean` in the former type and `string` in the latter type, and `boolean` is not exactly `string`\n\t" - " * accessing `c` results in `number` in the former type and `boolean` in the latter type, and `number` is not exactly `boolean`"; - CHECK(toString(result.errors[0]) == expected); + auto err0 = get(result.errors[0]); + REQUIRE(err0); + CHECK_EQ(result.errors[0].location, Location{{8, 16}, {8, 21}}); + CHECK_EQ("string", toString(err0->givenType)); + CHECK_EQ("number", toString(err0->wantedType)); + + auto err1 = get(result.errors[1]); + REQUIRE(err1); + CHECK_EQ(result.errors[1].location, Location{{9, 16}, {9, 21}}); + CHECK_EQ("boolean", toString(err1->givenType)); + CHECK_EQ("string", toString(err1->wantedType)); + + auto err2 = get(result.errors[2]); + REQUIRE(err2); + CHECK_EQ(result.errors[2].location, Location{{10, 16}, {10, 19}}); + CHECK_EQ("number", toString(err2->givenType)); + CHECK_EQ("boolean", toString(err2->wantedType)); } TEST_CASE_FIXTURE(Fixture, "read_and_write_only_table_properties_are_unsupported") @@ -4555,7 +4571,7 @@ TEST_CASE_FIXTURE(Fixture, "table_writes_introduce_write_properties") if (!FFlag::LuauSolverV2) return; - ScopedFastFlag sff[] = {{FFlag::LuauNonReentrantGeneralization2, true}}; + ScopedFastFlag sff[] = {{FFlag::LuauNonReentrantGeneralization3, true}}; CheckResult result = check(R"( function oc(player, speaker) @@ -4948,18 +4964,15 @@ end TEST_CASE_FIXTURE(BuiltinsFixture, "indexing_branching_table") { + ScopedFastFlag _{FFlag::LuauSimplifyOutOfLine, true}; + CheckResult result = check(R"( local test = if true then { "meow", "woof" } else { 4, 81 } local test2 = test[1] )"); LUAU_REQUIRE_NO_ERRORS(result); - - // unfortunate type duplication in the union - if (FFlag::LuauSolverV2) - CHECK("number | string | string" == toString(requireType("test2"))); - else - CHECK("number | string" == toString(requireType("test2"))); + CHECK("number | string" == toString(requireType("test2"))); } TEST_CASE_FIXTURE(BuiltinsFixture, "indexing_branching_table2") @@ -4999,6 +5012,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "subtyping_with_a_metatable_table_path") if (!FFlag::LuauSolverV2) return; + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; + CheckResult result = check(R"( type self = {} & {} type Class = typeof(setmetatable()) @@ -5090,7 +5105,10 @@ TEST_CASE_FIXTURE(Fixture, "function_check_constraint_too_eager") TEST_CASE_FIXTURE(BuiltinsFixture, "magic_functions_bidirectionally_inferred") { - ScopedFastFlag _{FFlag::LuauSolverV2, true}; + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauTableLiteralSubtypeSpecificCheck, true}, + }; CheckResult result = check(R"( local function getStuff(): (string, number, string) @@ -5115,9 +5133,11 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "magic_functions_bidirectionally_inferred") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - auto err = get(result.errors[0]); - CHECK_EQ("{ [string]: number | string }", toString(err->givenType)); - CHECK_EQ("{ [string]: number }", toString(err->wantedType)); + auto err0 = get(result.errors[0]); + REQUIRE(err0); + CHECK_EQ(result.errors[0].location, Location{{6, 38}, {6, 59}}); + CHECK_EQ("string", toString(err0->givenType)); + CHECK_EQ("number", toString(err0->wantedType)); } @@ -5278,6 +5298,7 @@ TEST_CASE_FIXTURE(Fixture, "returning_mismatched_optional_in_table") { ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, + {FFlag::LuauTableLiteralSubtypeSpecificCheck, true}, }; auto result = check(R"( @@ -5289,15 +5310,18 @@ TEST_CASE_FIXTURE(Fixture, "returning_mismatched_optional_in_table") end )"); LUAU_CHECK_ERROR_COUNT(1, result); - auto err = get(result.errors[0]); + auto err = get(result.errors[0]); REQUIRE(err); - CHECK_EQ(toString(err->givenTp), "{ Value: string }"); - CHECK_EQ(toString(err->wantedTp), "{ Value: number? }"); + CHECK_EQ(toString(err->givenType), "string"); + CHECK_EQ(toString(err->wantedType), "number?"); } TEST_CASE_FIXTURE(Fixture, "optional_function_in_table") { - ScopedFastFlag _{FFlag::LuauSolverV2, true}; + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauTableLiteralSubtypeSpecificCheck, true} + }; LUAU_CHECK_NO_ERRORS(check(R"( local t: { (() -> ())? } = { @@ -5314,8 +5338,9 @@ TEST_CASE_FIXTURE(Fixture, "optional_function_in_table") LUAU_CHECK_ERROR_COUNT(1, result); auto err = get(result.errors[0]); REQUIRE(err); - CHECK_EQ(toString(err->givenType), "{(string) -> ()}"); - CHECK_EQ(toString(err->wantedType), "{((number) -> ())?}"); + CHECK_EQ(result.errors[0].location, Location{{2, 12}, {2, 35}}); + CHECK_EQ(toString(err->givenType), "(string) -> ()"); + CHECK_EQ(toString(err->wantedType), "((number) -> ())?"); } TEST_CASE_FIXTURE(Fixture, "oss_1596_expression_in_table") @@ -5350,7 +5375,10 @@ TEST_CASE_FIXTURE(Fixture, "oss_1543_optional_generic_param") TEST_CASE_FIXTURE(Fixture, "missing_fields_bidirectional_inference") { - ScopedFastFlag _{FFlag::LuauSolverV2, true}; + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauTableLiteralSubtypeSpecificCheck, true}, + }; auto result = check(R"( type Book = { title: string, author: string } @@ -5363,22 +5391,24 @@ TEST_CASE_FIXTURE(Fixture, "missing_fields_bidirectional_inference") )"); LUAU_CHECK_ERROR_COUNT(2, result); - auto err = get(result.errors[0]); - REQUIRE(err); - CHECK_EQ(toString(err->givenType), "{ title: string }"); - CHECK_EQ(toString(err->wantedType), "Book"); + auto err0 = get(result.errors[0]); + REQUIRE(err0); + CHECK_EQ(err0->properties.size(), 1); + CHECK_EQ(*err0->properties.begin(), "author"); CHECK_EQ(result.errors[0].location, Location{{2, 24}, {2, 49}}); - err = get(result.errors[1]); - REQUIRE(err); - // CLI-144203: This could be better. - CHECK_EQ(toString(err->givenType), "{{ author: string }}"); - CHECK_EQ(toString(err->wantedType), "{Book}"); - CHECK_EQ(result.errors[1].location, Location{{3, 28}, {7, 9}}); + auto err1 = get(result.errors[1]); + REQUIRE(err1); + CHECK_EQ(err1->properties.size(), 1); + CHECK_EQ(*err1->properties.begin(), "title"); + CHECK_EQ(result.errors[1].location, Location{{6, 12}, {6, 33}}); } TEST_CASE_FIXTURE(Fixture, "generic_index_syntax_bidirectional_infer_with_tables") { - ScopedFastFlag _{FFlag::LuauSolverV2, true}; + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauTableLiteralSubtypeSpecificCheck, true}, + }; auto result = check((R"( local function getStatus(): string @@ -5398,12 +5428,22 @@ TEST_CASE_FIXTURE(Fixture, "generic_index_syntax_bidirectional_infer_with_tables } )")); - LUAU_CHECK_ERROR_COUNT(1, result); - auto err = get(result.errors[0]); - REQUIRE(err); - // NOTE: This is because the inferred keys of `books` are all primitive types. - CHECK_EQ(toString(err->givenType), "{ [string | string | string]: string | { daysLate: string, isLate: boolean } | { isLate: boolean } }"); - CHECK_EQ(toString(err->wantedType), "{ [string]: Status }"); + LUAU_CHECK_ERROR_COUNT(3, result); + auto err0 = get(result.errors[0]); + REQUIRE(err0); + CHECK_EQ(toString(err0->givenType), "string"); + CHECK_EQ(toString(err0->wantedType), "number?"); + CHECK_EQ(result.errors[0].location, Location{{12, 49}, {12, 58}}); + auto err1 = get(result.errors[1]); + REQUIRE(err1); + CHECK_EQ(toString(err1->givenType), "string"); + CHECK_EQ(toString(err1->wantedType), "Status"); + CHECK_EQ(result.errors[1].location, Location{{13, 21}, {13, 32}}); + auto err2 = get(result.errors[2]); + REQUIRE(err2); + CHECK_EQ(toString(err2->givenType), "{ isLate: boolean }"); + CHECK_EQ(toString(err2->wantedType), "Status"); + CHECK_EQ(result.errors[2].location, Location{{14, 21}, {14, 41}}); } TEST_CASE_FIXTURE(Fixture, "deeply_nested_classish_inference") @@ -5432,6 +5472,7 @@ TEST_CASE_FIXTURE(Fixture, "bigger_nested_table_causes_big_type_error") { ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, + {FFlag::LuauTableLiteralSubtypeSpecificCheck, true} }; auto result = check(R"( @@ -5466,16 +5507,7 @@ TEST_CASE_FIXTURE(Fixture, "bigger_nested_table_causes_big_type_error") LUAU_REQUIRE_ERROR_COUNT(1, result); std::string expected = - "Type\n\t" - "'{Dir | File | { children: ({Dir | File | { content: string?, path: string, type: \"file\" }} | {Dir | File})?, name: " - "string, type: \"dir\" }}'" - "\ncould not be converted into\n\t" - "'DirectoryChildren'; \n" - "this is because in the result of indexing has the 3rd component of the union as `{ children: ({Dir | File | { content: " - "string?, path: string, type: \"file\" }} | {Dir | File})?, name: string, type: \"dir\" }` and the result of indexing is " - "`Dir | File`, and `{ children: ({Dir | File | { content: string?, path: string, type: \"file\" }} | {Dir | File})?, " - "name: string, type: \"dir\" }` is not exactly `Dir | File`"; - + R"(Type '{ children: {{ path: string, type: string }}, name: string, type: "dir" }' could not be converted into 'Dir | File')"; CHECK_EQ(expected, toString(result.errors[0])); } @@ -5550,6 +5582,11 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_match_literal_type_crash_again") TEST_CASE_FIXTURE(Fixture, "type_mismatch_in_dict") { + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauTableLiteralSubtypeSpecificCheck, true}, + }; + CheckResult result = check(R"( --!strict local dict: {[string]: boolean} = { @@ -5559,12 +5596,69 @@ TEST_CASE_FIXTURE(Fixture, "type_mismatch_in_dict") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); TypeMismatch* tm = get(result.errors[0]); - if (FFlag::LuauSolverV2) - { - // ideally, we'd actually want this to give you `boolean` and `number` mismatch on `123`, but this is okay. - CHECK_EQ(toString(tm->wantedType, {true}), "{ [string]: boolean }"); - CHECK_EQ(toString(tm->givenType, {true}), "{ [string]: boolean | number }"); - } + CHECK_EQ(toString(tm->wantedType, {true}), "boolean"); + CHECK_EQ(toString(tm->givenType, {true}), "number"); +} + +TEST_CASE_FIXTURE(Fixture, "narrow_table_literal_check") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauTableLiteralSubtypeSpecificCheck, true}, + }; + CheckResult result = check(R"( + --!strict + local dict: { code1: boolean } = { + code1 = 123, + } + )"); + LUAU_REQUIRE_ERROR_COUNT(1, result); + TypeMismatch* tm = get(result.errors[0]); + REQUIRE(tm); + CHECK_EQ("number", toString(tm->givenType)); + CHECK_EQ("boolean", toString(tm->wantedType)); +} + +TEST_CASE_FIXTURE(Fixture, "narrow_table_literal_check_regression") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauTableLiteralSubtypeSpecificCheck, true}, + }; + CheckResult result = check(R"( + --!strict + local d1: { code1: boolean } = { + code1 = true, + } + local d2: { [string]: number } = d1 + )"); + LUAU_REQUIRE_ERROR_COUNT(1, result); + TypeMismatch* tm = get(result.errors[0]); + REQUIRE(tm); + CHECK_EQ("{ code1: boolean }", toString(tm->givenType)); + CHECK_EQ("{ [string]: number }", toString(tm->wantedType)); +} + +TEST_CASE_FIXTURE(Fixture, "narrow_table_literal_check_assignment") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauTableLiteralSubtypeSpecificCheck, true}, + }; + + CheckResult result = check(R"( + --!strict + local d1: { code1: boolean } = { + code1 = true, + } + d1 = { code1 = 42 } + )"); + LUAU_REQUIRE_ERROR_COUNT(1, result); + TypeMismatch* tm = get(result.errors[0]); + REQUIRE(tm); + CHECK_EQ(result.errors[0].location, Location{{5, 23}, {5, 25}}); + CHECK_EQ("number", toString(tm->givenType)); + CHECK_EQ("boolean", toString(tm->wantedType)); } diff --git a/tests/TypeInfer.test.cpp b/tests/TypeInfer.test.cpp index a7c37341..9deeb3d7 100644 --- a/tests/TypeInfer.test.cpp +++ b/tests/TypeInfer.test.cpp @@ -24,19 +24,20 @@ LUAU_FASTINT(LuauNormalizeCacheLimit) LUAU_FASTINT(LuauRecursionLimit) LUAU_FASTINT(LuauTypeInferTypePackLoopLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit) -LUAU_FASTFLAG(LuauNewNonStrictWarnOnUnknownGlobals) LUAU_FASTFLAG(LuauTypeCheckerAcceptNumberConcats) LUAU_FASTFLAG(LuauPreprocessTypestatedArgument) -LUAU_FASTFLAG(LuauCacheInferencePerAstExpr) LUAU_FASTFLAG(LuauMagicFreezeCheckBlocked2) -LUAU_FASTFLAG(LuauNonReentrantGeneralization2) +LUAU_FASTFLAG(LuauNoMoreInjectiveTypeFunctions) +LUAU_FASTFLAG(LuauNonReentrantGeneralization3) LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect) LUAU_FASTFLAG(LuauHasPropProperBlock) LUAU_FASTFLAG(LuauStringPartLengthLimit) LUAU_FASTFLAG(LuauSimplificationRecheckAssumption) -LUAU_FASTFLAG(LuauAlwaysResolveAstTypes) LUAU_FASTFLAG(LuauReportSubtypingErrors) LUAU_FASTFLAG(LuauAvoidDoubleNegation) +LUAU_FASTFLAG(LuauInsertErrorTypesIntoIndexerResult) +LUAU_FASTFLAG(LuauSimplifyOutOfLine) +LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops) using namespace Luau; @@ -444,7 +445,7 @@ TEST_CASE_FIXTURE(Fixture, "check_expr_recursion_limit") #endif ScopedFastInt luauRecursionLimit{FInt::LuauRecursionLimit, limit + 100}; ScopedFastInt luauCheckRecursionLimit{FInt::LuauCheckRecursionLimit, limit - 100}; - ScopedFastFlag _{FFlag::LuauNonReentrantGeneralization2, false}; + ScopedFastFlag _{FFlag::LuauNonReentrantGeneralization3, false}; CheckResult result = check(R"(("foo"))" + rep(":lower()", limit)); @@ -832,10 +833,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "no_heap_use_after_free_error") end )"); - if (FFlag::LuauSolverV2 && !FFlag::LuauNewNonStrictWarnOnUnknownGlobals) - LUAU_REQUIRE_NO_ERRORS(result); - else - LUAU_REQUIRE_ERRORS(result); + LUAU_REQUIRE_ERRORS(result); } TEST_CASE_FIXTURE(Fixture, "infer_type_assertion_value_type") @@ -890,6 +888,7 @@ TEST_CASE_FIXTURE(Fixture, "tc_if_else_expressions1") CheckResult result = check(R"(local a = if true then "true" else "false")"); LUAU_REQUIRE_NO_ERRORS(result); TypeId aType = requireType("a"); + CHECK("string" == toString(aType)); } @@ -1913,6 +1912,21 @@ end )"); } +TEST_CASE_FIXTURE(Fixture, "fuzzer_derived_unsound_loops") +{ + ScopedFastFlag _{FFlag::LuauDfgAllowUpdatesInLoops, true}; + LUAU_REQUIRE_NO_ERRORS(check(R"( + for _ in ... do + repeat + _ = 42 + until _ + repeat + _ = _ + 2 + until _ + end + )")); +} + TEST_CASE_FIXTURE(Fixture, "concat_string_with_string_union") { ScopedFastFlag _{FFlag::LuauSolverV2, true}; @@ -1949,7 +1963,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_local_before_declaration_ice") TEST_CASE_FIXTURE(Fixture, "fuzz_dont_double_solve_compound_assignment" * doctest::timeout(1.0)) { - ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauCacheInferencePerAstExpr, true}}; + ScopedFastFlag _{FFlag::LuauSolverV2, true}; CheckResult result = check(R"( local _ = {} @@ -1969,7 +1983,6 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_dont_double_solve_compound_assignment" * doctes TEST_CASE_FIXTURE(Fixture, "assert_allows_singleton_union_or_intersection") { - ScopedFastFlag sff{FFlag::LuauAlwaysResolveAstTypes, true}; LUAU_REQUIRE_NO_ERRORS(check(R"( local x = 42 :: | number local y = 42 :: & number @@ -2029,7 +2042,7 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_generalize_one_remove_type_assert") ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, {FFlag::LuauHasPropProperBlock, true}, - {FFlag::LuauNonReentrantGeneralization2, true}, + {FFlag::LuauNonReentrantGeneralization3, true}, {FFlag::LuauOptimizeFalsyAndTruthyIntersect, true} }; @@ -2059,14 +2072,13 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_generalize_one_remove_type_assert") end )"); LUAU_REQUIRE_ERRORS(result); - LUAU_REQUIRE_NO_ERROR(result, ConstraintSolvingIncompleteError); } TEST_CASE_FIXTURE(Fixture, "fuzz_generalize_one_remove_type_assert_2") { ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, - {FFlag::LuauNonReentrantGeneralization2, true}, + {FFlag::LuauNonReentrantGeneralization3, true}, {FFlag::LuauOptimizeFalsyAndTruthyIntersect, true}, }; @@ -2099,7 +2111,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_simplify_combinatorial_explosion") ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, {FFlag::LuauHasPropProperBlock, true}, - {FFlag::LuauNonReentrantGeneralization2, true}, + {FFlag::LuauNonReentrantGeneralization3, true}, {FFlag::LuauOptimizeFalsyAndTruthyIntersect, true}, {FFlag::LuauStringPartLengthLimit, true}, {FFlag::LuauSimplificationRecheckAssumption, true}, @@ -2137,7 +2149,11 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_missing_follow_table_freeze") TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_avoid_double_negation" * doctest::timeout(0.5)) { - ScopedFastFlag _{FFlag::LuauAvoidDoubleNegation, true}; + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauOptimizeFalsyAndTruthyIntersect, true}, + {FFlag::LuauAvoidDoubleNegation, true}, + }; // We don't care about errors, only that we don't OOM during typechecking. LUAU_REQUIRE_ERRORS(check(R"( local _ = _ @@ -2167,4 +2183,108 @@ end )")); } +TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_has_indexer_can_create_cyclic_union") +{ + ScopedFastFlag _{FFlag::LuauInsertErrorTypesIntoIndexerResult, true}; + + LUAU_REQUIRE_ERRORS(check(R"( + local _ = nil + repeat + _ = {[true] = _[_]} + do + repeat + _ = {[_[l0]] = _[_]} + return + until #next(_) < _ + end + local l0 = require(module0) + until #_[_](_) < next(_) + )")); +} + +TEST_CASE_FIXTURE(Fixture, "fuzzer_simplify_table_indexer" * doctest::timeout(0.5)) +{ + ScopedFastFlag _{FFlag::LuauSimplifyOutOfLine, true}; + + LUAU_REQUIRE_ERRORS(check(R"( + _[_] += true + _ = { + [{ + [_] = _[_][if ... then _ else _](), + [-1795162112] = function() + end, + [{ + _G = function() + end + }] = _(_(true)), + _G = _ + }] = _, + [_[not _][_]] = _(), + _ + } + + )")); +} + +TEST_CASE_FIXTURE(Fixture, "fuzzer_simplify_crash") +{ + LUAU_REQUIRE_ERRORS(check(R"( + if _ then + _ = nil + else if _ and _ then + _ = nil + end + )")); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_simplify_is_check_on_bound_type") +{ + LUAU_REQUIRE_ERRORS(check(R"( + _[if _ then false],_,_._,log10 = {{[_]={_,},_G=not function():true + _ = nil + end,},[_[_ + true][_][_]]=_,sort=_,},_ + )")); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "regexp_hang" * doctest::timeout(0.5)) +{ + LUAU_REQUIRE_ERRORS(check(R"( +local outln, group_id, verb_flags = {}, {}, { + newline = 1, + newline_seq = 1, + not_empty = 0 +} +if not escape_c then +elseif escape_c >= 48 and escape_c <= 57 then +elseif escape_c == 69 then +elseif escape_c == 81 then +elseif escape_c == 78 then + if codes[i] ~= 125 or i == start_i then + end + table.insert(outln, code_point) +elseif escape_c == 80 or escape_c == 112 then + if script_set then + elseif not valid_categories[c_name]then + else + table.insert(outln, { 'category', negate, c_name }) + end +elseif escape_c == 103 and (codes[i + 1] == 123 or codes[i + 1] >= 48 and codes[i + 1] <= 57)then +elseif escape_c == 111 then +elseif escape_c == 120 then +else + table.insert(outln, esc_char or escape_c) +end + +for i, v in ipairs(outln)do + if type(v) == 'table' and (v[1] == 40 or v[1] == 'quantifier' and type(v[5]) == 'table' and v[5][1] == 40)then + v = v[5] + elseif type(v) == 'table' and (v[1] == 'backref' or v[1] == 'recurmatch')then + for i1, v1 in ipairs(outln)do + break + end + end +end + )")); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.typePacks.test.cpp b/tests/TypeInfer.typePacks.test.cpp index 231d537b..3df04b71 100644 --- a/tests/TypeInfer.typePacks.test.cpp +++ b/tests/TypeInfer.typePacks.test.cpp @@ -15,6 +15,7 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(DebugLuauGreedyGeneralization) LUAU_FASTFLAG(LuauReportSubtypingErrors) LUAU_FASTFLAG(LuauTrackInferredFunctionTypeFromCall) +LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck) TEST_SUITE_BEGIN("TypePackTests"); @@ -1095,6 +1096,8 @@ TEST_CASE_FIXTURE(Fixture, "unify_variadic_tails_in_arguments") TEST_CASE_FIXTURE(Fixture, "unify_variadic_tails_in_arguments_free") { + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; + CheckResult result = check(R"( function foo(...: T...): T... return ... diff --git a/tests/TypeInfer.typestates.test.cpp b/tests/TypeInfer.typestates.test.cpp index 3f08652a..7c9383db 100644 --- a/tests/TypeInfer.typestates.test.cpp +++ b/tests/TypeInfer.typestates.test.cpp @@ -8,7 +8,11 @@ LUAU_FASTFLAG(LuauRefineWaitForBlockedTypesInTarget) LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType) LUAU_FASTFLAG(LuauDfgIfBlocksShouldRespectControlFlow) LUAU_FASTFLAG(LuauReportSubtypingErrors) -LUAU_FASTFLAG(LuauNonReentrantGeneralization2) +LUAU_FASTFLAG(LuauNonReentrantGeneralization3) +LUAU_FASTFLAG(LuauDfgMatchCGScopes) +LUAU_FASTFLAG(LuauPreprocessTypestatedArgument) +LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck) +LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops) using namespace Luau; @@ -66,6 +70,8 @@ TEST_CASE_FIXTURE(TypeStateFixture, "assign_different_values_to_x") TEST_CASE_FIXTURE(TypeStateFixture, "parameter_x_was_constrained_by_two_types") { + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; + // Parameter `x` has a fresh type `'x` bounded by `never` and `unknown`. // The first use of `x` constrains `x`'s upper bound by `string | number`. // The second use of `x`, aliased by `y`, constrains `x`'s upper bound by `string?`. @@ -87,11 +93,10 @@ TEST_CASE_FIXTURE(TypeStateFixture, "parameter_x_was_constrained_by_two_types") // as a type for `x`, but it's a limitation we can accept for now. LUAU_REQUIRE_ERRORS(result); - TypePackMismatch* tpm = get(result.errors[0]); - REQUIRE_MESSAGE(tpm, "Expected TypePackMismatch but got " << result.errors[0]); - CHECK("string?" == toString(tpm->wantedTp)); - CHECK("number | string" == toString(tpm->givenTp)); - + TypeMismatch* tm = get(result.errors[0]); + REQUIRE_MESSAGE(tm, "Expected TypeMismatch but got " << result.errors[0]); + CHECK("string?" == toString(tm->wantedType)); + CHECK("number | string" == toString(tm->givenType)); CHECK("(number | string) -> string?" == toString(requireType("f"))); } else @@ -409,7 +414,7 @@ TEST_CASE_FIXTURE(TypeStateFixture, "prototyped_recursive_functions") TEST_CASE_FIXTURE(BuiltinsFixture, "prototyped_recursive_functions_but_has_future_assignments") { - ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauReportSubtypingErrors, true}, {FFlag::LuauNonReentrantGeneralization2, true}}; + ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauReportSubtypingErrors, true}, {FFlag::LuauNonReentrantGeneralization3, true}}; CheckResult result = check(R"( local f @@ -753,6 +758,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "refinement_through_erroring_in_loop") ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, {FFlag::LuauDfgIfBlocksShouldRespectControlFlow, true}, + {FFlag::LuauDfgAllowUpdatesInLoops,true} }; CheckResult result = check(R"( @@ -769,10 +775,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "refinement_through_erroring_in_loop") )"); LUAU_REQUIRE_NO_ERRORS(result); - - // CLI-142447: This should probably be `nil` given that the `while` loop - // unconditionally returns, but `number?` is sound, if incomplete. - CHECK_EQ("number?", toString(requireTypeAtPosition({10, 14}))); + CHECK_EQ("nil", toString(requireTypeAtPosition({10, 14}))); } TEST_CASE_FIXTURE(BuiltinsFixture, "type_refinement_in_loop") @@ -846,4 +849,55 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "assign_in_an_if_branch_without_else") CHECK_EQ("string?", toString(requireTypeAtPosition({9, 14}))); } +TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_table_freeze_in_binary_expr") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauPreprocessTypestatedArgument, true}, + {FFlag::LuauDfgMatchCGScopes, true}, + }; + + // Previously this would ICE due to mismatched scopes between the + // constraint generator and the data flow graph. + LUAU_REQUIRE_ERRORS(check(R"( + local _ + if _ or table.freeze(_,_) or table.freeze(_,_) then + end + )")); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_in_conditional") +{ + ScopedFastFlag _{FFlag::LuauDfgMatchCGScopes, true}; + + // NOTE: This _probably_ should be disallowed, but it is representing that + // type stating functions in short circuiting binary expressions do not + // reflect their type states. + CheckResult result = check(R"( + local t = { x = 42 } + if math.random() > 0.5 and table.freeze(t) then + end + t.y = 13 + )"); + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_table_freeze_in_conditional_expr") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauPreprocessTypestatedArgument, true}, + {FFlag::LuauDfgMatchCGScopes, true}, + }; + + // Previously this would ICE due to mismatched scopes between the + // constraint generator and the data flow graph. + LUAU_REQUIRE_ERRORS(check(R"( + --!strict + local _ + if + if table.freeze(_,_) then _ else _ + then + end + )")); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.unionTypes.test.cpp b/tests/TypeInfer.unionTypes.test.cpp index 7f1a888b..136fab44 100644 --- a/tests/TypeInfer.unionTypes.test.cpp +++ b/tests/TypeInfer.unionTypes.test.cpp @@ -11,6 +11,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(DebugLuauGreedyGeneralization) +LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck) TEST_SUITE_BEGIN("UnionTypes"); @@ -582,6 +583,8 @@ TEST_CASE_FIXTURE(Fixture, "error_detailed_union_all") TEST_CASE_FIXTURE(Fixture, "error_detailed_optional") { + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; + CheckResult result = check(R"( type X = { x: number } @@ -590,7 +593,7 @@ local a: X? = { w = 4 } LUAU_REQUIRE_ERROR_COUNT(1, result); if (FFlag::LuauSolverV2) - CHECK("Type '{ w: number }' could not be converted into 'X?'" == toString(result.errors[0])); + CHECK("Table type '{ w: number }' not compatible with type 'X' because the former is missing field 'x'" == toString(result.errors[0])); else { const std::string expected = R"(Type 'a' could not be converted into 'X?' diff --git a/tests/TypeInfer.unknownnever.test.cpp b/tests/TypeInfer.unknownnever.test.cpp index 0c62d0b6..5294634a 100644 --- a/tests/TypeInfer.unknownnever.test.cpp +++ b/tests/TypeInfer.unknownnever.test.cpp @@ -7,6 +7,8 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2); +LUAU_FASTFLAG(LuauNoMoreInjectiveTypeFunctions); +LUAU_FASTFLAG(DebugLuauGreedyGeneralization); TEST_SUITE_BEGIN("TypeInferUnknownNever"); @@ -335,7 +337,12 @@ TEST_CASE_FIXTURE(Fixture, "dont_unify_operands_if_one_of_the_operand_is_never_i LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::LuauSolverV2) + if (FFlag::LuauSolverV2 && FFlag::LuauNoMoreInjectiveTypeFunctions) + { + // FIXME: CLI-152325 + CHECK_EQ("(nil, nil & ~nil) -> boolean", toString(requireType("ord"))); + } + else if (FFlag::LuauSolverV2) CHECK_EQ("(nil, unknown) -> boolean", toString(requireType("ord"))); else CHECK_EQ("(nil, a) -> boolean", toString(requireType("ord"))); diff --git a/tests/require/with_config/src/submodule/.luaurc b/tests/require/with_config/src/submodule/.luaurc new file mode 100644 index 00000000..e303d257 --- /dev/null +++ b/tests/require/with_config/src/submodule/.luaurc @@ -0,0 +1,5 @@ +{ + "aliases": { + "dep": "./this_should_not_be_read_by_init_luau", + } +} diff --git a/tests/require/with_config/src/submodule/init.luau b/tests/require/with_config/src/submodule/init.luau new file mode 100644 index 00000000..4375a783 --- /dev/null +++ b/tests/require/with_config/src/submodule/init.luau @@ -0,0 +1 @@ +return require("@dep") diff --git a/tests/require/without_config/nested_inits/init.luau b/tests/require/without_config/nested_inits/init.luau new file mode 100644 index 00000000..9a36b68a --- /dev/null +++ b/tests/require/without_config/nested_inits/init.luau @@ -0,0 +1,2 @@ +local result = require("@self/init") +return result diff --git a/tests/require/without_config/nested_inits/init/init.luau b/tests/require/without_config/nested_inits/init/init.luau new file mode 100644 index 00000000..0623c941 --- /dev/null +++ b/tests/require/without_config/nested_inits/init/init.luau @@ -0,0 +1 @@ +return {"result from nested_inits/init"} diff --git a/tests/require/without_config/nested_inits_requirer.luau b/tests/require/without_config/nested_inits_requirer.luau new file mode 100644 index 00000000..6c4a0a5f --- /dev/null +++ b/tests/require/without_config/nested_inits_requirer.luau @@ -0,0 +1,3 @@ +local result = require("./nested_inits") +result[#result+1] = "required into module" +return result