diff --git a/Analysis/include/Luau/Error.h b/Analysis/include/Luau/Error.h index fe9d7924..c1ac076e 100644 --- a/Analysis/include/Luau/Error.h +++ b/Analysis/include/Luau/Error.h @@ -455,6 +455,13 @@ struct UserDefinedTypeFunctionError bool operator==(const UserDefinedTypeFunctionError& rhs) const; }; +struct ReservedIdentifier +{ + std::string name; + + bool operator==(const ReservedIdentifier& rhs) const; +}; + using TypeErrorData = Variant< TypeMismatch, UnknownSymbol, @@ -504,7 +511,8 @@ using TypeErrorData = Variant< UnexpectedTypeInSubtyping, UnexpectedTypePackInSubtyping, ExplicitFunctionAnnotationRecommended, - UserDefinedTypeFunctionError>; + UserDefinedTypeFunctionError, + ReservedIdentifier>; struct TypeErrorSummary { diff --git a/Analysis/include/Luau/FileResolver.h b/Analysis/include/Luau/FileResolver.h index 3a4c58a6..e3fc292a 100644 --- a/Analysis/include/Luau/FileResolver.h +++ b/Analysis/include/Luau/FileResolver.h @@ -20,7 +20,7 @@ struct SourceCode None, Module, Script, - Local + Local_DEPRECATED }; std::string source; diff --git a/Analysis/include/Luau/Generalization.h b/Analysis/include/Luau/Generalization.h index b2b89c07..4860abe2 100644 --- a/Analysis/include/Luau/Generalization.h +++ b/Analysis/include/Luau/Generalization.h @@ -16,8 +16,24 @@ struct GeneralizationParams Polarity polarity = Polarity::None; }; +template +struct GeneralizationResult +{ + std::optional result; + + // True if the provided type was replaced with a generic. + bool wasReplacedByGeneric = false; + + bool resourceLimitsExceeded = false; + + explicit operator bool() const + { + return bool(result); + } +}; + // Replace a single free type by its bounds according to the polarity provided. -std::optional generalizeType( +GeneralizationResult generalizeType( NotNull arena, NotNull builtinTypes, NotNull scope, @@ -26,7 +42,7 @@ std::optional generalizeType( ); // Generalize one type pack -std::optional generalizeTypePack( +GeneralizationResult generalizeTypePack( NotNull arena, NotNull builtinTypes, NotNull scope, @@ -36,11 +52,31 @@ std::optional generalizeTypePack( void sealTable(NotNull scope, TypeId ty); +/** Attempt to generalize a type. + * + * If generalizationTarget is set, then only that type will be replaced by its + * bounds. The way this is intended to be used is that ty is some function that + * is not fully generalized, and generalizationTarget is a type within its + * signature. There should be no further constraints that could affect the + * bounds of generalizationTarget. + * + * Returns nullopt if generalization failed due to resources limits. + */ std::optional generalize( + NotNull arena, + NotNull builtinTypes, + NotNull scope, + NotNull> cachedTypes, + TypeId ty, + std::optional generalizationTarget = {} +); + +void pruneUnnecessaryGenerics( NotNull arena, NotNull builtinTypes, NotNull scope, NotNull> cachedTypes, TypeId ty ); -} + +} // namespace Luau diff --git a/Analysis/include/Luau/Simplify.h b/Analysis/include/Luau/Simplify.h index aab37876..01c1c9a2 100644 --- a/Analysis/include/Luau/Simplify.h +++ b/Analysis/include/Luau/Simplify.h @@ -24,6 +24,9 @@ SimplifyResult simplifyIntersection(NotNull builtinTypes, NotNull< SimplifyResult simplifyUnion(NotNull builtinTypes, NotNull arena, TypeId left, TypeId right); +SimplifyResult simplifyIntersectWithTruthy(NotNull builtinTypes, NotNull arena, TypeId target); +SimplifyResult simplifyIntersectWithFalsy(NotNull builtinTypes, NotNull arena, TypeId target); + enum class Relation { Disjoint, // No A is a B or vice versa diff --git a/Analysis/include/Luau/Substitution.h b/Analysis/include/Luau/Substitution.h index 28ebc93d..c67f37e2 100644 --- a/Analysis/include/Luau/Substitution.h +++ b/Analysis/include/Luau/Substitution.h @@ -86,6 +86,7 @@ struct TarjanNode struct Tarjan { Tarjan(); + virtual ~Tarjan() = default; // Vertices (types and type packs) are indexed, using pre-order traversal. DenseHashMap typeToIndex{nullptr}; @@ -121,7 +122,7 @@ struct Tarjan void visitChildren(TypePackId tp, int index); void visitChild(TypeId ty); - void visitChild(TypePackId ty); + void visitChild(TypePackId tp); template void visitChild(std::optional ty) @@ -132,7 +133,7 @@ struct Tarjan // Visit the root vertex. TarjanResult visitRoot(TypeId ty); - TarjanResult visitRoot(TypePackId ty); + TarjanResult visitRoot(TypePackId tp); // Used to reuse the object for a new operation void clearTarjan(const TxnLog* log); @@ -150,26 +151,12 @@ struct Tarjan void visitSCC(int index); // Each subclass can decide to ignore some nodes. - virtual bool ignoreChildren(TypeId ty) - { - return false; - } - - virtual bool ignoreChildren(TypePackId ty) - { - return false; - } + virtual bool ignoreChildren(TypeId ty); + virtual bool ignoreChildren(TypePackId ty); // Some subclasses might ignore children visit, but not other actions like replacing the children - virtual bool ignoreChildrenVisit(TypeId ty) - { - return ignoreChildren(ty); - } - - virtual bool ignoreChildrenVisit(TypePackId ty) - { - return ignoreChildren(ty); - } + virtual bool ignoreChildrenVisit(TypeId ty); + virtual bool ignoreChildrenVisit(TypePackId ty); // Subclasses should say which vertices are dirty, // and what to do with dirty vertices. @@ -184,6 +171,7 @@ struct Tarjan struct Substitution : Tarjan { protected: + explicit Substitution(TypeArena* arena); Substitution(const TxnLog* log_, TypeArena* arena); /* @@ -232,28 +220,23 @@ public: virtual TypeId clean(TypeId ty) = 0; virtual TypePackId clean(TypePackId tp) = 0; +protected: // Helper functions to create new types (used by subclasses) template - TypeId addType(const T& tv) + TypeId addType(T tv) { - return arena->addType(tv); + return arena->addType(std::move(tv)); } template - TypePackId addTypePack(const T& tp) + TypePackId addTypePack(T tp) { - return arena->addTypePack(TypePackVar{tp}); + return arena->addTypePack(TypePackVar{std::move(tp)}); } private: template - std::optional replace(std::optional ty) - { - if (ty) - return replace(*ty); - else - return std::nullopt; - } + std::optional replace(std::optional ty); }; } // namespace Luau diff --git a/Analysis/include/Luau/TypeFunction.h b/Analysis/include/Luau/TypeFunction.h index 396fc3c1..444ae3e3 100644 --- a/Analysis/include/Luau/TypeFunction.h +++ b/Analysis/include/Luau/TypeFunction.h @@ -155,6 +155,9 @@ struct TypeFunction /// The reducer function for the type function. ReducerFunction reducer; + + /// If true, this type function can reduce even if it is parameterized on a generic. + bool canReduceGenerics = false; }; /// Represents a type function that may be applied to map a series of types and @@ -167,6 +170,9 @@ struct TypePackFunction /// The reducer function for the type pack function. ReducerFunction reducer; + + /// If true, this type function can reduce even if it is parameterized on a generic. + bool canReduceGenerics = false; }; struct FunctionGraphReductionResult diff --git a/Analysis/include/Luau/TypeInfer.h b/Analysis/include/Luau/TypeInfer.h index 2b8dbc3a..5e817f41 100644 --- a/Analysis/include/Luau/TypeInfer.h +++ b/Analysis/include/Luau/TypeInfer.h @@ -130,6 +130,7 @@ struct TypeChecker const PredicateVec& predicates = {} ); WithPredicate checkExpr(const ScopePtr& scope, const AstExprBinary& expr, std::optional expectedType = std::nullopt); + WithPredicate checkExpr_DEPRECATED(const ScopePtr& scope, const AstExprBinary& expr, std::optional expectedType = std::nullopt); WithPredicate checkExpr(const ScopePtr& scope, const AstExprTypeAssertion& expr); WithPredicate checkExpr(const ScopePtr& scope, const AstExprError& expr); WithPredicate checkExpr(const ScopePtr& scope, const AstExprIfElse& expr, std::optional expectedType = std::nullopt); diff --git a/Analysis/src/AutocompleteCore.cpp b/Analysis/src/AutocompleteCore.cpp index 5d15f751..7918e91f 100644 --- a/Analysis/src/AutocompleteCore.cpp +++ b/Analysis/src/AutocompleteCore.cpp @@ -31,6 +31,7 @@ LUAU_FASTFLAGVARIABLE(LuauAutocompleteRefactorsForIncrementalAutocomplete) LUAU_FASTFLAGVARIABLE(LuauAutocompleteUsesModuleForTypeCompatibility) LUAU_FASTFLAGVARIABLE(LuauAutocompleteUnionCopyPreviousSeen) +LUAU_FASTFLAGVARIABLE(LuauAutocompleteMissingFollows) static const std::unordered_set kStatementStartingKeywords = {"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"}; @@ -83,6 +84,8 @@ static ParenthesesRecommendation getParenRecommendationForIntersect(const Inters ParenthesesRecommendation rec = ParenthesesRecommendation::None; for (Luau::TypeId partId : intersect->parts) { + if (FFlag::LuauAutocompleteMissingFollows) + partId = follow(partId); if (auto partFunc = Luau::get(partId)) { rec = std::max(rec, getParenRecommendationForFunc(partFunc, nodes)); @@ -1623,6 +1626,8 @@ static std::optional autocompleteStringParams( { for (TypeId part : intersect->parts) { + if (FFlag::LuauAutocompleteMissingFollows) + part = follow(part); if (auto candidateFunctionType = Luau::get(part)) { if (std::optional ret = performCallback(candidateFunctionType)) diff --git a/Analysis/src/BuiltinDefinitions.cpp b/Analysis/src/BuiltinDefinitions.cpp index 7c5d8e76..d3c0e093 100644 --- a/Analysis/src/BuiltinDefinitions.cpp +++ b/Analysis/src/BuiltinDefinitions.cpp @@ -30,7 +30,7 @@ */ LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauNonReentrantGeneralization) +LUAU_FASTFLAG(LuauNonReentrantGeneralization2) LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3) LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope) LUAU_FASTFLAGVARIABLE(LuauFollowTableFreeze) @@ -314,8 +314,8 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC TypeArena& arena = globals.globalTypes; NotNull builtinTypes = globals.builtinTypes; - Scope* globalScope = nullptr; // NotNull when removing FFlag::LuauNonReentrantGeneralization - if (FFlag::LuauNonReentrantGeneralization) + Scope* globalScope = nullptr; // NotNull when removing FFlag::LuauNonReentrantGeneralization2 + if (FFlag::LuauNonReentrantGeneralization2) globalScope = globals.globalScope.get(); if (FFlag::LuauSolverV2) @@ -1614,7 +1614,7 @@ bool MagicFreeze::infer(const MagicFunctionCallContext& context) if (resultTy && !get(resultTy)) { // If there's an existing result type but it's _not_ blocked, then - // we aren't type stating this builtin and should fall back to + // we aren't type stating this builtin and should fall back to // regular inference. return false; } diff --git a/Analysis/src/ConstraintGenerator.cpp b/Analysis/src/ConstraintGenerator.cpp index ef4f4690..36482075 100644 --- a/Analysis/src/ConstraintGenerator.cpp +++ b/Analysis/src/ConstraintGenerator.cpp @@ -33,19 +33,16 @@ LUAU_FASTINT(LuauCheckRecursionLimit) LUAU_FASTFLAG(DebugLuauLogSolverToJson) LUAU_FASTFLAG(DebugLuauMagicTypes) -LUAU_FASTFLAG(LuauNonReentrantGeneralization) +LUAU_FASTFLAG(LuauNonReentrantGeneralization2) LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType) LUAU_FASTFLAGVARIABLE(LuauPropagateExpectedTypesForCalls) LUAU_FASTFLAG(DebugLuauGreedyGeneralization) LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTypesOnScope) LUAU_FASTFLAGVARIABLE(LuauUngeneralizedTypesForRecursiveFunctions) -LUAU_FASTFLAGVARIABLE(LuauGlobalSelfAssignmentCycle) LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds) -LUAU_FASTFLAGVARIABLE(LuauInferLocalTypesInMultipleAssignments) LUAU_FASTFLAGVARIABLE(LuauDoNotLeakNilInRefinement) -LUAU_FASTFLAGVARIABLE(LuauExtraFollows) LUAU_FASTFLAG(LuauUserTypeFunTypecheck) LUAU_FASTFLAGVARIABLE(LuauRetainDefinitionAliasLocations) @@ -53,6 +50,9 @@ LUAU_FASTFLAG(LuauDeprecatedAttribute) LUAU_FASTFLAGVARIABLE(LuauCacheInferencePerAstExpr) LUAU_FASTFLAGVARIABLE(LuauAlwaysResolveAstTypes) LUAU_FASTFLAGVARIABLE(LuauWeakNilRefinementType) +LUAU_FASTFLAG(LuauGlobalVariableModuleIsolation) + +LUAU_FASTFLAGVARIABLE(LuauNoTypeFunctionsNamedTypeOf) namespace Luau { @@ -230,7 +230,7 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block) rootScope->location = block->location; module->astScopes[block] = NotNull{scope.get()}; - if (FFlag::LuauNonReentrantGeneralization) + if (FFlag::LuauNonReentrantGeneralization2) interiorFreeTypes.emplace_back(); else DEPRECATED_interiorTypes.emplace_back(); @@ -263,12 +263,12 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block) GeneralizationConstraint{ result, moduleFnTy, - (FFlag::LuauNonReentrantGeneralization || FFlag::LuauTrackInteriorFreeTypesOnScope) ? std::vector{} - : std::move(DEPRECATED_interiorTypes.back()) + (FFlag::LuauNonReentrantGeneralization2 || FFlag::LuauTrackInteriorFreeTypesOnScope) ? std::vector{} + : std::move(DEPRECATED_interiorTypes.back()) } ); - if (FFlag::LuauNonReentrantGeneralization) + if (FFlag::LuauNonReentrantGeneralization2) { scope->interiorFreeTypes = std::move(interiorFreeTypes.back().types); scope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks); @@ -287,7 +287,7 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block) } ); - if (FFlag::LuauNonReentrantGeneralization) + if (FFlag::LuauNonReentrantGeneralization2) interiorFreeTypes.pop_back(); else DEPRECATED_interiorTypes.pop_back(); @@ -319,13 +319,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::LuauNonReentrantGeneralization) + if (FFlag::LuauNonReentrantGeneralization2) interiorFreeTypes.emplace_back(); else DEPRECATED_interiorTypes.emplace_back(); visitBlockWithoutChildScope(resumeScope, block); // Post - if (FFlag::LuauNonReentrantGeneralization) + if (FFlag::LuauNonReentrantGeneralization2) interiorFreeTypes.pop_back(); else DEPRECATED_interiorTypes.pop_back(); @@ -355,7 +355,7 @@ void ConstraintGenerator::visitFragmentRoot(const ScopePtr& resumeScope, AstStat TypeId ConstraintGenerator::freshType(const ScopePtr& scope, Polarity polarity) { - if (FFlag::LuauNonReentrantGeneralization) + if (FFlag::LuauNonReentrantGeneralization2) { auto ft = Luau::freshType(arena, builtinTypes, scope.get(), polarity); interiorFreeTypes.back().types.push_back(ft); @@ -377,7 +377,7 @@ TypePackId ConstraintGenerator::freshTypePack(const ScopePtr& scope, Polarity po { FreeTypePack f{scope.get(), polarity}; TypePackId result = arena->addTypePack(TypePackVar{std::move(f)}); - if (FFlag::LuauNonReentrantGeneralization) + if (FFlag::LuauNonReentrantGeneralization2) interiorFreeTypes.back().typePacks.push_back(result); return result; } @@ -1120,122 +1120,62 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocal* stat TypePackId rvaluePack = checkPack(scope, statLocal->values, expectedTypes).tp; Checkpoint end = checkpoint(this); - if (FFlag::LuauInferLocalTypesInMultipleAssignments) + std::vector deferredTypes; + auto [head, tail] = flatten(rvaluePack); + + for (size_t i = 0; i < statLocal->vars.size; ++i) { - std::vector deferredTypes; - auto [head, tail] = flatten(rvaluePack); + LUAU_ASSERT(get(assignees[i])); + TypeIds* localDomain = localTypes.find(assignees[i]); + LUAU_ASSERT(localDomain); - for (size_t i = 0; i < statLocal->vars.size; ++i) + if (statLocal->vars.data[i]->annotation) { - LUAU_ASSERT(get(assignees[i])); - TypeIds* localDomain = localTypes.find(assignees[i]); - LUAU_ASSERT(localDomain); - - if (statLocal->vars.data[i]->annotation) - { - localDomain->insert(annotatedTypes[i]); - } - else - { - if (i < head.size()) - { - localDomain->insert(head[i]); - } - else if (tail) - { - deferredTypes.push_back(arena->addType(BlockedType{})); - localDomain->insert(deferredTypes.back()); - } - else - { - localDomain->insert(builtinTypes->nilType); - } - } - } - - if (hasAnnotation) - { - TypePackId annotatedPack = arena->addTypePack(std::move(annotatedTypes)); - addConstraint(scope, statLocal->location, PackSubtypeConstraint{rvaluePack, annotatedPack}); - } - - if (!deferredTypes.empty()) - { - LUAU_ASSERT(tail); - NotNull uc = addConstraint(scope, statLocal->location, UnpackConstraint{deferredTypes, *tail}); - - forEachConstraint( - start, - end, - this, - [&uc](const ConstraintPtr& runBefore) - { - uc->dependencies.emplace_back(runBefore.get()); - } - ); - - for (TypeId t : deferredTypes) - getMutable(t)->setOwner(uc); - } - } - else - { - if (hasAnnotation) - { - for (size_t i = 0; i < statLocal->vars.size; ++i) - { - LUAU_ASSERT(get(assignees[i])); - TypeIds* localDomain = localTypes.find(assignees[i]); - LUAU_ASSERT(localDomain); - localDomain->insert(annotatedTypes[i]); - } - - TypePackId annotatedPack = arena->addTypePack(std::move(annotatedTypes)); - addConstraint(scope, statLocal->location, PackSubtypeConstraint{rvaluePack, annotatedPack}); + localDomain->insert(annotatedTypes[i]); } else { - std::vector valueTypes; - valueTypes.reserve(statLocal->vars.size); - - auto [head, tail] = flatten(rvaluePack); - - if (head.size() >= statLocal->vars.size) + if (i < head.size()) { - for (size_t i = 0; i < statLocal->vars.size; ++i) - valueTypes.push_back(head[i]); + localDomain->insert(head[i]); + } + else if (tail) + { + deferredTypes.push_back(arena->addType(BlockedType{})); + localDomain->insert(deferredTypes.back()); } else { - for (size_t i = 0; i < statLocal->vars.size; ++i) - valueTypes.push_back(arena->addType(BlockedType{})); - - auto uc = addConstraint(scope, statLocal->location, UnpackConstraint{valueTypes, rvaluePack}); - - forEachConstraint( - start, - end, - this, - [&uc](const ConstraintPtr& runBefore) - { - uc->dependencies.push_back(NotNull{runBefore.get()}); - } - ); - - for (TypeId t : valueTypes) - getMutable(t)->setOwner(uc); - } - - for (size_t i = 0; i < statLocal->vars.size; ++i) - { - LUAU_ASSERT(get(assignees[i])); - TypeIds* localDomain = localTypes.find(assignees[i]); - LUAU_ASSERT(localDomain); - localDomain->insert(valueTypes[i]); + localDomain->insert(builtinTypes->nilType); } } } + if (hasAnnotation) + { + TypePackId annotatedPack = arena->addTypePack(std::move(annotatedTypes)); + addConstraint(scope, statLocal->location, PackSubtypeConstraint{rvaluePack, annotatedPack}); + } + + if (!deferredTypes.empty()) + { + LUAU_ASSERT(tail); + NotNull uc = addConstraint(scope, statLocal->location, UnpackConstraint{deferredTypes, *tail}); + + forEachConstraint( + start, + end, + this, + [&uc](const ConstraintPtr& runBefore) + { + uc->dependencies.emplace_back(runBefore.get()); + } + ); + + for (TypeId t : deferredTypes) + getMutable(t)->setOwner(uc); + } + if (statLocal->vars.size == 1 && statLocal->values.size == 1 && firstValueType && scope.get() == rootScope && !hasAnnotation) { AstLocal* var = statLocal->vars.data[0]; @@ -1747,7 +1687,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeAlias* if (alias->name == "typeof") { - reportError(alias->location, GenericError{"Type aliases cannot be named typeof"}); + reportError(alias->location, ReservedIdentifier{"typeof"}); return ControlFlow::None; } @@ -1808,6 +1748,14 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio if (!FFlag::LuauUserTypeFunTypecheck) return ControlFlow::None; + if (FFlag::LuauNoTypeFunctionsNamedTypeOf) + { + if (function->name == "typeof") + { + reportError(function->location, ReservedIdentifier{"typeof"}); + } + } + auto scopePtr = astTypeFunctionEnvironmentScopes.find(function); LUAU_ASSERT(scopePtr); @@ -1817,7 +1765,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::LuauNonReentrantGeneralization) + if (FFlag::LuauNonReentrantGeneralization2) interiorFreeTypes.emplace_back(); else DEPRECATED_interiorTypes.push_back(std::vector{}); @@ -1835,7 +1783,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio } ); - if (FFlag::LuauNonReentrantGeneralization) + if (FFlag::LuauNonReentrantGeneralization2) { sig.signatureScope->interiorFreeTypes = std::move(interiorFreeTypes.back().types); sig.signatureScope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks); @@ -1844,7 +1792,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio sig.signatureScope->interiorFreeTypes = std::move(DEPRECATED_interiorTypes.back()); getMutable(generalizedTy)->setOwner(gc); - if (FFlag::LuauNonReentrantGeneralization) + if (FFlag::LuauNonReentrantGeneralization2) interiorFreeTypes.pop_back(); else DEPRECATED_interiorTypes.pop_back(); @@ -2285,7 +2233,7 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall* { std::vector unpackedTypes; if (args.size() > 0) - target = FFlag::LuauExtraFollows ? follow(args[0]) : args[0]; + target = follow(args[0]); else { target = arena->addType(BlockedType{}); @@ -2506,7 +2454,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::LuauNonReentrantGeneralization) + if (FFlag::LuauNonReentrantGeneralization2) { freeTy = freshType(scope, Polarity::Positive); FreeType* ft = getMutable(freeTy); @@ -2534,7 +2482,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantBool* return Inference{singletonType}; TypeId freeTy = nullptr; - if (FFlag::LuauNonReentrantGeneralization) + if (FFlag::LuauNonReentrantGeneralization2) { freeTy = freshType(scope, Polarity::Positive); FreeType* ft = getMutable(freeTy); @@ -2696,7 +2644,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* fun Checkpoint startCheckpoint = checkpoint(this); FunctionSignature sig = checkFunctionSignature(scope, func, expectedType); - if (FFlag::LuauNonReentrantGeneralization) + if (FFlag::LuauNonReentrantGeneralization2) interiorFreeTypes.emplace_back(); else DEPRECATED_interiorTypes.push_back(std::vector{}); @@ -2710,12 +2658,12 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* fun GeneralizationConstraint{ generalizedTy, sig.signature, - (FFlag::LuauNonReentrantGeneralization || FFlag::LuauTrackInteriorFreeTypesOnScope) ? std::vector{} - : std::move(DEPRECATED_interiorTypes.back()) + (FFlag::LuauNonReentrantGeneralization2 || FFlag::LuauTrackInteriorFreeTypesOnScope) ? std::vector{} + : std::move(DEPRECATED_interiorTypes.back()) } ); - if (FFlag::LuauNonReentrantGeneralization) + if (FFlag::LuauNonReentrantGeneralization2) { sig.signatureScope->interiorFreeTypes = std::move(interiorFreeTypes.back().types); sig.signatureScope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks); @@ -3142,12 +3090,9 @@ void ConstraintGenerator::visitLValue(const ScopePtr& scope, AstExprGlobal* glob DefId def = dfg->getDef(global); rootScope->lvalueTypes[def] = rhsType; - if (FFlag::LuauGlobalSelfAssignmentCycle) - { - // Ignore possible self-assignment, it doesn't create a new constraint - if (annotatedTy == follow(rhsType)) - return; - } + // Ignore possible self-assignment, it doesn't create a new constraint + if (annotatedTy == follow(rhsType)) + return; // Sketchy: We're specifically looking for BlockedTypes that were // initially created by ConstraintGenerator::prepopulateGlobalScope. @@ -3210,7 +3155,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr, ttv->definitionLocation = expr->location; ttv->scope = scope.get(); - if (FFlag::LuauNonReentrantGeneralization) + if (FFlag::LuauNonReentrantGeneralization2) interiorFreeTypes.back().types.push_back(ty); else DEPRECATED_interiorTypes.back().push_back(ty); @@ -3373,7 +3318,7 @@ ConstraintGenerator::FunctionSignature ConstraintGenerator::checkFunctionSignatu if (fn->self) { - TypeId selfType = freshType(signatureScope); + TypeId selfType = freshType(signatureScope, Polarity::Negative); argTypes.push_back(selfType); argNames.emplace_back(FunctionArgument{fn->self->name.value, fn->self->location}); signatureScope->bindings[fn->self] = Binding{selfType, fn->self->location}; @@ -4043,14 +3988,14 @@ TypeId ConstraintGenerator::makeIntersect(const ScopePtr& scope, Location locati return resultType; } -struct FragmentTypeCheckGlobalPrepopulator : AstVisitor +struct FragmentTypeCheckGlobalPrepopulator_DEPRECATED : AstVisitor { const NotNull globalScope; const NotNull currentScope; const NotNull dfg; const NotNull arena; - FragmentTypeCheckGlobalPrepopulator( + FragmentTypeCheckGlobalPrepopulator_DEPRECATED( NotNull globalScope, NotNull currentScope, NotNull dfg, @@ -4167,12 +4112,16 @@ struct GlobalPrepopulator : AstVisitor void ConstraintGenerator::prepopulateGlobalScopeForFragmentTypecheck(const ScopePtr& globalScope, const ScopePtr& resumeScope, AstStatBlock* program) { - FragmentTypeCheckGlobalPrepopulator gp{NotNull{globalScope.get()}, NotNull{resumeScope.get()}, dfg, arena}; + if (!FFlag::LuauGlobalVariableModuleIsolation) + { + FragmentTypeCheckGlobalPrepopulator_DEPRECATED gp{NotNull{globalScope.get()}, NotNull{resumeScope.get()}, dfg, arena}; - if (prepareModuleScope) - prepareModuleScope(module->name, resumeScope); + if (prepareModuleScope) + prepareModuleScope(module->name, resumeScope); + + program->visit(&gp); + } - program->visit(&gp); if (FFlag::LuauUserTypeFunTypecheck) { diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index c786f882..c9e52953 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -39,7 +39,7 @@ LUAU_FASTFLAGVARIABLE(LuauHasPropProperBlock) LUAU_FASTFLAGVARIABLE(DebugLuauGreedyGeneralization) LUAU_FASTFLAG(LuauSearchForRefineableType) LUAU_FASTFLAG(LuauDeprecatedAttribute) -LUAU_FASTFLAG(LuauNonReentrantGeneralization) +LUAU_FASTFLAG(LuauNonReentrantGeneralization2) LUAU_FASTFLAG(LuauBidirectionalInferenceCollectIndexerTypes) LUAU_FASTFLAG(LuauNewTypeFunReductionChecks2) LUAU_FASTFLAGVARIABLE(LuauTrackInferredFunctionTypeFromCall) @@ -673,73 +673,20 @@ void ConstraintSolver::generalizeOneType(TypeId ty) if (!freeTy) return; - NotNull tyScope{freeTy->scope}; + TypeId* functionType = scopeToFunction->find(freeTy->scope); + if (!functionType) + return; - // TODO: If freeTy occurs within the enclosing function's type, we need to - // check to see whether this type should instead be generic. + std::optional resultTy = generalize(arena, builtinTypes, NotNull{freeTy->scope}, generalizedTypes, *functionType, ty); - TypeId newBound = follow(freeTy->upperBound); - - TypeId* functionTyPtr = nullptr; - while (true) + if (FFlag::DebugLuauLogSolver) { - functionTyPtr = scopeToFunction->find(tyScope); - if (functionTyPtr || !tyScope->parent) - break; - else if (tyScope->parent) - tyScope = NotNull{tyScope->parent.get()}; - else - break; - } - - if (ty == newBound) - ty = builtinTypes->unknownType; - - if (!functionTyPtr) - { - asMutable(ty)->reassign(Type{BoundType{follow(freeTy->upperBound)}}); - } - else - { - const TypeId functionTy = follow(*functionTyPtr); - FunctionType* const function = getMutable(functionTy); - LUAU_ASSERT(function); - - TypeSearcher ts{ty}; - ts.traverse(functionTy); - - const TypeId upperBound = follow(freeTy->upperBound); - const TypeId lowerBound = follow(freeTy->lowerBound); - - switch (ts.result) - { - case Polarity::None: - asMutable(ty)->reassign(Type{BoundType{upperBound}}); - break; - - case Polarity::Negative: - case Polarity::Mixed: - if (get(upperBound) && ts.count > 1) - { - asMutable(ty)->reassign(Type{GenericType{tyScope}}); - function->generics.emplace_back(ty); - } - else - asMutable(ty)->reassign(Type{BoundType{upperBound}}); - break; - - case Polarity::Positive: - if (get(lowerBound) && ts.count > 1) - { - asMutable(ty)->reassign(Type{GenericType{tyScope}}); - function->generics.emplace_back(ty); - } - else - asMutable(ty)->reassign(Type{BoundType{lowerBound}}); - break; - default: - LUAU_ASSERT(!"Unreachable"); - } + printf( + "Eagerly generalized %s (now %s)\n\tin function %s\n", + saveme.c_str(), + toString(ty, opts).c_str(), + toString(resultTy.value_or(*functionType), opts).c_str() + ); } } @@ -755,7 +702,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::LuauNonReentrantGeneralization) + if (FFlag::LuauNonReentrantGeneralization2) trackInteriorFreeType(constraint->scope, ty); return; @@ -890,6 +837,7 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNullscope, generalizedTypes, *generalizedTy); if (get(generalizedType)) bind(constraint, generalizedType, *generalizedTy); else @@ -918,7 +866,7 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNullscope->interiorFreeTypes) // NOLINT(bugprone-unchecked-optional-access) { - if (FFlag::LuauNonReentrantGeneralization) + if (FFlag::LuauNonReentrantGeneralization2) { ty = follow(ty); if (auto freeTy = get(ty)) @@ -928,7 +876,9 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNullpolarity; - generalizeType(arena, builtinTypes, constraint->scope, ty, params); + GeneralizationResult res = generalizeType(arena, builtinTypes, constraint->scope, ty, params); + if (res.resourceLimitsExceeded) + reportError(CodeTooComplex{}, constraint->scope->location); // FIXME: We don't have a very good location for this. } else if (get(ty)) sealTable(constraint->scope, ty); @@ -938,7 +888,7 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNullscope->interiorFreeTypePacks) { @@ -1544,7 +1494,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNullscope, freeTy); @@ -1944,7 +1894,7 @@ bool ConstraintSolver::tryDispatchHasIndexer( FreeType freeResult{tt->scope, builtinTypes->neverType, builtinTypes->unknownType, Polarity::Mixed}; emplace(constraint, resultType, freeResult); - if (FFlag::LuauNonReentrantGeneralization) + if (FFlag::LuauNonReentrantGeneralization2) trackInteriorFreeType(constraint->scope, resultType); tt->indexer = TableIndexer{indexType, resultType}; diff --git a/Analysis/src/Error.cpp b/Analysis/src/Error.cpp index 4b302eff..a4304cf3 100644 --- a/Analysis/src/Error.cpp +++ b/Analysis/src/Error.cpp @@ -18,7 +18,7 @@ #include LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10) -LUAU_FASTFLAG(LuauNonStrictFuncDefErrorFix) +LUAU_FASTFLAG(DebugLuauGreedyGeneralization) static std::string wrongNumberOfArgsString( size_t expectedCount, @@ -70,7 +70,7 @@ namespace Luau { // this list of binary operator type functions is used for better stringification of type functions errors -static const std::unordered_map kBinaryOps{ +static const std::unordered_map DEPRECATED_kBinaryOps{ {"add", "+"}, {"sub", "-"}, {"mul", "*"}, @@ -86,12 +86,27 @@ static const std::unordered_map kBinaryOps{ {"eq", "== or ~="} }; +static const std::unordered_map kBinaryOps{ + {"add", "+"}, + {"sub", "-"}, + {"mul", "*"}, + {"div", "/"}, + {"idiv", "//"}, + {"pow", "^"}, + {"mod", "%"}, + {"concat", ".."}, + {"lt", "< or >="}, + {"le", "<= or >"}, + {"eq", "== or ~="} +}; + // this list of unary operator type functions is used for better stringification of type functions errors static const std::unordered_map kUnaryOps{{"unm", "-"}, {"len", "#"}, {"not", "not"}}; // this list of type functions will receive a special error indicating that the user should file a bug on the GitHub repository // putting a type function in this list indicates that it is expected to _always_ reduce -static const std::unordered_set kUnreachableTypeFunctions{"refine", "singleton", "union", "intersect"}; +static const std::unordered_set DEPRECATED_kUnreachableTypeFunctions{"refine", "singleton", "union", "intersect"}; +static const std::unordered_set kUnreachableTypeFunctions{"refine", "singleton", "union", "intersect", "and", "or"}; struct ErrorConverter { @@ -643,7 +658,8 @@ struct ErrorConverter } // binary operators - if (auto binaryString = kBinaryOps.find(tfit->function->name); binaryString != kBinaryOps.end()) + const auto binaryOps = FFlag::DebugLuauGreedyGeneralization ? kBinaryOps : DEPRECATED_kBinaryOps; + if (auto binaryString = binaryOps.find(tfit->function->name); binaryString != binaryOps.end()) { std::string result = "Operator '" + std::string(binaryString->second) + "' could not be applied to operands of types "; @@ -697,10 +713,10 @@ struct ErrorConverter "'"; } - if (kUnreachableTypeFunctions.count(tfit->function->name)) + if ((FFlag::DebugLuauGreedyGeneralization ? kUnreachableTypeFunctions : DEPRECATED_kUnreachableTypeFunctions).count(tfit->function->name)) { return "Type function instance " + Luau::toString(e.ty) + " is uninhabited\n" + - "This is likely to be a bug, please report it at https://github.com/luau-lang/luau/issues"; + "This is likely to be a bug, please report it at https://github.com/luau-lang/luau/issues"; } // Everything should be specialized above to report a more descriptive error that hopefully does not mention "type functions" explicitly. @@ -756,7 +772,7 @@ struct ErrorConverter std::string operator()(const NonStrictFunctionDefinitionError& e) const { - if (FFlag::LuauNonStrictFuncDefErrorFix && e.functionName.empty()) + if (e.functionName.empty()) { return "Argument " + e.argument + " with type '" + toString(e.argumentType) + "' is used in a way that will run time error"; } @@ -803,6 +819,11 @@ struct ErrorConverter return e.message; } + std::string operator()(const ReservedIdentifier& e) const + { + return e.name + " cannot be used as an identifier for a type function or alias"; + } + std::string operator()(const CannotAssignToNever& e) const { std::string result = "Cannot assign a value of type " + toString(e.rhsType) + " to a field of type never"; @@ -1190,6 +1211,11 @@ bool UserDefinedTypeFunctionError::operator==(const UserDefinedTypeFunctionError return message == rhs.message; } +bool ReservedIdentifier::operator==(const ReservedIdentifier& rhs) const +{ + return name == rhs.name; +} + bool CannotAssignToNever::operator==(const CannotAssignToNever& rhs) const { if (cause.size() != rhs.cause.size()) @@ -1409,6 +1435,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 static_assert(always_false_v, "Non-exhaustive type switch"); } diff --git a/Analysis/src/FragmentAutocomplete.cpp b/Analysis/src/FragmentAutocomplete.cpp index 722db46a..463d3c2d 100644 --- a/Analysis/src/FragmentAutocomplete.cpp +++ b/Analysis/src/FragmentAutocomplete.cpp @@ -36,13 +36,13 @@ LUAU_FASTFLAGVARIABLE(LuauBetterCursorInCommentDetection) LUAU_FASTFLAGVARIABLE(LuauAllFreeTypesHaveScopes) LUAU_FASTFLAGVARIABLE(LuauPersistConstraintGenerationScopes) LUAU_FASTFLAGVARIABLE(LuauCloneTypeAliasBindings) -LUAU_FASTFLAGVARIABLE(LuauCloneReturnTypePack) LUAU_FASTFLAGVARIABLE(LuauIncrementalAutocompleteDemandBasedCloning) LUAU_FASTFLAG(LuauUserTypeFunTypecheck) LUAU_FASTFLAGVARIABLE(LuauFragmentNoTypeFunEval) LUAU_FASTFLAGVARIABLE(LuauBetterScopeSelection) LUAU_FASTFLAGVARIABLE(LuauBlockDiffFragmentSelection) LUAU_FASTFLAGVARIABLE(LuauFragmentAcMemoryLeak) +LUAU_FASTFLAGVARIABLE(LuauGlobalVariableModuleIsolation) namespace { @@ -569,12 +569,32 @@ struct UsageFinder : public AstVisitor return true; } + bool visit(AstExprGlobal* global) override + { + if (FFlag::LuauGlobalVariableModuleIsolation) + globalDefsToPrePopulate.emplace_back(global->name, dfg->getDef(global)); + return true; + } + + bool visit(AstStatFunction* function) override + { + if (FFlag::LuauGlobalVariableModuleIsolation) + { + if (AstExprGlobal* g = function->name->as()) + globalFunctionsReferenced.emplace_back(g->name); + } + + return true; + } + NotNull dfg; DenseHashSet declaredAliases{""}; std::vector> localBindingsReferenced; DenseHashSet mentionedDefs{nullptr}; std::vector referencedBindings{""}; std::vector> referencedImportedBindings{{"", ""}}; + std::vector> globalDefsToPrePopulate; + std::vector globalFunctionsReferenced; }; // Runs the `UsageFinder` traversal on the fragment and grabs all of the types that are @@ -648,7 +668,45 @@ void cloneTypesFromFragment( } } - // Finally - clone the returnType on the staleScope. This helps avoid potential leaks of free types. + if (FFlag::LuauGlobalVariableModuleIsolation) + { + // Fourth - prepopulate the global function types + for (const auto& name : f.globalFunctionsReferenced) + { + if (auto ty = staleModule->getModuleScope()->lookup(name)) + { + destScope->bindings[name] = Binding{Luau::cloneIncremental(*ty, *destArena, cloneState, destScope)}; + } + else + { + TypeId bt = destArena->addType(BlockedType{}); + destScope->bindings[name] = Binding{bt}; + } + } + + // Fifth - prepopulate the globals here + for (const auto& [name, def] : f.globalDefsToPrePopulate) + { + if (auto ty = staleModule->getModuleScope()->lookup(name)) + { + destScope->lvalueTypes[def] = Luau::cloneIncremental(*ty, *destArena, cloneState, destScope); + } + else if (auto ty = destScope->lookup(name)) + { + // This branch is a little strange - we are looking up a symbol in the destScope + // This scope has no parent pointer, and only cloned types are written to it, so this is a + // safe operation to do without cloning. + // The reason we do this, is the usage finder will traverse the global functions referenced first + // If there is no name associated with this function at the global scope, it must appear first in the fragment and we must + // create a blocked type for it. We write this blocked type directly into the `destScope` bindings + // Then when we go to traverse the `AstExprGlobal` associated with this function, we need to ensure that we map the def -> blockedType + // in `lvalueTypes`, which was previously written into `destScope` + destScope->lvalueTypes[def] = *ty; + } + } + } + + // Finally, clone the returnType on the staleScope. This helps avoid potential leaks of free types. if (staleScope->returnType) destScope->returnType = Luau::cloneIncremental(staleScope->returnType, *destArena, cloneState, destScope); } @@ -820,7 +878,7 @@ void cloneAndSquashScopes( } } - if (FFlag::LuauCloneReturnTypePack && destScope->returnType) + if (destScope->returnType) destScope->returnType = Luau::cloneIncremental(destScope->returnType, *destArena, cloneState, destScope); return; @@ -1452,7 +1510,7 @@ FragmentTypeCheckResult typecheckFragment_( SimplifierPtr simplifier = newSimplifier(NotNull{&incrementalModule->internalTypes}, frontend.builtinTypes); FrontendModuleResolver& resolver = getModuleResolver(frontend, opts); - + std::shared_ptr freshChildOfNearestScope = std::make_shared(nullptr); /// Contraint Generator ConstraintGenerator cg{ incrementalModule, @@ -1462,7 +1520,7 @@ FragmentTypeCheckResult typecheckFragment_( NotNull{&resolver}, frontend.builtinTypes, iceHandler, - stale->getModuleScope(), + FFlag::LuauGlobalVariableModuleIsolation ? freshChildOfNearestScope : stale->getModuleScope(), frontend.globals.globalTypeFunctionScope, nullptr, nullptr, @@ -1471,7 +1529,6 @@ FragmentTypeCheckResult typecheckFragment_( }; CloneState cloneState{frontend.builtinTypes}; - std::shared_ptr freshChildOfNearestScope = std::make_shared(nullptr); incrementalModule->scopes.emplace_back(root->location, freshChildOfNearestScope); freshChildOfNearestScope->interiorFreeTypes.emplace(); freshChildOfNearestScope->interiorFreeTypePacks.emplace(); diff --git a/Analysis/src/Frontend.cpp b/Analysis/src/Frontend.cpp index 81df13d8..7167fdf0 100644 --- a/Analysis/src/Frontend.cpp +++ b/Analysis/src/Frontend.cpp @@ -46,7 +46,6 @@ LUAU_FASTFLAGVARIABLE(DebugLuauForbidInternalTypes) LUAU_FASTFLAGVARIABLE(DebugLuauForceStrictMode) LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode) -LUAU_FASTFLAGVARIABLE(LuauSelectivelyRetainDFGArena) LUAU_FASTFLAG(LuauTypeFunResultInAutocomplete) namespace Luau @@ -1003,11 +1002,8 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item) freeze(module->interfaceTypes); module->internalTypes.clear(); - if (FFlag::LuauSelectivelyRetainDFGArena) - { - module->defArena.allocator.clear(); - module->keyArena.allocator.clear(); - } + module->defArena.allocator.clear(); + module->keyArena.allocator.clear(); module->astTypes.clear(); module->astTypePacks.clear(); diff --git a/Analysis/src/Generalization.cpp b/Analysis/src/Generalization.cpp index 71f82ba2..ca77c8dd 100644 --- a/Analysis/src/Generalization.cpp +++ b/Analysis/src/Generalization.cpp @@ -11,11 +11,13 @@ #include "Luau/Type.h" #include "Luau/TypeArena.h" #include "Luau/TypePack.h" +#include "Luau/Substitution.h" #include "Luau/VisitType.h" +LUAU_FASTFLAG(DebugLuauGreedyGeneralization) LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete) -LUAU_FASTFLAGVARIABLE(LuauNonReentrantGeneralization) +LUAU_FASTFLAGVARIABLE(LuauNonReentrantGeneralization2) namespace Luau { @@ -468,7 +470,7 @@ struct FreeTypeSearcher : TypeVisitor bool visit(TypeId ty, const FreeType& ft) override { - if (FFlag::LuauNonReentrantGeneralization) + if (FFlag::LuauNonReentrantGeneralization2) { if (!subsumes(scope, ft.scope)) return true; @@ -519,7 +521,7 @@ struct FreeTypeSearcher : TypeVisitor if ((tt.state == TableState::Free || tt.state == TableState::Unsealed) && subsumes(scope, tt.scope)) { - if (FFlag::LuauNonReentrantGeneralization) + if (FFlag::LuauNonReentrantGeneralization2) unsealedTables.insert(ty); else { @@ -558,7 +560,7 @@ struct FreeTypeSearcher : TypeVisitor if (tt.indexer) { - if (FFlag::LuauNonReentrantGeneralization) + if (FFlag::LuauNonReentrantGeneralization2) { // {[K]: V} is equivalent to three functions: get, set, and iterate // @@ -616,7 +618,7 @@ struct FreeTypeSearcher : TypeVisitor if (!subsumes(scope, ftp.scope)) return true; - if (FFlag::LuauNonReentrantGeneralization) + if (FFlag::LuauNonReentrantGeneralization2) { GeneralizationParams& params = typePacks[tp]; ++params.useCount; @@ -1092,6 +1094,118 @@ struct TypeCacher : TypeOnceVisitor } }; +struct RemoveType : Substitution // NOLINT +{ + NotNull builtinTypes; + TypeId needle; + + RemoveType(NotNull builtinTypes, TypeArena* arena, TypeId needle) + : Substitution(arena) + , builtinTypes(builtinTypes) + , needle(needle) + { + } + + bool ignoreChildren(TypeId ty) override + { + if (get(ty) || get(ty)) + return false; + else + return true; + } + + bool isDirty(TypeId ty) override + { + // A union or intersection is dirty if it contains the needle or if it has any duplicate members. + if (auto ut = get(ty)) + { + DenseHashSet distinctParts{nullptr}; + size_t count = 0; + for (TypeId part : ut) + { + ++count; + if (part == needle) + return true; + distinctParts.insert(follow(part)); + } + return distinctParts.size() != count; + } + else if (auto it = get(ty)) + { + DenseHashSet distinctParts{nullptr}; + size_t count = 0; + for (TypeId part : it) + { + ++count; + if (part == needle) + return true; + distinctParts.insert(follow(part)); + } + return distinctParts.size() != count; + } + + return false; + } + + bool isDirty(TypePackId tp) override + { + return false; + } + + TypeId clean(TypeId ty) override + { + if (auto ut = get(ty)) + { + OrderedSet newParts; + + for (TypeId ty : ut) + { + if (ty != needle) + newParts.insert(ty); + } + + if (newParts.empty()) + return builtinTypes->neverType; + else if (newParts.size() == 1) + { + TypeId onlyType = *newParts.begin(); + LUAU_ASSERT(onlyType != needle); + return onlyType; + } + else + return arena->addType(UnionType{newParts.takeVector()}); + } + else if (auto it = get(ty)) + { + OrderedSet newParts; + + for (TypeId ty : it) + { + if (ty != needle) + newParts.insert(ty); + } + + if (newParts.empty()) + return builtinTypes->unknownType; + else if (newParts.size() == 1) + { + TypeId onlyType = *newParts.begin(); + LUAU_ASSERT(onlyType != needle); + return onlyType; + } + else + return arena->addType(IntersectionType{newParts.takeVector()}); + } + else + return ty; + } + + TypePackId clean(TypePackId tp) override + { + return tp; + } +}; + /** * Remove occurrences of `needle` within `haystack`. This is used to cull cyclic bounds from free types. * @@ -1099,84 +1213,14 @@ struct TypeCacher : TypeOnceVisitor * @param needle The type to be removed. */ [[nodiscard]] -static TypeId removeType(NotNull arena, NotNull builtinTypes, DenseHashSet& seen, TypeId haystack, TypeId needle) +static std::optional< + TypeId> removeType(NotNull arena, NotNull builtinTypes, TypeId haystack, TypeId needle) { - haystack = follow(haystack); - - if (seen.find(haystack)) - return haystack; - seen.insert(haystack); - - if (const UnionType* ut = get(haystack)) - { - OrderedSet newOptions; - - for (TypeId option : ut) - { - if (option == needle) - continue; - - if (get(option)) - continue; - - LUAU_ASSERT(!get(option)); - - if (get(option)) - newOptions.insert(removeType(arena, builtinTypes, seen, option, needle)); - else - newOptions.insert(option); - } - - if (newOptions.empty()) - return builtinTypes->neverType; - else if (newOptions.size() == 1) - { - TypeId onlyType = *newOptions.begin(); - LUAU_ASSERT(onlyType != haystack); - return onlyType; - } - else - return arena->addType(UnionType{newOptions.takeVector()}); - } - - if (const IntersectionType* it = get(haystack)) - { - OrderedSet newParts; - - for (TypeId part : it) - { - part = follow(part); - - if (part == needle) - continue; - - if (get(part)) - continue; - - LUAU_ASSERT(!get(follow(part))); - - if (get(part)) - newParts.insert(removeType(arena, builtinTypes, seen, part, needle)); - else - newParts.insert(part); - } - - if (newParts.empty()) - return builtinTypes->unknownType; - else if (newParts.size() == 1) - { - TypeId onlyType = *newParts.begin(); - LUAU_ASSERT(onlyType != needle); - return onlyType; - } - else - return arena->addType(IntersectionType{newParts.takeVector()}); - } - - return haystack; + RemoveType rt{builtinTypes, arena, needle}; + return rt.substitute(haystack); } -std::optional generalizeType( +GeneralizationResult generalizeType( NotNull arena, NotNull builtinTypes, NotNull scope, @@ -1189,7 +1233,7 @@ std::optional generalizeType( FreeType* ft = getMutable(freeTy); LUAU_ASSERT(ft); - LUAU_ASSERT(isPositive(params.polarity) || isNegative(params.polarity)); + LUAU_ASSERT(isKnown(params.polarity)); const bool hasLowerBound = !get(follow(ft->lowerBound)); const bool hasUpperBound = !get(follow(ft->upperBound)); @@ -1198,12 +1242,12 @@ std::optional generalizeType( if (!hasLowerBound && !hasUpperBound) { - if ((params.polarity != Polarity::Mixed && params.useCount == 1) || !isWithinFunction) + if (!isWithinFunction || (!FFlag::DebugLuauGreedyGeneralization && (params.polarity != Polarity::Mixed && params.useCount == 1))) emplaceType(asMutable(freeTy), builtinTypes->unknownType); else { emplaceType(asMutable(freeTy), scope, params.polarity); - return freeTy; + return {freeTy, /*wasReplacedByGeneric*/ true}; } } // It is possible that this free type has other free types in its upper @@ -1219,20 +1263,24 @@ std::optional generalizeType( lowerFree->upperBound = builtinTypes->unknownType; else { - DenseHashSet replaceSeen{nullptr}; - lb = removeType(arena, builtinTypes, replaceSeen, lb, freeTy); + std::optional removed = removeType(arena, builtinTypes, lb, freeTy); + if (removed) + lb = *removed; + else + return {std::nullopt, false, /*resourceLimitsExceeded*/ true}; + ft->lowerBound = lb; } if (follow(lb) != freeTy) emplaceType(asMutable(freeTy), lb); - else if (!isWithinFunction || params.useCount == 1) + else if (!isWithinFunction || (!FFlag::DebugLuauGreedyGeneralization && params.useCount == 1)) emplaceType(asMutable(freeTy), builtinTypes->unknownType); else { // if the lower bound is the type in question (eg 'a <: 'a), we don't actually have a lower bound. emplaceType(asMutable(freeTy), scope, params.polarity); - return freeTy; + return {freeTy, /*wasReplacedByGeneric*/ true}; } } else @@ -1243,8 +1291,11 @@ std::optional generalizeType( else { // If the free type appears within its own upper bound, cull that cycle. - DenseHashSet replaceSeen{nullptr}; - ub = removeType(arena, builtinTypes, replaceSeen, ub, freeTy); + std::optional removed = removeType(arena, builtinTypes, ub, freeTy); + if (removed) + ub = *removed; + else + return {std::nullopt, false, /*resourceLimitsExceeded*/ true}; ft->upperBound = ub; } @@ -1256,14 +1307,14 @@ std::optional generalizeType( { // if the upper bound is the type in question, we don't actually have an upper bound. emplaceType(asMutable(freeTy), scope, params.polarity); - return freeTy; + return {freeTy, /*wasReplacedByGeneric*/ true}; } } - return std::nullopt; + return {freeTy, /*wasReplacedByGeneric*/ false}; } -std::optional generalizeTypePack( +GeneralizationResult generalizeTypePack( NotNull arena, NotNull builtinTypes, NotNull scope, @@ -1274,24 +1325,24 @@ std::optional generalizeTypePack( tp = follow(tp); if (tp->owningArena != arena) - return std::nullopt; + return {tp, /*wasReplacedByGeneric*/ false}; const FreeTypePack* ftp = get(tp); if (!ftp) - return std::nullopt; + return {tp, /*wasReplacedByGeneric*/ false}; if (!subsumes(scope, ftp->scope)) - return std::nullopt; + return {tp, /*wasReplacedByGeneric*/ false}; if (1 == params.useCount) emplaceTypePack(asMutable(tp), builtinTypes->unknownTypePack); else { emplaceTypePack(asMutable(tp), scope, params.polarity); - return tp; + return {tp, /*wasReplacedByGeneric*/ true}; } - return std::nullopt; + return {tp, /*wasReplacedByGeneric*/ false}; } void sealTable(NotNull scope, TypeId ty) @@ -1312,7 +1363,8 @@ std::optional generalize( NotNull builtinTypes, NotNull scope, NotNull> cachedTypes, - TypeId ty + TypeId ty, + std::optional generalizationTarget ) { ty = follow(ty); @@ -1323,7 +1375,7 @@ std::optional generalize( FreeTypeSearcher fts{scope, cachedTypes}; fts.traverse(ty); - if (FFlag::LuauNonReentrantGeneralization) + if (FFlag::LuauNonReentrantGeneralization2) { FunctionType* functionTy = getMutable(ty); auto pushGeneric = [&](TypeId t) @@ -1340,8 +1392,15 @@ std::optional generalize( for (const auto& [freeTy, params] : fts.types) { - if (std::optional genericTy = generalizeType(arena, builtinTypes, scope, freeTy, params)) - pushGeneric(*genericTy); + if (!generalizationTarget || freeTy == *generalizationTarget) + { + GeneralizationResult res = generalizeType(arena, builtinTypes, scope, freeTy, params); + if (res.resourceLimitsExceeded) + return std::nullopt; + + if (res && res.wasReplacedByGeneric) + pushGeneric(*res.result); + } } for (TypeId unsealedTableTy : fts.unsealedTables) @@ -1350,10 +1409,16 @@ std::optional generalize( for (const auto& [freePackId, params] : fts.typePacks) { TypePackId freePack = follow(freePackId); - std::optional generalizedTp = generalizeTypePack(arena, builtinTypes, scope, freePack, params); + if (!generalizationTarget) + { + GeneralizationResult generalizedTp = generalizeTypePack(arena, builtinTypes, scope, freePack, params); - if (generalizedTp) - pushGenericPack(freePack); + if (generalizedTp.resourceLimitsExceeded) + return std::nullopt; + + if (generalizedTp && generalizedTp.wasReplacedByGeneric) + pushGenericPack(freePack); + } } TypeCacher cacher{cachedTypes}; @@ -1397,4 +1462,121 @@ std::optional generalize( return ty; } +struct GenericCounter : TypeVisitor +{ + NotNull> cachedTypes; + DenseHashMap generics{nullptr}; + DenseHashMap genericPacks{nullptr}; + + explicit GenericCounter(NotNull> cachedTypes) + : cachedTypes(cachedTypes) + { + } + + bool visit(TypeId ty, const GenericType&) override + { + size_t* count = generics.find(ty); + if (count) + ++*count; + + return false; + } + + bool visit(TypePackId tp, const GenericTypePack&) override + { + size_t* count = genericPacks.find(tp); + if (count) + ++*count; + + return false; + } +}; + +void pruneUnnecessaryGenerics( + NotNull arena, + NotNull builtinTypes, + NotNull scope, + NotNull> cachedTypes, + TypeId ty +) +{ + if (!FFlag::DebugLuauGreedyGeneralization) + return; + + ty = follow(ty); + + if (ty->owningArena != arena || ty->persistent) + return; + + FunctionType* functionTy = getMutable(ty); + + if (!functionTy) + return; + + // Types (and packs) to be removed from the generics list + DenseHashSet clipTypes{nullptr}; + DenseHashSet clipTypePacks{nullptr}; + + GenericCounter counter{cachedTypes}; + for (TypeId generic : functionTy->generics) + { + auto g = get(generic); + LUAU_ASSERT(g); + if (!g) + clipTypes.insert(generic); + else if (!g->explicitName) + counter.generics[generic] = 0; + } + for (TypePackId genericPack : functionTy->genericPacks) + { + auto g = get(genericPack); + if (!g) + clipTypePacks.insert(genericPack); + else if (!g->explicitName) + counter.genericPacks[genericPack] = 0; + } + + counter.traverse(ty); + + for (const auto& [generic, count] : counter.generics) + { + if (count == 1) + { + emplaceType(asMutable(generic), builtinTypes->unknownType); + clipTypes.insert(generic); + } + } + + auto it = std::remove_if( + functionTy->generics.begin(), + functionTy->generics.end(), + [&](TypeId ty) + { + return clipTypes.contains(ty); + } + ); + + functionTy->generics.erase(it, functionTy->generics.end()); + + for (const auto& [genericPack, count] : counter.genericPacks) + { + if (count == 1) + { + emplaceTypePack(asMutable(genericPack), builtinTypes->unknownTypePack); + clipTypePacks.insert(genericPack); + } + } + + auto it2 = std::remove_if( + functionTy->genericPacks.begin(), + functionTy->genericPacks.end(), + [&](TypePackId tp) + { + return clipTypePacks.contains(tp); + } + ); + + functionTy->genericPacks.erase(it2, functionTy->genericPacks.end()); +} + } // namespace Luau diff --git a/Analysis/src/InferPolarity.cpp b/Analysis/src/InferPolarity.cpp index 3399abcf..aae80a2d 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(LuauNonReentrantGeneralization) +LUAU_FASTFLAG(LuauNonReentrantGeneralization2) namespace Luau { @@ -133,7 +133,7 @@ struct InferPolarity : TypeVisitor template static void inferGenericPolarities_(NotNull arena, NotNull scope, TID ty) { - if (!FFlag::LuauNonReentrantGeneralization) + if (!FFlag::LuauNonReentrantGeneralization2) return; InferPolarity infer{arena, scope}; diff --git a/Analysis/src/IostreamHelpers.cpp b/Analysis/src/IostreamHelpers.cpp index 64e05993..6e0c64d0 100644 --- a/Analysis/src/IostreamHelpers.cpp +++ b/Analysis/src/IostreamHelpers.cpp @@ -229,6 +229,8 @@ static void errorToString(std::ostream& stream, const T& err) stream << "UnexpectedTypePackInSubtyping { tp = '" + toString(err.tp) + "' }"; else if constexpr (std::is_same_v) stream << "UserDefinedTypeFunctionError { " << err.message << " }"; + else if constexpr (std::is_same_v) + stream << "ReservedIdentifier { " << err.name << " }"; else if constexpr (std::is_same_v) { stream << "CannotAssignToNever { rvalueType = '" << toString(err.rhsType) << "', reason = '" << err.reason << "', cause = { "; diff --git a/Analysis/src/NonStrictTypeChecker.cpp b/Analysis/src/NonStrictTypeChecker.cpp index de39e6fb..3ee7fce4 100644 --- a/Analysis/src/NonStrictTypeChecker.cpp +++ b/Analysis/src/NonStrictTypeChecker.cpp @@ -20,10 +20,12 @@ #include #include +LUAU_FASTFLAG(DebugLuauMagicTypes) + LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds) LUAU_FASTFLAGVARIABLE(LuauNonStrictVisitorImprovements) LUAU_FASTFLAGVARIABLE(LuauNewNonStrictWarnOnUnknownGlobals) -LUAU_FASTFLAGVARIABLE(LuauNonStrictFuncDefErrorFix) +LUAU_FASTFLAGVARIABLE(LuauNewNonStrictVisitTypes) namespace Luau { @@ -335,7 +337,15 @@ struct NonStrictTypeChecker // local x ; B generates the context of B without x visit(local); for (auto local : local->vars) + { ctx.remove(dfg->getDef(local)); + + if (FFlag::LuauNewNonStrictVisitTypes) + { + if (local->annotation) + visit(local->annotation); + } + } } else ctx = NonStrictContext::disjunction(builtinTypes, arena, visit(stat), ctx); @@ -420,6 +430,10 @@ struct NonStrictTypeChecker NonStrictContext visit(AstStatFor* forStatement) { + if (FFlag::LuauNewNonStrictVisitTypes) + if (forStatement->var->annotation) + visit(forStatement->var->annotation); + if (FFlag::LuauNonStrictVisitorImprovements) { // TODO: throwing out context based on same principle as existing code? @@ -439,6 +453,15 @@ struct NonStrictTypeChecker NonStrictContext visit(AstStatForIn* forInStatement) { + if (FFlag::LuauNewNonStrictVisitTypes) + { + for (auto var : forInStatement->vars) + { + if (var->annotation) + visit(var->annotation); + } + } + if (FFlag::LuauNonStrictVisitorImprovements) { for (AstExpr* rhs : forInStatement->values) @@ -487,6 +510,12 @@ struct NonStrictTypeChecker NonStrictContext visit(AstStatTypeAlias* typeAlias) { + if (FFlag::LuauNewNonStrictVisitTypes) + { + visitGenerics(typeAlias->generics, typeAlias->genericPacks); + visit(typeAlias->type); + } + return {}; } @@ -497,16 +526,38 @@ struct NonStrictTypeChecker NonStrictContext visit(AstStatDeclareFunction* declFn) { + if (FFlag::LuauNewNonStrictVisitTypes) + { + visitGenerics(declFn->generics, declFn->genericPacks); + visit(declFn->params); + visit(declFn->retTypes); + } + return {}; } NonStrictContext visit(AstStatDeclareGlobal* declGlobal) { + if (FFlag::LuauNewNonStrictVisitTypes) + visit(declGlobal->type); + return {}; } NonStrictContext visit(AstStatDeclareClass* declClass) { + if (FFlag::LuauNewNonStrictVisitTypes) + { + if (declClass->indexer) + { + visit(declClass->indexer->indexType); + visit(declClass->indexer->resultType); + } + + for (auto prop : declClass->props) + visit(prop.ty); + } + return {}; } @@ -766,18 +817,29 @@ struct NonStrictTypeChecker { if (std::optional ty = willRunTimeErrorFunctionDefinition(local, remainder)) { - if (FFlag::LuauNonStrictFuncDefErrorFix) - { - const char* debugname = exprFn->debugname.value; - reportError(NonStrictFunctionDefinitionError{debugname ? debugname : "", local->name.value, *ty}, local->location); - } - else - { - reportError(NonStrictFunctionDefinitionError{exprFn->debugname.value, local->name.value, *ty}, local->location); - } + const char* debugname = exprFn->debugname.value; + reportError(NonStrictFunctionDefinitionError{debugname ? debugname : "", local->name.value, *ty}, local->location); } remainder.remove(dfg->getDef(local)); + + if (FFlag::LuauNewNonStrictVisitTypes) + { + if (local->annotation) + visit(local->annotation); + } } + + if (FFlag::LuauNewNonStrictVisitTypes) + { + visitGenerics(exprFn->generics, exprFn->genericPacks); + + if (exprFn->returnAnnotation) + visit(*exprFn->returnAnnotation); + + if (exprFn->varargAnnotation) + visit(exprFn->varargAnnotation); + } + return remainder; } @@ -818,6 +880,9 @@ struct NonStrictTypeChecker NonStrictContext visit(AstExprTypeAssertion* typeAssertion) { + if (FFlag::LuauNewNonStrictVisitTypes) + visit(typeAssertion->annotation); + if (FFlag::LuauNonStrictVisitorImprovements) return visit(typeAssertion->expr, ValueContext::RValue); else @@ -854,6 +919,323 @@ struct NonStrictTypeChecker return {}; } + void visit(AstType* ty) + { + LUAU_ASSERT(FFlag::LuauNewNonStrictVisitTypes); + + if (auto t = ty->as()) + return visit(t); + else if (auto t = ty->as()) + return visit(t); + else if (auto t = ty->as()) + return visit(t); + else if (auto t = ty->as()) + return visit(t); + else if (auto t = ty->as()) + return visit(t); + else if (auto t = ty->as()) + return visit(t); + else if (auto t = ty->as()) + return visit(t->type); + } + + void visit(AstTypeReference* ty) + { + // No further validation is necessary in this case. The main logic for + // _luau_print is contained in lookupAnnotation. + if (FFlag::DebugLuauMagicTypes && ty->name == "_luau_print") + return; + + for (const AstTypeOrPack& param : ty->parameters) + { + if (param.type) + visit(param.type); + else + visit(param.typePack); + } + + Scope* scope = findInnermostScope(ty->location); + LUAU_ASSERT(scope); + + std::optional alias = ty->prefix ? scope->lookupImportedType(ty->prefix->value, ty->name.value) : scope->lookupType(ty->name.value); + + if (alias.has_value()) + { + size_t typesRequired = alias->typeParams.size(); + size_t packsRequired = alias->typePackParams.size(); + + bool hasDefaultTypes = std::any_of( + alias->typeParams.begin(), + alias->typeParams.end(), + [](auto&& el) + { + return el.defaultValue.has_value(); + } + ); + + bool hasDefaultPacks = std::any_of( + alias->typePackParams.begin(), + alias->typePackParams.end(), + [](auto&& el) + { + return el.defaultValue.has_value(); + } + ); + + if (!ty->hasParameterList) + { + if ((!alias->typeParams.empty() && !hasDefaultTypes) || (!alias->typePackParams.empty() && !hasDefaultPacks)) + reportError(GenericError{"Type parameter list is required"}, ty->location); + } + + size_t typesProvided = 0; + size_t extraTypes = 0; + size_t packsProvided = 0; + + for (const AstTypeOrPack& p : ty->parameters) + { + if (p.type) + { + if (packsProvided != 0) + { + reportError(GenericError{"Type parameters must come before type pack parameters"}, ty->location); + continue; + } + + if (typesProvided < typesRequired) + typesProvided += 1; + else + extraTypes += 1; + } + else if (p.typePack) + { + std::optional tp = lookupPackAnnotation(p.typePack); + if (!tp.has_value()) + continue; + + if (typesProvided < typesRequired && size(*tp) == 1 && finite(*tp) && first(*tp)) + typesProvided += 1; + else + packsProvided += 1; + } + } + + if (extraTypes != 0 && packsProvided == 0) + { + // Extra types are only collected into a pack if a pack is expected + if (packsRequired != 0) + packsProvided += 1; + else + typesProvided += extraTypes; + } + + for (size_t i = typesProvided; i < typesRequired; ++i) + { + if (alias->typeParams[i].defaultValue) + typesProvided += 1; + } + + for (size_t i = packsProvided; i < packsRequired; ++i) + { + if (alias->typePackParams[i].defaultValue) + packsProvided += 1; + } + + if (extraTypes == 0 && packsProvided + 1 == packsRequired) + packsProvided += 1; + + + if (typesProvided != typesRequired || packsProvided != packsRequired) + { + reportError( + IncorrectGenericParameterCount{ + /* name */ ty->name.value, + /* typeFun */ *alias, + /* actualParameters */ typesProvided, + /* actualPackParameters */ packsProvided, + }, + ty->location + ); + } + } + else + { + if (scope->lookupPack(ty->name.value)) + { + reportError( + SwappedGenericTypeParameter{ + ty->name.value, + SwappedGenericTypeParameter::Kind::Type, + }, + ty->location + ); + } + else + { + std::string symbol = ""; + if (ty->prefix) + { + symbol += (*(ty->prefix)).value; + symbol += "."; + } + symbol += ty->name.value; + + reportError(UnknownSymbol{symbol, UnknownSymbol::Context::Type}, ty->location); + } + } + } + + void visit(AstTypeTable* table) + { + if (table->indexer) + { + visit(table->indexer->indexType); + visit(table->indexer->resultType); + } + + for (auto prop : table->props) + visit(prop.type); + } + + void visit(AstTypeFunction* function) + { + visit(function->argTypes); + visit(function->returnTypes); + } + + void visit(AstTypeTypeof* typeOf) + { + visit(typeOf->expr, ValueContext::RValue); + } + + void visit(AstTypeUnion* unionType) + { + for (auto typ : unionType->types) + visit(typ); + } + + void visit(AstTypeIntersection* intersectionType) + { + for (auto typ : intersectionType->types) + visit(typ); + } + + void visit(AstTypeList& list) + { + for (auto typ : list.types) + visit(typ); + if (list.tailType) + visit(list.tailType); + } + + void visit(AstTypePack* pack) + { + LUAU_ASSERT(FFlag::LuauNewNonStrictVisitTypes); + + if (auto p = pack->as()) + return visit(p); + else if (auto p = pack->as()) + return visit(p); + else if (auto p = pack->as()) + return visit(p); + } + + void visit(AstTypePackExplicit* tp) + { + for (AstType* type : tp->typeList.types) + visit(type); + + if (tp->typeList.tailType) + visit(tp->typeList.tailType); + } + + void visit(AstTypePackVariadic* tp) + { + visit(tp->variadicType); + } + + void visit(AstTypePackGeneric* tp) + { + Scope* scope = findInnermostScope(tp->location); + LUAU_ASSERT(scope); + + std::optional alias = scope->lookupPack(tp->genericName.value); + if (!alias.has_value()) + { + if (scope->lookupType(tp->genericName.value)) + { + reportError( + SwappedGenericTypeParameter{ + tp->genericName.value, + SwappedGenericTypeParameter::Kind::Pack, + }, + tp->location + ); + } + } + else + { + reportError(UnknownSymbol{tp->genericName.value, UnknownSymbol::Context::Type}, tp->location); + } + } + + void visitGenerics(AstArray generics, AstArray genericPacks) + { + DenseHashSet seen{AstName{}}; + + for (const auto* g : generics) + { + if (seen.contains(g->name)) + reportError(DuplicateGenericParameter{g->name.value}, g->location); + else + seen.insert(g->name); + + if (g->defaultValue) + visit(g->defaultValue); + } + + for (const auto* g : genericPacks) + { + if (seen.contains(g->name)) + reportError(DuplicateGenericParameter{g->name.value}, g->location); + else + seen.insert(g->name); + + if (g->defaultValue) + visit(g->defaultValue); + } + } + + Scope* findInnermostScope(Location location) const + { + Scope* bestScope = module->getModuleScope().get(); + + bool didNarrow; + do + { + didNarrow = false; + for (auto scope : bestScope->children) + { + if (scope->location.encloses(location)) + { + bestScope = scope.get(); + didNarrow = true; + break; + } + } + } while (didNarrow && bestScope->children.size() > 0); + + return bestScope; + } + + std::optional lookupPackAnnotation(AstTypePack* annotation) const + { + TypePackId* tp = module->astResolvedTypePacks.find(annotation); + if (tp != nullptr) + return {follow(*tp)}; + return {}; + } + void reportError(TypeErrorData data, const Location& location) { module->errors.emplace_back(location, module->name, std::move(data)); diff --git a/Analysis/src/Normalize.cpp b/Analysis/src/Normalize.cpp index 575a2051..3754f5a3 100644 --- a/Analysis/src/Normalize.cpp +++ b/Analysis/src/Normalize.cpp @@ -17,15 +17,11 @@ LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant) -LUAU_FASTFLAGVARIABLE(LuauNormalizeNegatedErrorToAnError) -LUAU_FASTFLAGVARIABLE(LuauNormalizeIntersectErrorToAnError) LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000) LUAU_FASTINTVARIABLE(LuauNormalizeIntersectionLimit, 200) LUAU_FASTINTVARIABLE(LuauNormalizeUnionLimit, 100) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAGVARIABLE(LuauFixInfiniteRecursionInNormalization) -LUAU_FASTFLAGVARIABLE(LuauNormalizedBufferIsNotUnknown) -LUAU_FASTFLAGVARIABLE(LuauNormalizeLimitFunctionSet) LUAU_FASTFLAGVARIABLE(LuauNormalizationCatchMetatableCycles) namespace Luau @@ -308,9 +304,7 @@ bool NormalizedType::isUnknown() const // Otherwise, we can still be unknown! bool hasAllPrimitives = isPrim(booleans, PrimitiveType::Boolean) && isPrim(nils, PrimitiveType::NilType) && isNumber(numbers) && - strings.isString() && - (FFlag::LuauNormalizedBufferIsNotUnknown ? isThread(threads) && isBuffer(buffers) - : isPrim(threads, PrimitiveType::Thread) && isThread(threads)); + strings.isString() && isThread(threads) && isBuffer(buffers); // Check is class bool isTopClass = false; @@ -1691,12 +1685,9 @@ NormalizationResult Normalizer::unionNormals(NormalizedType& here, const Normali return res; } - if (FFlag::LuauNormalizeLimitFunctionSet) - { - // Limit based on worst-case expansion of the function unions - if (here.functions.parts.size() * there.functions.parts.size() >= size_t(FInt::LuauNormalizeUnionLimit)) - return NormalizationResult::HitLimits; - } + // Limit based on worst-case expansion of the function unions + if (here.functions.parts.size() * there.functions.parts.size() >= size_t(FInt::LuauNormalizeUnionLimit)) + return NormalizationResult::HitLimits; here.booleans = unionOfBools(here.booleans, there.booleans); unionClasses(here.classes, there.classes); @@ -3087,11 +3078,8 @@ NormalizationResult Normalizer::intersectNormals(NormalizedType& here, const Nor if (here.tables.size() * there.tables.size() >= size_t(FInt::LuauNormalizeIntersectionLimit)) return NormalizationResult::HitLimits; - if (FFlag::LuauNormalizeLimitFunctionSet) - { - if (here.functions.parts.size() * there.functions.parts.size() >= size_t(FInt::LuauNormalizeIntersectionLimit)) - return NormalizationResult::HitLimits; - } + if (here.functions.parts.size() * there.functions.parts.size() >= size_t(FInt::LuauNormalizeIntersectionLimit)) + return NormalizationResult::HitLimits; here.booleans = intersectionOfBools(here.booleans, there.booleans); @@ -3228,7 +3216,7 @@ NormalizationResult Normalizer::intersectNormalWithTy( { TypeId errors = here.errors; clearNormal(here); - here.errors = FFlag::LuauNormalizeIntersectErrorToAnError && get(errors) ? errors : there; + here.errors = get(errors) ? errors : there; } else if (const PrimitiveType* ptv = get(there)) { @@ -3325,12 +3313,12 @@ NormalizationResult Normalizer::intersectNormalWithTy( clearNormal(here); return NormalizationResult::True; } - else if (FFlag::LuauNormalizeNegatedErrorToAnError && get(t)) + else if (get(t)) { // ~error is still an error, so intersecting with the negation is the same as intersecting with a type TypeId errors = here.errors; clearNormal(here); - here.errors = FFlag::LuauNormalizeIntersectErrorToAnError && get(errors) ? errors : t; + here.errors = get(errors) ? errors : t; } else if (auto nt = get(t)) { diff --git a/Analysis/src/OverloadResolution.cpp b/Analysis/src/OverloadResolution.cpp index 35e65ca1..342aee95 100644 --- a/Analysis/src/OverloadResolution.cpp +++ b/Analysis/src/OverloadResolution.cpp @@ -10,6 +10,8 @@ #include "Luau/TypeUtils.h" #include "Luau/Unifier2.h" +LUAU_FASTFLAGVARIABLE(LuauArityMismatchOnUndersaturatedUnknownArguments) + namespace Luau { @@ -254,15 +256,32 @@ std::pair OverloadResolver::checkOverload_ } // If any of the unsatisfied arguments are not supertypes of - // nil, then this overload does not match. + // nil or are `unknown`, then this overload does not match. for (size_t i = firstUnsatisfiedArgument; i < requiredHead.size(); ++i) { - if (!subtyping.isSubtype(builtinTypes->nilType, requiredHead[i], scope).isSubtype) + if (FFlag::LuauArityMismatchOnUndersaturatedUnknownArguments) { - auto [minParams, optMaxParams] = getParameterExtents(TxnLog::empty(), fn->argTypes); - TypeError error{fnExpr->location, CountMismatch{minParams, optMaxParams, args->head.size(), CountMismatch::Arg, isVariadic}}; + if (get(follow(requiredHead[i])) || !subtyping.isSubtype(builtinTypes->nilType, requiredHead[i], scope).isSubtype) + { + auto [minParams, optMaxParams] = getParameterExtents(TxnLog::empty(), fn->argTypes); + for (auto arg : fn->argTypes) + if (get(follow(arg))) + minParams += 1; - return {Analysis::ArityMismatch, {error}}; + TypeError error{fnExpr->location, CountMismatch{minParams, optMaxParams, args->head.size(), CountMismatch::Arg, isVariadic}}; + + return {Analysis::ArityMismatch, {error}}; + } + } + else + { + if (!subtyping.isSubtype(builtinTypes->nilType, requiredHead[i], scope).isSubtype) + { + auto [minParams, optMaxParams] = getParameterExtents(TxnLog::empty(), fn->argTypes); + TypeError error{fnExpr->location, CountMismatch{minParams, optMaxParams, args->head.size(), CountMismatch::Arg, isVariadic}}; + + return {Analysis::ArityMismatch, {error}}; + } } } diff --git a/Analysis/src/Simplify.cpp b/Analysis/src/Simplify.cpp index 8a0483e6..7c5188f7 100644 --- a/Analysis/src/Simplify.cpp +++ b/Analysis/src/Simplify.cpp @@ -14,8 +14,9 @@ LUAU_FASTINT(LuauTypeReductionRecursionLimit) LUAU_FASTFLAG(LuauSolverV2) -LUAU_DYNAMIC_FASTINTVARIABLE(LuauSimplificationComplexityLimit, 8); -LUAU_FASTFLAGVARIABLE(LuauFlagBasicIntersectFollows); +LUAU_DYNAMIC_FASTINTVARIABLE(LuauSimplificationComplexityLimit, 8) +LUAU_FASTFLAGVARIABLE(LuauSimplificationRecheckAssumption) +LUAU_FASTFLAGVARIABLE(LuauOptimizeFalsyAndTruthyIntersect) namespace Luau { @@ -47,6 +48,8 @@ struct TypeSimplifier // Attempt to intersect the two types. Does not recurse. Does not handle // unions, intersections, or negations. std::optional basicIntersect(TypeId left, TypeId right); + std::optional basicIntersectWithTruthy(TypeId target) const; + std::optional basicIntersectWithFalsy(TypeId target) const; TypeId intersect(TypeId left, TypeId right); TypeId union_(TypeId left, TypeId right); @@ -707,7 +710,9 @@ TypeId TypeSimplifier::intersectUnionWithType(TypeId left, TypeId right) bool changed = false; std::set newParts; - if (leftUnion->options.size() > (size_t)DFInt::LuauSimplificationComplexityLimit) + size_t maxSize = DFInt::LuauSimplificationComplexityLimit; + + if (leftUnion->options.size() > maxSize) return arena->addType(IntersectionType{{left, right}}); for (TypeId part : leftUnion) @@ -722,6 +727,13 @@ TypeId TypeSimplifier::intersectUnionWithType(TypeId left, TypeId right) } newParts.insert(simplified); + + if (FFlag::LuauSimplificationRecheckAssumption) + { + // Initial combination size check could not predict nested union iteration + if (newParts.size() > maxSize) + return arena->addType(IntersectionType{{left, right}}); + } } if (!changed) @@ -762,6 +774,13 @@ TypeId TypeSimplifier::intersectUnions(TypeId left, TypeId right) continue; newParts.insert(simplified); + + if (FFlag::LuauSimplificationRecheckAssumption) + { + // Initial combination size check could not predict nested union iteration + if (newParts.size() > maxSize) + return arena->addType(IntersectionType{{left, right}}); + } } } @@ -840,6 +859,78 @@ TypeId TypeSimplifier::intersectNegatedUnion(TypeId left, TypeId right) return intersectFromParts(std::move(newParts)); } +std::optional TypeSimplifier::basicIntersectWithTruthy(TypeId target) const +{ + target = follow(target); + + if (is(target)) + return builtinTypes->truthyType; + + if (is(target)) + // any = *error-type* | unknown, so truthy & any = *error-type* | truthy + return arena->addType(UnionType{{builtinTypes->truthyType, builtinTypes->errorType}}); + + if (is(target)) + return target; + + if (is(target)) + return target; + + if (auto pt = get(target)) + { + switch (pt->type) + { + case PrimitiveType::NilType: + return builtinTypes->neverType; + case PrimitiveType::Boolean: + return builtinTypes->trueType; + default: + return target; + } + } + + if (auto st = get(target)) + return st->variant == BooleanSingleton{false} ? builtinTypes->neverType : target; + + return std::nullopt; +} + +std::optional TypeSimplifier::basicIntersectWithFalsy(TypeId target) const +{ + target = follow(target); + + if (is(target)) + return target; + + if (is(target)) + // any = *error-type* | unknown, so falsy & any = *error-type* | falsy + return arena->addType(UnionType{{builtinTypes->falsyType, builtinTypes->errorType}}); + + if (is(target)) + return builtinTypes->falsyType; + + if (is(target)) + return builtinTypes->neverType; + + if (auto pt = get(target)) + { + switch (pt->type) + { + case PrimitiveType::NilType: + return builtinTypes->nilType; + case PrimitiveType::Boolean: + return builtinTypes->falseType; + default: + return builtinTypes->neverType; + } + } + + if (auto st = get(target)) + return st->variant == BooleanSingleton{false} ? builtinTypes->falseType : builtinTypes->neverType; + + return std::nullopt; +} + TypeId TypeSimplifier::intersectTypeWithNegation(TypeId left, TypeId right) { const NegationType* leftNegation = get(left); @@ -1066,11 +1157,8 @@ TypeId TypeSimplifier::intersectIntersectionWithType(TypeId left, TypeId right) std::optional TypeSimplifier::basicIntersect(TypeId left, TypeId right) { - if (FFlag::LuauFlagBasicIntersectFollows) - { - left = follow(left); - right = follow(right); - } + left = follow(left); + right = follow(right); if (get(left) && get(right)) return right; @@ -1179,6 +1267,25 @@ std::optional TypeSimplifier::basicIntersect(TypeId left, TypeId right) return std::nullopt; } + if (FFlag::LuauOptimizeFalsyAndTruthyIntersect) + { + if (isTruthyType(left)) + if (auto res = basicIntersectWithTruthy(right)) + return res; + + if (isTruthyType(right)) + if (auto res = basicIntersectWithTruthy(left)) + return res; + + if (isFalsyType(left)) + if (auto res = basicIntersectWithFalsy(right)) + return res; + + if (isFalsyType(right)) + if (auto res = basicIntersectWithFalsy(left)) + return res; + } + Relation relation = relate(left, right); if (left == right || Relation::Coincident == relation) return left; diff --git a/Analysis/src/Substitution.cpp b/Analysis/src/Substitution.cpp index c6ffcecb..4736c5ee 100644 --- a/Analysis/src/Substitution.cpp +++ b/Analysis/src/Substitution.cpp @@ -2,12 +2,10 @@ #include "Luau/Substitution.h" #include "Luau/Common.h" -#include "Luau/Clone.h" #include "Luau/TxnLog.h" #include "Luau/Type.h" #include -#include LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000) LUAU_FASTFLAG(LuauSolverV2) @@ -18,9 +16,9 @@ LUAU_FASTFLAG(LuauDeprecatedAttribute) namespace Luau { -static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log, bool alwaysClone) +static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log) { - auto go = [ty, &dest, alwaysClone](auto&& a) + auto go = [ty, &dest](auto&& a) { using T = std::decay_t; @@ -140,13 +138,8 @@ static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log, bool a } else if constexpr (std::is_same_v) { - if (alwaysClone) - { - ClassType clone{a.name, a.props, a.parent, a.metatable, a.tags, a.userData, a.definitionModuleName, a.definitionLocation, a.indexer}; - return dest.addType(std::move(clone)); - } - else - return ty; + ClassType clone{a.name, a.props, a.parent, a.metatable, a.tags, a.userData, a.definitionModuleName, a.definitionLocation, a.indexer}; + return dest.addType(std::move(clone)); } else if constexpr (std::is_same_v) return dest.addType(NegationType{a.ty}); @@ -547,6 +540,27 @@ void Tarjan::visitSCC(int index) } } +bool Tarjan::ignoreChildren(TypeId ty) +{ + return false; +} + +bool Tarjan::ignoreChildren(TypePackId ty) +{ + return false; +} + +// Some subclasses might ignore children visit, but not other actions like replacing the children +bool Tarjan::ignoreChildrenVisit(TypeId ty) +{ + return ignoreChildren(ty); +} + +bool Tarjan::ignoreChildrenVisit(TypePackId ty) +{ + return ignoreChildren(ty); +} + TarjanResult Tarjan::findDirty(TypeId ty) { return visitRoot(ty); @@ -557,6 +571,11 @@ TarjanResult Tarjan::findDirty(TypePackId tp) return visitRoot(tp); } +Substitution::Substitution(TypeArena* arena) + : Substitution(TxnLog::empty(), arena) +{ +} + Substitution::Substitution(const TxnLog* log_, TypeArena* arena) : arena(arena) { @@ -657,7 +676,7 @@ void Substitution::resetState(const TxnLog* log, TypeArena* arena) TypeId Substitution::clone(TypeId ty) { - return shallowClone(ty, *arena, log, /* alwaysClone */ true); + return shallowClone(ty, *arena, log); } TypePackId Substitution::clone(TypePackId tp) @@ -873,4 +892,13 @@ void Substitution::replaceChildren(TypePackId tp) } } +template +std::optional Substitution::replace(std::optional ty) +{ + if (ty) + return replace(*ty); + else + return std::nullopt; +} + } // namespace Luau diff --git a/Analysis/src/Subtyping.cpp b/Analysis/src/Subtyping.cpp index e7bc6b0f..b3bac468 100644 --- a/Analysis/src/Subtyping.cpp +++ b/Analysis/src/Subtyping.cpp @@ -22,7 +22,6 @@ #include LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity) -LUAU_FASTFLAGVARIABLE(LuauSubtypingStopAtNormFail) LUAU_FASTINTVARIABLE(LuauSubtypingReasoningLimit, 100) LUAU_FASTFLAGVARIABLE(LuauSubtypingEnableReasoningLimit) @@ -424,7 +423,7 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy, NotNullnormalize(subTy), normalizer->normalize(superTy), scope); - if (FFlag::LuauSubtypingStopAtNormFail && semantic.normalizationTooComplex) + if (semantic.normalizationTooComplex) { result = semantic; } @@ -630,7 +629,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub { SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy), scope); - if (FFlag::LuauSubtypingStopAtNormFail && semantic.normalizationTooComplex) + if (semantic.normalizationTooComplex) { result = semantic; } @@ -1110,7 +1109,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub { SubtypingResult next = isCovariantWith(env, subTy, ty, scope); - if (FFlag::LuauSubtypingStopAtNormFail && next.normalizationTooComplex) + if (next.normalizationTooComplex) return SubtypingResult{false, /* normalizationTooComplex */ true}; if (next.isSubtype) @@ -1134,7 +1133,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Unio { subtypings.push_back(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++, TypePath::Index::Variant::Union})); - if (FFlag::LuauSubtypingStopAtNormFail && subtypings.back().normalizationTooComplex) + if (subtypings.back().normalizationTooComplex) return SubtypingResult{false, /* normalizationTooComplex */ true}; } @@ -1150,7 +1149,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub { subtypings.push_back(isCovariantWith(env, subTy, ty, scope).withSuperComponent(TypePath::Index{i++, TypePath::Index::Variant::Intersection})); - if (FFlag::LuauSubtypingStopAtNormFail && subtypings.back().normalizationTooComplex) + if (subtypings.back().normalizationTooComplex) return SubtypingResult{false, /* normalizationTooComplex */ true}; } @@ -1166,7 +1165,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Inte { subtypings.push_back(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++, TypePath::Index::Variant::Intersection})); - if (FFlag::LuauSubtypingStopAtNormFail && subtypings.back().normalizationTooComplex) + if (subtypings.back().normalizationTooComplex) return SubtypingResult{false, /* normalizationTooComplex */ true}; } @@ -1812,7 +1811,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type { results.back().orElse(isCovariantWith(env, subTy, superTy, scope)); - if (FFlag::LuauSubtypingStopAtNormFail && results.back().normalizationTooComplex) + if (results.back().normalizationTooComplex) return SubtypingResult{false, /* normalizationTooComplex */ true}; } } diff --git a/Analysis/src/TableLiteralInference.cpp b/Analysis/src/TableLiteralInference.cpp index 36fcc34c..a94eaf59 100644 --- a/Analysis/src/TableLiteralInference.cpp +++ b/Analysis/src/TableLiteralInference.cpp @@ -13,7 +13,6 @@ #include "Luau/TypeUtils.h" #include "Luau/Unifier2.h" -LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceUpcast) LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceCollectIndexerTypes) LUAU_FASTFLAGVARIABLE(LuauBidirectionalFailsafe) LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceElideAssert) @@ -142,13 +141,8 @@ TypeId matchLiteralType( if (!isLiteral(expr)) { - if (FFlag::LuauBidirectionalInferenceUpcast) - { - auto result = subtyping->isSubtype(/*subTy=*/exprType, /*superTy=*/expectedType, unifier->scope); - return result.isSubtype ? expectedType : exprType; - } - else - return exprType; + auto result = subtyping->isSubtype(/*subTy=*/exprType, /*superTy=*/expectedType, unifier->scope); + return result.isSubtype ? expectedType : exprType; } expectedType = follow(expectedType); @@ -239,7 +233,7 @@ TypeId matchLiteralType( } - if (FFlag::LuauBidirectionalInferenceUpcast && expr->is()) + if (expr->is()) { // TODO: Push argument / return types into the lambda. For now, just do // the non-literal thing: check for a subtype and upcast if valid. diff --git a/Analysis/src/ToString.cpp b/Analysis/src/ToString.cpp index 1e3fedea..20ed7a11 100644 --- a/Analysis/src/ToString.cpp +++ b/Analysis/src/ToString.cpp @@ -21,6 +21,7 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAGVARIABLE(LuauSyntheticErrors) +LUAU_FASTFLAGVARIABLE(LuauStringPartLengthLimit) /* * Enables increasing levels of verbosity for Luau type names when stringifying. @@ -910,6 +911,9 @@ struct TypeStringifier bool hasNonNilDisjunct = false; std::vector results = {}; + size_t resultsLength = 0; + bool lengthLimitHit = false; + for (auto el : &uv) { el = follow(el); @@ -936,14 +940,34 @@ struct TypeStringifier if (needParens) state.emit(")"); + if (FFlag::LuauStringPartLengthLimit) + resultsLength += state.result.name.length(); + results.push_back(std::move(state.result.name)); + state.result.name = std::move(saved); + + if (FFlag::LuauStringPartLengthLimit) + { + lengthLimitHit = state.opts.maxTypeLength > 0 && resultsLength > state.opts.maxTypeLength; + + if (lengthLimitHit) + break; + } } state.unsee(&uv); - if (!FFlag::DebugLuauToStringNoLexicalSort) - std::sort(results.begin(), results.end()); + if (FFlag::LuauStringPartLengthLimit) + { + if (!lengthLimitHit && !FFlag::DebugLuauToStringNoLexicalSort) + std::sort(results.begin(), results.end()); + } + else + { + if (!FFlag::DebugLuauToStringNoLexicalSort) + std::sort(results.begin(), results.end()); + } if (optional && results.size() > 1) state.emit("("); @@ -987,6 +1011,9 @@ struct TypeStringifier } std::vector results = {}; + size_t resultsLength = 0; + bool lengthLimitHit = false; + for (auto el : uv.parts) { el = follow(el); @@ -1003,14 +1030,34 @@ struct TypeStringifier if (needParens) state.emit(")"); + if (FFlag::LuauStringPartLengthLimit) + resultsLength += state.result.name.length(); + results.push_back(std::move(state.result.name)); + state.result.name = std::move(saved); + + if (FFlag::LuauStringPartLengthLimit) + { + lengthLimitHit = state.opts.maxTypeLength > 0 && resultsLength > state.opts.maxTypeLength; + + if (lengthLimitHit) + break; + } } state.unsee(&uv); - if (!FFlag::DebugLuauToStringNoLexicalSort) - std::sort(results.begin(), results.end()); + if (FFlag::LuauStringPartLengthLimit) + { + if (!lengthLimitHit && !FFlag::DebugLuauToStringNoLexicalSort) + std::sort(results.begin(), results.end()); + } + else + { + if (!FFlag::DebugLuauToStringNoLexicalSort) + std::sort(results.begin(), results.end()); + } bool first = true; bool shouldPlaceOnNewlines = results.size() > state.opts.compositeTypesSingleLineLimit || isOverloadedFunction(ty); diff --git a/Analysis/src/Transpiler.cpp b/Analysis/src/Transpiler.cpp index 5cb7f58a..7ff4cf68 100644 --- a/Analysis/src/Transpiler.cpp +++ b/Analysis/src/Transpiler.cpp @@ -2646,6 +2646,7 @@ struct Printer { advance(item.indexerOpenPosition); writer.symbol("["); + advance(item.stringPosition); writer.sourceString( std::string_view(item.stringInfo->sourceString.data, item.stringInfo->sourceString.size), item.stringInfo->quoteStyle, diff --git a/Analysis/src/TypeFunction.cpp b/Analysis/src/TypeFunction.cpp index 3b5263f6..45edb903 100644 --- a/Analysis/src/TypeFunction.cpp +++ b/Analysis/src/TypeFunction.cpp @@ -48,7 +48,8 @@ LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyUseGuesserDepth, -1); LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(LuauTypeFunResultInAutocomplete) -LUAU_FASTFLAG(LuauNonReentrantGeneralization) +LUAU_FASTFLAG(LuauNonReentrantGeneralization2) +LUAU_FASTFLAG(DebugLuauGreedyGeneralization) LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies) LUAU_FASTFLAGVARIABLE(LuauMetatableTypeFunctions) @@ -65,6 +66,8 @@ LUAU_FASTFLAGVARIABLE(LuauSimplyRefineNotNil) LUAU_FASTFLAGVARIABLE(LuauIndexDeferPendingIndexee) LUAU_FASTFLAGVARIABLE(LuauNewTypeFunReductionChecks2) LUAU_FASTFLAGVARIABLE(LuauReduceUnionFollowUnionType) +LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect) +LUAU_FASTFLAGVARIABLE(LuauNarrowIntersectionNevers) namespace Luau { @@ -309,9 +312,22 @@ struct TypeFunctionReducer enum class SkipTestResult { + /// If a type function is cyclic, it cannot be reduced, but maybe we can + /// make a guess and offer a suggested annotation to the user. CyclicTypeFunction, + + /// Indicase that we will not be able to reduce this type function this + /// time. Constraint resolution may cause this type function to become + /// reducible later. Irreducible, + + /// Some type functions can operate on generic parameters + Generic, + + /// We might be able to reduce this type function, but not yet. Defer, + + /// We can attempt to reduce this type function right now. Okay, }; @@ -334,7 +350,10 @@ struct TypeFunctionReducer } else if (is(ty)) { - return SkipTestResult::Irreducible; + if (FFlag::DebugLuauGreedyGeneralization) + return SkipTestResult::Generic; + else + return SkipTestResult::Irreducible; } return SkipTestResult::Okay; @@ -353,7 +372,10 @@ struct TypeFunctionReducer } else if (is(ty)) { - return SkipTestResult::Irreducible; + if (FFlag::DebugLuauGreedyGeneralization) + return SkipTestResult::Generic; + else + return SkipTestResult::Irreducible; } return SkipTestResult::Okay; @@ -435,7 +457,7 @@ struct TypeFunctionReducer { SkipTestResult skip = testForSkippability(p); - if (skip == SkipTestResult::Irreducible) + if (skip == SkipTestResult::Irreducible || (skip == SkipTestResult::Generic && !tfit->function->canReduceGenerics)) { if (FFlag::DebugLuauLogTypeFamilies) printf("%s is irreducible due to a dependency on %s\n", toString(subject, {true}).c_str(), toString(p, {true}).c_str()); @@ -461,7 +483,7 @@ struct TypeFunctionReducer { SkipTestResult skip = testForSkippability(p); - if (skip == SkipTestResult::Irreducible) + if (skip == SkipTestResult::Irreducible || (skip == SkipTestResult::Generic && !tfit->function->canReduceGenerics)) { if (FFlag::DebugLuauLogTypeFamilies) printf("%s is irreducible due to a dependency on %s\n", toString(subject, {true}).c_str(), toString(p, {true}).c_str()); @@ -1221,7 +1243,7 @@ TypeFunctionReductionResult unmTypeFunction( if (isPending(operandTy, ctx->solver)) return {std::nullopt, Reduction::MaybeOk, {operandTy}, {}}; - if (FFlag::LuauNonReentrantGeneralization) + if (FFlag::LuauNonReentrantGeneralization2) operandTy = follow(operandTy); std::shared_ptr normTy = ctx->normalizer->normalize(operandTy); @@ -2163,6 +2185,44 @@ struct ContainsRefinableType : TypeOnceVisitor } }; +namespace +{ +bool isApproximateFalsy(TypeId ty) +{ + ty = follow(ty); + bool seenNil = false; + bool seenFalse = false; + if (auto ut = get(ty)) + { + for (auto option : ut) + { + if (auto pt = get(option); pt && pt->type == PrimitiveType::NilType) + seenNil = true; + else if (auto st = get(option); st && st->variant == BooleanSingleton{false}) + seenFalse = true; + else + return false; + } + } + return seenFalse && seenNil; +} + +bool isApproximateTruthy(TypeId ty) +{ + ty = follow(ty); + if (auto nt = get(ty)) + return isApproximateFalsy(nt->ty); + return false; +} + +bool isSimpleDiscriminant(TypeId ty) +{ + ty = follow(ty); + return isApproximateTruthy(ty) || isApproximateFalsy(ty); +} + +} + TypeFunctionReductionResult refineTypeFunction( TypeId instance, const std::vector& typeParams, @@ -2257,16 +2317,37 @@ TypeFunctionReductionResult refineTypeFunction( } } - // If the target type is a table, then simplification already implements the logic to deal with refinements properly since the - // type of the discriminant is guaranteed to only ever be an (arbitrarily-nested) table of a single property type. - if (get(target)) + if (FFlag::LuauOptimizeFalsyAndTruthyIntersect) { - SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, target, discriminant); - if (!result.blockedTypes.empty()) - return {nullptr, {result.blockedTypes.begin(), result.blockedTypes.end()}}; - - return {result.result, {}}; + // If the target type is a table, then simplification already implements the logic to deal with refinements properly since the + // type of the discriminant is guaranteed to only ever be an (arbitrarily-nested) table of a single property type. + // We also fire for simple discriminants such as false? and ~(false?): the falsy and truthy types respectively + // NOTE: It would be nice to be able to do a simple intersection for something like: + // + // { a: A, b: B, ... } & { x: X } + // + if (is(target) || isSimpleDiscriminant(discriminant)) + { + SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, target, discriminant); + if (!result.blockedTypes.empty()) + return {nullptr, {result.blockedTypes.begin(), result.blockedTypes.end()}}; + return {result.result, {}}; + } } + else + { + // If the target type is a table, then simplification already implements the logic to deal with refinements properly since the + // type of the discriminant is guaranteed to only ever be an (arbitrarily-nested) table of a single property type. + if (get(target)) + { + SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, target, discriminant); + if (!result.blockedTypes.empty()) + return {nullptr, {result.blockedTypes.begin(), result.blockedTypes.end()}}; + + return {result.result, {}}; + } + } + // In the general case, we'll still use normalization though. TypeId intersection = ctx->arena->addType(IntersectionType{{target, discriminant}}); @@ -2485,6 +2566,8 @@ TypeFunctionReductionResult intersectTypeFunction( // fold over the types with `simplifyIntersection` TypeId resultTy = ctx->builtins->unknownType; + // collect types which caused intersection to return never + DenseHashSet unintersectableTypes{nullptr}; for (auto ty : types) { // skip any `*no-refine*` types. @@ -2493,6 +2576,17 @@ TypeFunctionReductionResult intersectTypeFunction( SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, resultTy, ty); + if (FFlag::LuauNarrowIntersectionNevers) + { + // If simplifying the intersection returned never, note the type we tried to intersect it with, and continue trying to intersect with the + // rest + if (get(result.result)) + { + unintersectableTypes.insert(follow(ty)); + continue; + } + } + if (FFlag::LuauIntersectNotNil) { for (TypeId blockedType : result.blockedTypes) @@ -2510,6 +2604,24 @@ TypeFunctionReductionResult intersectTypeFunction( resultTy = result.result; } + if (FFlag::LuauNarrowIntersectionNevers) + { + if (!unintersectableTypes.empty()) + { + unintersectableTypes.insert(resultTy); + if (unintersectableTypes.size() > 1) + { + TypeId intersection = + ctx->arena->addType(IntersectionType{std::vector(unintersectableTypes.begin(), unintersectableTypes.end())}); + return {intersection, Reduction::MaybeOk, {}, {}}; + } + else + { + return {*unintersectableTypes.begin(), Reduction::MaybeOk, {}, {}}; + } + } + } + // if the intersection simplifies to `never`, this gives us bad autocomplete. // we'll just produce the intersection plainly instead, but this might be revisitable // if we ever give `never` some kind of "explanation" trail. @@ -3413,8 +3525,8 @@ BuiltinTypeFunctions::BuiltinTypeFunctions() , powFunc{"pow", powTypeFunction} , modFunc{"mod", modTypeFunction} , concatFunc{"concat", concatTypeFunction} - , andFunc{"and", andTypeFunction} - , orFunc{"or", orTypeFunction} + , andFunc{"and", andTypeFunction, /*canReduceGenerics*/ true} + , orFunc{"or", orTypeFunction, /*canReduceGenerics*/ true} , ltFunc{"lt", ltTypeFunction} , leFunc{"le", leTypeFunction} , eqFunc{"eq", eqTypeFunction} diff --git a/Analysis/src/TypeInfer.cpp b/Analysis/src/TypeInfer.cpp index ef14abf8..d8334195 100644 --- a/Analysis/src/TypeInfer.cpp +++ b/Analysis/src/TypeInfer.cpp @@ -36,6 +36,8 @@ LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType) LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds) LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations) LUAU_FASTFLAGVARIABLE(LuauStatForInFix) +LUAU_FASTFLAGVARIABLE(LuauReduceCheckBinaryExprStackPressure) +LUAU_FASTFLAGVARIABLE(LuauLimitIterationWhenCheckingArgumentCounts) namespace Luau { @@ -1924,7 +1926,7 @@ WithPredicate TypeChecker::checkExpr(const ScopePtr& scope, const AstExp else if (auto a = expr.as()) result = checkExpr(scope, *a); else if (auto a = expr.as()) - result = checkExpr(scope, *a, expectedType); + result = FFlag::LuauReduceCheckBinaryExprStackPressure ? checkExpr(scope, *a, expectedType) : checkExpr_DEPRECATED(scope, *a, expectedType); else if (auto a = expr.as()) result = checkExpr(scope, *a); else if (auto a = expr.as()) @@ -3186,20 +3188,82 @@ WithPredicate TypeChecker::checkExpr(const ScopePtr& scope, const AstExp return {result, {OrPredicate{std::move(lhsPredicates), std::move(rhsPredicates)}}}; } else if (expr.op == AstExprBinary::CompareEq || expr.op == AstExprBinary::CompareNe) + { + // Defer the stack allocation of lhs, predicate etc until this lambda is called. + auto checkExprOr = [&]() -> WithPredicate + { + // For these, passing expectedType is worse than simply forcing them, because their implementation + // may inadvertently check if expectedTypes exist first and use it, instead of forceSingleton first. + WithPredicate lhs = checkExpr(scope, *expr.left, std::nullopt, /*forceSingleton=*/true); + WithPredicate rhs = checkExpr(scope, *expr.right, std::nullopt, /*forceSingleton=*/true); + + if (auto predicate = tryGetTypeGuardPredicate(expr)) + return {booleanType, {std::move(*predicate)}}; + + PredicateVec predicates; + + if (auto lvalue = tryGetLValue(*expr.left)) + predicates.emplace_back(EqPredicate{std::move(*lvalue), rhs.type, expr.location}); + + if (auto lvalue = tryGetLValue(*expr.right)) + predicates.emplace_back(EqPredicate{std::move(*lvalue), lhs.type, expr.location}); + + if (!predicates.empty() && expr.op == AstExprBinary::CompareNe) + predicates = {NotPredicate{std::move(predicates)}}; + + return {checkBinaryOperation(scope, expr, lhs.type, rhs.type), std::move(predicates)}; + }; + return checkExprOr(); + } + else + { + // Expected types are not useful for other binary operators. + WithPredicate lhs = checkExpr(scope, *expr.left); + WithPredicate rhs = checkExpr(scope, *expr.right); + + // Intentionally discarding predicates with other operators. + return WithPredicate{checkBinaryOperation(scope, expr, lhs.type, rhs.type, lhs.predicates)}; + } +} + +WithPredicate TypeChecker::checkExpr_DEPRECATED(const ScopePtr& scope, const AstExprBinary& expr, std::optional expectedType) +{ + if (expr.op == AstExprBinary::And) + { + auto [lhsTy, lhsPredicates] = checkExpr(scope, *expr.left, expectedType); + + ScopePtr innerScope = childScope(scope, expr.location); + resolve(lhsPredicates, innerScope, true); + + auto [rhsTy, rhsPredicates] = checkExpr(innerScope, *expr.right, expectedType); + + return {checkBinaryOperation(scope, expr, lhsTy, rhsTy), {AndPredicate{std::move(lhsPredicates), std::move(rhsPredicates)}}}; + } + else if (expr.op == AstExprBinary::Or) + { + auto [lhsTy, lhsPredicates] = checkExpr(scope, *expr.left, expectedType); + + ScopePtr innerScope = childScope(scope, expr.location); + resolve(lhsPredicates, innerScope, false); + + auto [rhsTy, rhsPredicates] = checkExpr(innerScope, *expr.right, expectedType); + + // Because of C++, I'm not sure if lhsPredicates was not moved out by the time we call checkBinaryOperation. + TypeId result = checkBinaryOperation(scope, expr, lhsTy, rhsTy, lhsPredicates); + return {result, {OrPredicate{std::move(lhsPredicates), std::move(rhsPredicates)}}}; + } + else if (expr.op == AstExprBinary::CompareEq || expr.op == AstExprBinary::CompareNe) { // For these, passing expectedType is worse than simply forcing them, because their implementation // may inadvertently check if expectedTypes exist first and use it, instead of forceSingleton first. WithPredicate lhs = checkExpr(scope, *expr.left, std::nullopt, /*forceSingleton=*/true); WithPredicate rhs = checkExpr(scope, *expr.right, std::nullopt, /*forceSingleton=*/true); - if (auto predicate = tryGetTypeGuardPredicate(expr)) return {booleanType, {std::move(*predicate)}}; PredicateVec predicates; - if (auto lvalue = tryGetLValue(*expr.left)) predicates.push_back(EqPredicate{std::move(*lvalue), rhs.type, expr.location}); - if (auto lvalue = tryGetLValue(*expr.right)) predicates.push_back(EqPredicate{std::move(*lvalue), lhs.type, expr.location}); @@ -4050,6 +4114,23 @@ void TypeChecker::checkArgumentList( size_t paramIndex = 0; + int loopCount = 0; + auto exceedsLoopCount = [&]() + { + if (FFlag::LuauLimitIterationWhenCheckingArgumentCounts) + { + ++loopCount; + if (loopCount > FInt::LuauTypeInferTypePackLoopLimit) + { + state.reportError(TypeError{state.location, CodeTooComplex{}}); + reportErrorCodeTooComplex(state.location); + return true; + } + } + + return false; + }; + auto reportCountMismatchError = [&state, &argLocations, paramPack, argPack, &funName]() { // For this case, we want the error span to cover every errant extra parameter @@ -4124,12 +4205,17 @@ void TypeChecker::checkArgumentList( } else if (auto vtp = state.log.getMutable(tail)) { + loopCount = 0; + // Function is variadic and requires that all subsequent parameters // be compatible with a type. while (paramIter != endIter) { state.tryUnify(vtp->ty, *paramIter); ++paramIter; + + if (exceedsLoopCount()) + return; } return; @@ -4138,10 +4224,16 @@ void TypeChecker::checkArgumentList( { std::vector rest; rest.reserve(std::distance(paramIter, endIter)); + + loopCount = 0; + while (paramIter != endIter) { rest.push_back(*paramIter); ++paramIter; + + if (exceedsLoopCount()) + return; } TypePackId varPack = addTypePack(TypePackVar{TypePack{rest, paramIter.tail()}}); @@ -4185,12 +4277,17 @@ void TypeChecker::checkArgumentList( // too many parameters passed if (!paramIter.tail()) { + loopCount = 0; + while (argIter != endIter) { // The use of unify here is deliberate. We don't want this unification // to be undoable. unify(errorRecoveryType(scope), *argIter, scope, state.location); ++argIter; + + if (exceedsLoopCount()) + return; } reportCountMismatchError(); return; @@ -4204,6 +4301,8 @@ void TypeChecker::checkArgumentList( } else if (auto vtp = state.log.getMutable(tail)) { + loopCount = 0; + // Function is variadic and requires that all subsequent parameters // be compatible with a type. size_t argIndex = paramIndex; @@ -4219,12 +4318,17 @@ void TypeChecker::checkArgumentList( ++argIter; ++argIndex; + + if (exceedsLoopCount()) + return; } return; } else if (state.log.getMutable(tail)) { + loopCount = 0; + // Create a type pack out of the remaining argument types // and unify it with the tail. std::vector rest; @@ -4233,7 +4337,10 @@ void TypeChecker::checkArgumentList( { rest.push_back(*argIter); ++argIter; - } + + if (exceedsLoopCount()) + return; + } TypePackId varPack = addTypePack(TypePackVar{TypePack{rest, argIter.tail()}}); state.tryUnify(varPack, tail); diff --git a/Analysis/src/TypeUtils.cpp b/Analysis/src/TypeUtils.cpp index 3240a2d3..04f7679a 100644 --- a/Analysis/src/TypeUtils.cpp +++ b/Analysis/src/TypeUtils.cpp @@ -14,7 +14,7 @@ LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete); LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope); LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds) -LUAU_FASTFLAG(LuauNonReentrantGeneralization) +LUAU_FASTFLAG(LuauNonReentrantGeneralization2) LUAU_FASTFLAG(LuauDisableNewSolverAssertsInMixedMode) namespace Luau @@ -308,7 +308,7 @@ TypePack extendTypePack( TypePack newPack; newPack.tail = arena.freshTypePack(ftp->scope, ftp->polarity); - if (FFlag::LuauNonReentrantGeneralization) + if (FFlag::LuauNonReentrantGeneralization2) trackInteriorFreeTypePack(ftp->scope, *newPack.tail); if (FFlag::LuauSolverV2) @@ -577,7 +577,7 @@ void trackInteriorFreeType(Scope* scope, TypeId ty) void trackInteriorFreeTypePack(Scope* scope, TypePackId tp) { LUAU_ASSERT(tp); - if (!FFlag::LuauNonReentrantGeneralization) + if (!FFlag::LuauNonReentrantGeneralization2) return; for (; scope; scope = scope->parent.get()) diff --git a/Analysis/src/Unifier2.cpp b/Analysis/src/Unifier2.cpp index 8ad2fae4..b3a0a7ad 100644 --- a/Analysis/src/Unifier2.cpp +++ b/Analysis/src/Unifier2.cpp @@ -18,9 +18,7 @@ #include LUAU_FASTINT(LuauTypeInferRecursionLimit) -LUAU_FASTFLAGVARIABLE(LuauUnifyMetatableWithAny) -LUAU_FASTFLAG(LuauExtraFollows) -LUAU_FASTFLAG(LuauNonReentrantGeneralization) +LUAU_FASTFLAG(LuauNonReentrantGeneralization2) namespace Luau { @@ -238,9 +236,9 @@ bool Unifier2::unify(TypeId subTy, TypeId superTy) auto superMetatable = get(superTy); if (subMetatable && superMetatable) return unify(subMetatable, superMetatable); - else if (FFlag::LuauUnifyMetatableWithAny && subMetatable && superAny) + else if (subMetatable && superAny) return unify(subMetatable, superAny); - else if (FFlag::LuauUnifyMetatableWithAny && subAny && superMetatable) + else if (subAny && superMetatable) return unify(subAny, superMetatable); else if (subMetatable) // if we only have one metatable, unify with the inner table return unify(subMetatable->table, superTy); @@ -284,7 +282,7 @@ bool Unifier2::unifyFreeWithType(TypeId subTy, TypeId superTy) if (superArgTail) return doDefault(); - const IntersectionType* upperBoundIntersection = get(FFlag::LuauExtraFollows ? upperBound : subFree->upperBound); + const IntersectionType* upperBoundIntersection = get(upperBound); if (!upperBoundIntersection) return doDefault(); @@ -321,18 +319,18 @@ bool Unifier2::unify(TypeId subTy, const FunctionType* superFn) if (shouldInstantiate) { - for (auto generic : subFn->generics) + for (TypeId generic : subFn->generics) { const GenericType* gen = get(generic); LUAU_ASSERT(gen); genericSubstitutions[generic] = freshType(scope, gen->polarity); } - for (auto genericPack : subFn->genericPacks) + for (TypePackId genericPack : subFn->genericPacks) { - if (FFlag::LuauNonReentrantGeneralization) + if (FFlag::LuauNonReentrantGeneralization2) { - const GenericTypePack* gen = get(genericPack); + const GenericTypePack* gen = get(follow(genericPack)); LUAU_ASSERT(gen); genericPackSubstitutions[genericPack] = freshTypePack(scope, gen->polarity); } @@ -651,211 +649,6 @@ bool Unifier2::unify(TypePackId subTp, TypePackId superTp) return true; } -struct FreeTypeSearcher : TypeVisitor -{ - NotNull scope; - - explicit FreeTypeSearcher(NotNull scope) - : TypeVisitor(/*skipBoundTypes*/ true) - , scope(scope) - { - } - - Polarity polarity = Polarity::Positive; - - void flip() - { - switch (polarity) - { - case Polarity::Positive: - polarity = Polarity::Negative; - break; - case Polarity::Negative: - polarity = Polarity::Positive; - break; - case Polarity::Mixed: - break; - default: - LUAU_ASSERT(!"Unreachable"); - } - } - - DenseHashSet seenPositive{nullptr}; - DenseHashSet seenNegative{nullptr}; - - bool seenWithCurrentPolarity(const void* ty) - { - switch (polarity) - { - case Polarity::Positive: - { - if (seenPositive.contains(ty)) - return true; - - seenPositive.insert(ty); - return false; - } - case Polarity::Negative: - { - if (seenNegative.contains(ty)) - return true; - - seenNegative.insert(ty); - return false; - } - case Polarity::Mixed: - { - if (seenPositive.contains(ty) && seenNegative.contains(ty)) - return true; - - seenPositive.insert(ty); - seenNegative.insert(ty); - return false; - } - default: - LUAU_ASSERT(!"Unreachable"); - } - - return false; - } - - // The keys in these maps are either TypeIds or TypePackIds. It's safe to - // mix them because we only use these pointers as unique keys. We never - // indirect them. - DenseHashMap negativeTypes{0}; - DenseHashMap positiveTypes{0}; - - bool visit(TypeId ty) override - { - if (seenWithCurrentPolarity(ty)) - return false; - - LUAU_ASSERT(ty); - return true; - } - - bool visit(TypeId ty, const FreeType& ft) override - { - if (seenWithCurrentPolarity(ty)) - return false; - - if (!subsumes(scope, ft.scope)) - return true; - - switch (polarity) - { - case Polarity::Positive: - positiveTypes[ty]++; - break; - case Polarity::Negative: - negativeTypes[ty]++; - break; - case Polarity::Mixed: - positiveTypes[ty]++; - negativeTypes[ty]++; - break; - default: - LUAU_ASSERT(!"Unreachable"); - } - - return true; - } - - bool visit(TypeId ty, const TableType& tt) override - { - if (seenWithCurrentPolarity(ty)) - return false; - - if ((tt.state == TableState::Free || tt.state == TableState::Unsealed) && subsumes(scope, tt.scope)) - { - switch (polarity) - { - case Polarity::Positive: - positiveTypes[ty]++; - break; - case Polarity::Negative: - negativeTypes[ty]++; - break; - case Polarity::Mixed: - positiveTypes[ty]++; - negativeTypes[ty]++; - break; - default: - LUAU_ASSERT(!"Unreachable"); - } - } - - for (const auto& [_name, prop] : tt.props) - { - if (prop.isReadOnly()) - traverse(*prop.readTy); - else - { - LUAU_ASSERT(prop.isShared()); - - Polarity p = polarity; - polarity = Polarity::Mixed; - traverse(prop.type()); - polarity = p; - } - } - - if (tt.indexer) - { - traverse(tt.indexer->indexType); - traverse(tt.indexer->indexResultType); - } - - return false; - } - - bool visit(TypeId ty, const FunctionType& ft) override - { - if (seenWithCurrentPolarity(ty)) - return false; - - flip(); - traverse(ft.argTypes); - flip(); - - traverse(ft.retTypes); - - return false; - } - - bool visit(TypeId, const ClassType&) override - { - return false; - } - - bool visit(TypePackId tp, const FreeTypePack& ftp) override - { - if (seenWithCurrentPolarity(tp)) - return false; - - if (!subsumes(scope, ftp.scope)) - return true; - - switch (polarity) - { - case Polarity::Positive: - positiveTypes[tp]++; - break; - case Polarity::Negative: - negativeTypes[tp]++; - break; - case Polarity::Mixed: - positiveTypes[tp]++; - negativeTypes[tp]++; - break; - default: - LUAU_ASSERT(!"Unreachable"); - } - - return true; - } -}; - TypeId Unifier2::mkUnion(TypeId left, TypeId right) { left = follow(left); diff --git a/Ast/include/Luau/Ast.h b/Ast/include/Luau/Ast.h index e8649755..d0e1db43 100644 --- a/Ast/include/Luau/Ast.h +++ b/Ast/include/Luau/Ast.h @@ -87,8 +87,8 @@ struct AstLocal template struct AstArray { - T* data; - size_t size; + T* data = nullptr; + size_t size = 0; const T* begin() const { diff --git a/Ast/include/Luau/Cst.h b/Ast/include/Luau/Cst.h index 0f7b5911..3c006eb0 100644 --- a/Ast/include/Luau/Cst.h +++ b/Ast/include/Luau/Cst.h @@ -388,6 +388,7 @@ public: std::optional separatorPosition; CstExprConstantString* stringInfo = nullptr; // only if Kind == StringProperty + Position stringPosition{0, 0}; // only if Kind == StringProperty }; CstTypeTable(AstArray items, bool isArray); diff --git a/Ast/src/Parser.cpp b/Ast/src/Parser.cpp index 04f32b3f..429e5d10 100644 --- a/Ast/src/Parser.cpp +++ b/Ast/src/Parser.cpp @@ -18,8 +18,6 @@ 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(LuauAllowComplexTypesInGenericParams) -LUAU_FASTFLAGVARIABLE(LuauErrorRecoveryForTableTypes) LUAU_FASTFLAGVARIABLE(LuauStoreCSTData2) LUAU_FASTFLAGVARIABLE(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType) LUAU_FASTFLAGVARIABLE(LuauAstTypeGroup3) @@ -1670,8 +1668,7 @@ std::pair Parser::parseFunctionBody( // // function (t: { a: number }) end // - if (FFlag::LuauErrorRecoveryForTableTypes) - matchRecoveryStopOnToken[')']++; + matchRecoveryStopOnToken[')']++; TempVector args(scratchBinding); @@ -1690,8 +1687,7 @@ std::pair Parser::parseFunctionBody( expectMatchAndConsume(')', matchParen, true); - if (FFlag::LuauErrorRecoveryForTableTypes) - matchRecoveryStopOnToken[')']--; + matchRecoveryStopOnToken[')']--; std::optional typelist = parseOptionalReturnType(cstNode ? &cstNode->returnSpecifierPosition : nullptr); @@ -2173,6 +2169,7 @@ AstType* Parser::parseTableType(bool inDeclarationContext) if (FFlag::LuauStoreCSTData2 && options.storeCstData) std::tie(style, blockDepth) = extractStringDetails(); + Position stringPosition = lexer.current().location.begin; AstArray sourceString; std::optional> chars = parseCharArray(options.storeCstData ? &sourceString : nullptr); @@ -2197,7 +2194,8 @@ AstType* Parser::parseTableType(bool inDeclarationContext) colonPosition, tableSeparator(), lexer.current().location.begin, - allocator.alloc(sourceString, style, blockDepth) + allocator.alloc(sourceString, style, blockDepth), + stringPosition }); } else @@ -2288,6 +2286,7 @@ AstType* Parser::parseTableType(bool inDeclarationContext) if (FFlag::LuauStoreCSTData2 && options.storeCstData) std::tie(style, blockDepth) = extractStringDetails(); + Position stringPosition = lexer.current().location.begin; AstArray sourceString; std::optional> chars = parseCharArray(options.storeCstData ? &sourceString : nullptr); @@ -2312,7 +2311,8 @@ AstType* Parser::parseTableType(bool inDeclarationContext) colonPosition, tableSeparator(), lexer.current().location.begin, - allocator.alloc(sourceString, style, blockDepth) + allocator.alloc(sourceString, style, blockDepth), + stringPosition }); } else @@ -2408,7 +2408,7 @@ AstType* Parser::parseTableType(bool inDeclarationContext) Location end = lexer.current().location; - if (!expectMatchAndConsume('}', matchBrace, /* searchForMissing = */ FFlag::LuauErrorRecoveryForTableTypes)) + if (!expectMatchAndConsume('}', matchBrace, /* searchForMissing = */ true)) end = lexer.previousLocation(); if (FFlag::LuauStoreCSTData2) @@ -4088,78 +4088,66 @@ AstArray Parser::parseTypeParams(Position* openingPosition, TempV } else if (lexer.current().type == '(') { - if (FFlag::LuauAllowComplexTypesInGenericParams) + Location begin = lexer.current().location; + AstType* type = nullptr; + AstTypePack* typePack = nullptr; + Lexeme::Type c = lexer.current().type; + + if (c != '|' && c != '&') { - Location begin = lexer.current().location; - AstType* type = nullptr; - AstTypePack* typePack = nullptr; - Lexeme::Type c = lexer.current().type; + auto typeOrTypePack = parseSimpleType(/* allowPack */ true, /* inDeclarationContext */ false); + type = typeOrTypePack.type; + typePack = typeOrTypePack.typePack; + } - if (c != '|' && c != '&') + // Consider the following type: + // + // X<(T)> + // + // Is this a type pack or a parenthesized type? The + // assumption will be a type pack, as that's what allows one + // to express either a singular type pack or a potential + // complex type. + + if (typePack) + { + auto explicitTypePack = typePack->as(); + if (explicitTypePack && explicitTypePack->typeList.tailType == nullptr && explicitTypePack->typeList.types.size == 1 && + isTypeFollow(lexer.current().type)) { - auto typeOrTypePack = parseSimpleType(/* allowPack */ true, /* inDeclarationContext */ false); - type = typeOrTypePack.type; - typePack = typeOrTypePack.typePack; - } - - // Consider the following type: - // - // X<(T)> - // - // Is this a type pack or a parenthesized type? The - // assumption will be a type pack, as that's what allows one - // to express either a singular type pack or a potential - // complex type. - - if (typePack) - { - auto explicitTypePack = typePack->as(); - if (explicitTypePack && explicitTypePack->typeList.tailType == nullptr && explicitTypePack->typeList.types.size == 1 && - isTypeFollow(lexer.current().type)) + // If we parsed an explicit type pack with a single + // type in it (something of the form `(T)`), and + // the next lexeme is one that follows a type + // (&, |, ?), then assume that this was actually a + // parenthesized type. + if (FFlag::LuauAstTypeGroup3) { - // If we parsed an explicit type pack with a single - // type in it (something of the form `(T)`), and - // the next lexeme is one that follows a type - // (&, |, ?), then assume that this was actually a - // parenthesized type. - if (FFlag::LuauAstTypeGroup3) - { - auto parenthesizedType = explicitTypePack->typeList.types.data[0]; - parameters.push_back( - {parseTypeSuffix(allocator.alloc(parenthesizedType->location, parenthesizedType), begin), {}} - ); - } - else - parameters.push_back({parseTypeSuffix(explicitTypePack->typeList.types.data[0], begin), {}}); + auto parenthesizedType = explicitTypePack->typeList.types.data[0]; + parameters.push_back( + {parseTypeSuffix(allocator.alloc(parenthesizedType->location, parenthesizedType), begin), {}} + ); } else - { - // Otherwise, it's a type pack. - parameters.push_back({{}, typePack}); - } + parameters.push_back({parseTypeSuffix(explicitTypePack->typeList.types.data[0], begin), {}}); } else { - // There's two cases in which `typePack` will be null: - // - We try to parse a simple type or a type pack, and - // we get a simple type: there's no ambiguity and - // we attempt to parse a complex type. - // - The next lexeme was a `|` or `&` indicating a - // union or intersection type with a leading - // separator. We just fall right into - // `parseTypeSuffix`, which allows its first - // argument to be `nullptr` - parameters.push_back({parseTypeSuffix(type, begin), {}}); + // Otherwise, it's a type pack. + parameters.push_back({{}, typePack}); } } else { - auto [type, typePack] = parseSimpleTypeOrPack(); - - if (typePack) - parameters.push_back({{}, typePack}); - else - parameters.push_back({type, {}}); + // There's two cases in which `typePack` will be null: + // - We try to parse a simple type or a type pack, and + // we get a simple type: there's no ambiguity and + // we attempt to parse a complex type. + // - The next lexeme was a `|` or `&` indicating a + // union or intersection type with a leading + // separator. We just fall right into + // `parseTypeSuffix`, which allows its first + // argument to be `nullptr` + parameters.push_back({parseTypeSuffix(type, begin), {}}); } } else if (lexer.current().type == '>' && parameters.empty()) diff --git a/CodeGen/include/Luau/IrUtils.h b/CodeGen/include/Luau/IrUtils.h index 1afa1a34..eb1cf079 100644 --- a/CodeGen/include/Luau/IrUtils.h +++ b/CodeGen/include/Luau/IrUtils.h @@ -5,8 +5,6 @@ #include "Luau/Common.h" #include "Luau/IrData.h" -LUAU_FASTFLAG(LuauVectorLibNativeDot); - namespace Luau { namespace CodeGen @@ -15,80 +13,11 @@ namespace CodeGen struct IrBuilder; enum class HostMetamethod; -inline bool isJumpD(LuauOpcode op) -{ - switch (op) - { - case LOP_JUMP: - case LOP_JUMPIF: - case LOP_JUMPIFNOT: - case LOP_JUMPIFEQ: - case LOP_JUMPIFLE: - case LOP_JUMPIFLT: - case LOP_JUMPIFNOTEQ: - case LOP_JUMPIFNOTLE: - case LOP_JUMPIFNOTLT: - case LOP_FORNPREP: - case LOP_FORNLOOP: - case LOP_FORGPREP: - case LOP_FORGLOOP: - case LOP_FORGPREP_INEXT: - case LOP_FORGPREP_NEXT: - case LOP_JUMPBACK: - case LOP_JUMPXEQKNIL: - case LOP_JUMPXEQKB: - case LOP_JUMPXEQKN: - case LOP_JUMPXEQKS: - return true; - - default: - return false; - } -} - -inline bool isSkipC(LuauOpcode op) -{ - switch (op) - { - case LOP_LOADB: - return true; - - default: - return false; - } -} - -inline bool isFastCall(LuauOpcode op) -{ - switch (op) - { - case LOP_FASTCALL: - case LOP_FASTCALL1: - case LOP_FASTCALL2: - case LOP_FASTCALL2K: - case LOP_FASTCALL3: - return true; - - default: - return false; - } -} - -inline int getJumpTarget(uint32_t insn, uint32_t pc) -{ - LuauOpcode op = LuauOpcode(LUAU_INSN_OP(insn)); - - if (isJumpD(op)) - return int(pc + LUAU_INSN_D(insn) + 1); - else if (isFastCall(op)) - return int(pc + LUAU_INSN_C(insn) + 2); - else if (isSkipC(op) && LUAU_INSN_C(insn)) - return int(pc + LUAU_INSN_C(insn) + 1); - else if (op == LOP_JUMPX) - return int(pc + LUAU_INSN_E(insn) + 1); - else - return -1; -} +int getOpLength(LuauOpcode op); +bool isJumpD(LuauOpcode op); +bool isSkipC(LuauOpcode op); +bool isFastCall(LuauOpcode op); +int getJumpTarget(uint32_t insn, uint32_t pc); inline bool isBlockTerminator(IrCmd cmd) { @@ -180,9 +109,6 @@ inline bool hasResult(IrCmd cmd) case IrCmd::MUL_VEC: case IrCmd::DIV_VEC: case IrCmd::DOT_VEC: - if (cmd == IrCmd::DOT_VEC) - LUAU_ASSERT(FFlag::LuauVectorLibNativeDot); - LUAU_FALLTHROUGH; case IrCmd::UNM_VEC: case IrCmd::NOT_ANY: case IrCmd::CMP_ANY: diff --git a/CodeGen/src/AssemblyBuilderA64.cpp b/CodeGen/src/AssemblyBuilderA64.cpp index 9e17d3fd..23384e57 100644 --- a/CodeGen/src/AssemblyBuilderA64.cpp +++ b/CodeGen/src/AssemblyBuilderA64.cpp @@ -7,8 +7,6 @@ #include #include -LUAU_FASTFLAG(LuauVectorLibNativeDot); - namespace Luau { namespace CodeGen @@ -590,7 +588,6 @@ void AssemblyBuilderA64::fabs(RegisterA64 dst, RegisterA64 src) void AssemblyBuilderA64::faddp(RegisterA64 dst, RegisterA64 src) { - LUAU_ASSERT(FFlag::LuauVectorLibNativeDot); CODEGEN_ASSERT(dst.kind == KindA64::d || dst.kind == KindA64::s); CODEGEN_ASSERT(dst.kind == src.kind); diff --git a/CodeGen/src/AssemblyBuilderX64.cpp b/CodeGen/src/AssemblyBuilderX64.cpp index 1fb1b671..b48627cc 100644 --- a/CodeGen/src/AssemblyBuilderX64.cpp +++ b/CodeGen/src/AssemblyBuilderX64.cpp @@ -6,8 +6,6 @@ #include #include -LUAU_FASTFLAG(LuauVectorLibNativeDot); - namespace Luau { namespace CodeGen @@ -955,7 +953,6 @@ void AssemblyBuilderX64::vpinsrd(RegisterX64 dst, RegisterX64 src1, OperandX64 s void AssemblyBuilderX64::vdpps(OperandX64 dst, OperandX64 src1, OperandX64 src2, uint8_t mask) { - LUAU_ASSERT(FFlag::LuauVectorLibNativeDot); placeAvx("vdpps", dst, src1, src2, mask, 0x40, false, AVX_0F3A, AVX_66); } diff --git a/CodeGen/src/BytecodeAnalysis.cpp b/CodeGen/src/BytecodeAnalysis.cpp index b859b111..ddccf9ca 100644 --- a/CodeGen/src/BytecodeAnalysis.cpp +++ b/CodeGen/src/BytecodeAnalysis.cpp @@ -1,7 +1,6 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/BytecodeAnalysis.h" -#include "Luau/BytecodeUtils.h" #include "Luau/CodeGenOptions.h" #include "Luau/IrData.h" #include "Luau/IrUtils.h" @@ -639,7 +638,7 @@ void buildBytecodeBlocks(IrFunction& function, const std::vector& jumpT bcBlocks.push_back(BytecodeBlock{nexti, -1}); } // Returns just terminate the block - else if (op == LOP_RETURN) + else if (int(op) == LOP_RETURN) { bcBlocks.back().finishpc = i; } @@ -702,7 +701,7 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks) BytecodeTypes& bcType = function.bcTypes[i]; - switch (op) + switch (int(op)) { case LOP_NOP: break; diff --git a/CodeGen/src/BytecodeSummary.cpp b/CodeGen/src/BytecodeSummary.cpp index d179dcc5..4e011d37 100644 --- a/CodeGen/src/BytecodeSummary.cpp +++ b/CodeGen/src/BytecodeSummary.cpp @@ -1,6 +1,8 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/BytecodeSummary.h" -#include "Luau/BytecodeUtils.h" + +#include "Luau/IrUtils.h" + #include "CodeGenLower.h" #include "lua.h" @@ -42,7 +44,7 @@ FunctionBytecodeSummary FunctionBytecodeSummary::fromProto(Proto* proto, unsigne Instruction insn = proto->code[i]; uint8_t op = LUAU_INSN_OP(insn); summary.incCount(0, op); - i += Luau::getOpLength(LuauOpcode(op)); + i += getOpLength(LuauOpcode(op)); } return summary; diff --git a/CodeGen/src/CodeBlockUnwind.cpp b/CodeGen/src/CodeBlockUnwind.cpp index d21dd14b..d90dfd69 100644 --- a/CodeGen/src/CodeBlockUnwind.cpp +++ b/CodeGen/src/CodeBlockUnwind.cpp @@ -18,7 +18,7 @@ #endif #include -#elif defined(__linux__) || defined(__APPLE__) +#elif (defined(__linux__) || defined(__APPLE__)) && (defined(CODEGEN_TARGET_X64) || defined(CODEGEN_TARGET_A64)) // __register_frame and __deregister_frame are defined in libgcc or libc++ // (depending on how it's built). We want to declare them as weak symbols @@ -81,7 +81,7 @@ static int findDynamicUnwindSections(uintptr_t addr, unw_dynamic_unwind_sections } #endif -#if defined(__linux__) || defined(__APPLE__) +#if (defined(__linux__) || defined(__APPLE__)) && (defined(CODEGEN_TARGET_X64) || defined(CODEGEN_TARGET_A64)) static void visitFdeEntries(char* pos, void (*cb)(const void*)) { // When using glibc++ unwinder, we need to call __register_frame/__deregister_frame on the entire .eh_frame data @@ -132,7 +132,7 @@ void* createBlockUnwindInfo(void* context, uint8_t* block, size_t blockSize, siz } #endif -#elif defined(__linux__) || defined(__APPLE__) +#elif (defined(__linux__) || defined(__APPLE__)) && (defined(CODEGEN_TARGET_X64) || defined(CODEGEN_TARGET_A64)) if (!&__register_frame) return nullptr; @@ -161,7 +161,7 @@ void destroyBlockUnwindInfo(void* context, void* unwindData) CODEGEN_ASSERT(!"Failed to deallocate function table"); #endif -#elif defined(__linux__) || defined(__APPLE__) +#elif (defined(__linux__) || defined(__APPLE__)) && (defined(CODEGEN_TARGET_X64) || defined(CODEGEN_TARGET_A64)) if (!&__deregister_frame) { CODEGEN_ASSERT(!"Cannot deregister unwind information"); @@ -184,7 +184,7 @@ bool isUnwindSupported() size_t verLength = sizeof(ver); // libunwind on macOS 12 and earlier (which maps to osrelease 21) assumes JIT frames use pointer authentication without a way to override that return sysctlbyname("kern.osrelease", ver, &verLength, NULL, 0) == 0 && atoi(ver) >= 22; -#elif defined(__linux__) || defined(__APPLE__) +#elif (defined(__linux__) || defined(__APPLE__)) && (defined(CODEGEN_TARGET_X64) || defined(CODEGEN_TARGET_A64)) return true; #else return false; diff --git a/CodeGen/src/CodeGenA64.cpp b/CodeGen/src/CodeGenA64.cpp index 1d6e17a5..560fbd8d 100644 --- a/CodeGen/src/CodeGenA64.cpp +++ b/CodeGen/src/CodeGenA64.cpp @@ -284,7 +284,6 @@ bool initHeaderFunctions(BaseCodeGenContext& codeGenContext) codeStart )) { - CODEGEN_ASSERT(!"Failed to create entry function"); return false; } diff --git a/CodeGen/src/CodeGenAssembly.cpp b/CodeGen/src/CodeGenAssembly.cpp index 6bbdc473..082b6f97 100644 --- a/CodeGen/src/CodeGenAssembly.cpp +++ b/CodeGen/src/CodeGenAssembly.cpp @@ -1,8 +1,8 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/BytecodeAnalysis.h" -#include "Luau/BytecodeUtils.h" #include "Luau/BytecodeSummary.h" #include "Luau/IrDump.h" +#include "Luau/IrUtils.h" #include "CodeGenLower.h" @@ -135,7 +135,7 @@ unsigned getInstructionCount(const Instruction* insns, const unsigned size) for (unsigned i = 0; i < size;) { ++count; - i += Luau::getOpLength(LuauOpcode(LUAU_INSN_OP(insns[i]))); + i += getOpLength(LuauOpcode(LUAU_INSN_OP(insns[i]))); } return count; } diff --git a/CodeGen/src/CodeGenX64.cpp b/CodeGen/src/CodeGenX64.cpp index 3a7aa2b5..0884fe68 100644 --- a/CodeGen/src/CodeGenX64.cpp +++ b/CodeGen/src/CodeGenX64.cpp @@ -212,7 +212,6 @@ bool initHeaderFunctions(BaseCodeGenContext& codeGenContext) codeStart )) { - CODEGEN_ASSERT(!"Failed to create entry function"); return false; } diff --git a/CodeGen/src/IrBuilder.cpp b/CodeGen/src/IrBuilder.cpp index 3e6f85d8..bf762dc4 100644 --- a/CodeGen/src/IrBuilder.cpp +++ b/CodeGen/src/IrBuilder.cpp @@ -3,7 +3,6 @@ #include "Luau/Bytecode.h" #include "Luau/BytecodeAnalysis.h" -#include "Luau/BytecodeUtils.h" #include "Luau/IrData.h" #include "Luau/IrUtils.h" @@ -177,7 +176,7 @@ void IrBuilder::buildFunctionIr(Proto* proto) // Numeric for loops require additional processing to maintain loop stack // Notably, this must be performed even when the block is dead so that we maintain the pairing FORNPREP-FORNLOOP - if (op == LOP_FORNPREP) + if (int(op) == LOP_FORNPREP) beforeInstForNPrep(*this, pc, i); // We skip dead bytecode instructions when they appear after block was already terminated @@ -199,7 +198,7 @@ void IrBuilder::buildFunctionIr(Proto* proto) } // See above for FORNPREP..FORNLOOP processing - if (op == LOP_FORNLOOP) + if (int(op) == LOP_FORNLOOP) afterInstForNLoop(*this, pc); i = nexti; @@ -255,7 +254,7 @@ void IrBuilder::rebuildBytecodeBasicBlocks(Proto* proto) void IrBuilder::translateInst(LuauOpcode op, const Instruction* pc, int i) { - switch (op) + switch (int(op)) { case LOP_NOP: break; @@ -478,7 +477,7 @@ void IrBuilder::translateInst(LuauOpcode op, const Instruction* pc, int i) int ra = LUAU_INSN_A(*pc); IrOp loopRepeat = blockAtInst(i + 1 + LUAU_INSN_D(*pc)); - IrOp loopExit = blockAtInst(i + getOpLength(LOP_FORGLOOP)); + IrOp loopExit = blockAtInst(i + getOpLength(LuauOpcode(LOP_FORGLOOP))); IrOp fallback = block(IrBlockKind::Fallback); inst(IrCmd::INTERRUPT, constUint(i)); diff --git a/CodeGen/src/IrDump.cpp b/CodeGen/src/IrDump.cpp index dcc9d879..e4f0c27d 100644 --- a/CodeGen/src/IrDump.cpp +++ b/CodeGen/src/IrDump.cpp @@ -9,8 +9,6 @@ #include -LUAU_FASTFLAG(LuauVectorLibNativeDot); - namespace Luau { namespace CodeGen @@ -182,7 +180,6 @@ const char* getCmdName(IrCmd cmd) case IrCmd::UNM_VEC: return "UNM_VEC"; case IrCmd::DOT_VEC: - LUAU_ASSERT(FFlag::LuauVectorLibNativeDot); return "DOT_VEC"; case IrCmd::NOT_ANY: return "NOT_ANY"; diff --git a/CodeGen/src/IrLoweringA64.cpp b/CodeGen/src/IrLoweringA64.cpp index 086b91ed..ddfa2c1d 100644 --- a/CodeGen/src/IrLoweringA64.cpp +++ b/CodeGen/src/IrLoweringA64.cpp @@ -12,8 +12,6 @@ #include "lstate.h" #include "lgc.h" -LUAU_FASTFLAG(LuauVectorLibNativeDot) - namespace Luau { namespace CodeGen @@ -753,8 +751,6 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) } case IrCmd::DOT_VEC: { - LUAU_ASSERT(FFlag::LuauVectorLibNativeDot); - inst.regA64 = regs.allocReg(KindA64::d, index); RegisterA64 temp = regs.allocTemp(KindA64::q); diff --git a/CodeGen/src/IrLoweringX64.cpp b/CodeGen/src/IrLoweringX64.cpp index 373f4f59..1ef9f774 100644 --- a/CodeGen/src/IrLoweringX64.cpp +++ b/CodeGen/src/IrLoweringX64.cpp @@ -16,8 +16,6 @@ #include "lstate.h" #include "lgc.h" -LUAU_FASTFLAG(LuauVectorLibNativeDot) - namespace Luau { namespace CodeGen @@ -706,8 +704,6 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) } case IrCmd::DOT_VEC: { - LUAU_ASSERT(FFlag::LuauVectorLibNativeDot); - inst.regX64 = regs.allocRegOrReuse(SizeX64::xmmword, index, {inst.a, inst.b}); ScopedRegX64 tmp1{regs}; diff --git a/CodeGen/src/IrTranslateBuiltins.cpp b/CodeGen/src/IrTranslateBuiltins.cpp index ec72b692..04f24378 100644 --- a/CodeGen/src/IrTranslateBuiltins.cpp +++ b/CodeGen/src/IrTranslateBuiltins.cpp @@ -13,8 +13,6 @@ static const int kMinMaxUnrolledParams = 5; static const int kBit32BinaryOpUnrolledParams = 5; -LUAU_FASTFLAGVARIABLE(LuauVectorLibNativeDot); - namespace Luau { namespace CodeGen @@ -939,26 +937,9 @@ static BuiltinImplResult translateBuiltinVectorMagnitude( build.loadAndCheckTag(arg1, LUA_TVECTOR, build.vmExit(pcpos)); - IrOp sum; + IrOp a = build.inst(IrCmd::LOAD_TVALUE, arg1, build.constInt(0)); - if (FFlag::LuauVectorLibNativeDot) - { - IrOp a = build.inst(IrCmd::LOAD_TVALUE, arg1, build.constInt(0)); - - sum = build.inst(IrCmd::DOT_VEC, a, a); - } - else - { - IrOp x = build.inst(IrCmd::LOAD_FLOAT, arg1, build.constInt(0)); - IrOp y = build.inst(IrCmd::LOAD_FLOAT, arg1, build.constInt(4)); - IrOp z = build.inst(IrCmd::LOAD_FLOAT, arg1, build.constInt(8)); - - IrOp x2 = build.inst(IrCmd::MUL_NUM, x, x); - IrOp y2 = build.inst(IrCmd::MUL_NUM, y, y); - IrOp z2 = build.inst(IrCmd::MUL_NUM, z, z); - - sum = build.inst(IrCmd::ADD_NUM, build.inst(IrCmd::ADD_NUM, x2, y2), z2); - } + IrOp sum = build.inst(IrCmd::DOT_VEC, a, a); IrOp mag = build.inst(IrCmd::SQRT_NUM, sum); @@ -986,43 +967,18 @@ static BuiltinImplResult translateBuiltinVectorNormalize( build.loadAndCheckTag(arg1, LUA_TVECTOR, build.vmExit(pcpos)); - if (FFlag::LuauVectorLibNativeDot) - { - IrOp a = build.inst(IrCmd::LOAD_TVALUE, arg1, build.constInt(0)); - IrOp sum = build.inst(IrCmd::DOT_VEC, a, a); + IrOp a = build.inst(IrCmd::LOAD_TVALUE, arg1, build.constInt(0)); + IrOp sum = build.inst(IrCmd::DOT_VEC, a, a); - IrOp mag = build.inst(IrCmd::SQRT_NUM, sum); - IrOp inv = build.inst(IrCmd::DIV_NUM, build.constDouble(1.0), mag); - IrOp invvec = build.inst(IrCmd::NUM_TO_VEC, inv); + IrOp mag = build.inst(IrCmd::SQRT_NUM, sum); + IrOp inv = build.inst(IrCmd::DIV_NUM, build.constDouble(1.0), mag); + IrOp invvec = build.inst(IrCmd::NUM_TO_VEC, inv); - IrOp result = build.inst(IrCmd::MUL_VEC, a, invvec); + IrOp result = build.inst(IrCmd::MUL_VEC, a, invvec); - result = build.inst(IrCmd::TAG_VECTOR, result); + result = build.inst(IrCmd::TAG_VECTOR, result); - build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), result); - } - else - { - IrOp x = build.inst(IrCmd::LOAD_FLOAT, arg1, build.constInt(0)); - IrOp y = build.inst(IrCmd::LOAD_FLOAT, arg1, build.constInt(4)); - IrOp z = build.inst(IrCmd::LOAD_FLOAT, arg1, build.constInt(8)); - - IrOp x2 = build.inst(IrCmd::MUL_NUM, x, x); - IrOp y2 = build.inst(IrCmd::MUL_NUM, y, y); - IrOp z2 = build.inst(IrCmd::MUL_NUM, z, z); - - IrOp sum = build.inst(IrCmd::ADD_NUM, build.inst(IrCmd::ADD_NUM, x2, y2), z2); - - IrOp mag = build.inst(IrCmd::SQRT_NUM, sum); - IrOp inv = build.inst(IrCmd::DIV_NUM, build.constDouble(1.0), mag); - - IrOp xr = build.inst(IrCmd::MUL_NUM, x, inv); - IrOp yr = build.inst(IrCmd::MUL_NUM, y, inv); - IrOp zr = build.inst(IrCmd::MUL_NUM, z, inv); - - build.inst(IrCmd::STORE_VECTOR, build.vmReg(ra), xr, yr, zr); - build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TVECTOR)); - } + build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), result); return {BuiltinImplType::Full, 1}; } @@ -1074,31 +1030,10 @@ static BuiltinImplResult translateBuiltinVectorDot(IrBuilder& build, int nparams build.loadAndCheckTag(arg1, LUA_TVECTOR, build.vmExit(pcpos)); build.loadAndCheckTag(args, LUA_TVECTOR, build.vmExit(pcpos)); - IrOp sum; + IrOp a = build.inst(IrCmd::LOAD_TVALUE, arg1, build.constInt(0)); + IrOp b = build.inst(IrCmd::LOAD_TVALUE, args, build.constInt(0)); - if (FFlag::LuauVectorLibNativeDot) - { - IrOp a = build.inst(IrCmd::LOAD_TVALUE, arg1, build.constInt(0)); - IrOp b = build.inst(IrCmd::LOAD_TVALUE, args, build.constInt(0)); - - sum = build.inst(IrCmd::DOT_VEC, a, b); - } - else - { - IrOp x1 = build.inst(IrCmd::LOAD_FLOAT, arg1, build.constInt(0)); - IrOp x2 = build.inst(IrCmd::LOAD_FLOAT, args, build.constInt(0)); - IrOp xx = build.inst(IrCmd::MUL_NUM, x1, x2); - - IrOp y1 = build.inst(IrCmd::LOAD_FLOAT, arg1, build.constInt(4)); - IrOp y2 = build.inst(IrCmd::LOAD_FLOAT, args, build.constInt(4)); - IrOp yy = build.inst(IrCmd::MUL_NUM, y1, y2); - - IrOp z1 = build.inst(IrCmd::LOAD_FLOAT, arg1, build.constInt(8)); - IrOp z2 = build.inst(IrCmd::LOAD_FLOAT, args, build.constInt(8)); - IrOp zz = build.inst(IrCmd::MUL_NUM, z1, z2); - - sum = build.inst(IrCmd::ADD_NUM, build.inst(IrCmd::ADD_NUM, xx, yy), zz); - } + IrOp sum = build.inst(IrCmd::DOT_VEC, a, b); build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), sum); build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER)); diff --git a/CodeGen/src/IrTranslation.cpp b/CodeGen/src/IrTranslation.cpp index d15d57e2..0c22bfc3 100644 --- a/CodeGen/src/IrTranslation.cpp +++ b/CodeGen/src/IrTranslation.cpp @@ -2,7 +2,6 @@ #include "IrTranslation.h" #include "Luau/Bytecode.h" -#include "Luau/BytecodeUtils.h" #include "Luau/CodeGenOptions.h" #include "Luau/IrBuilder.h" #include "Luau/IrUtils.h" @@ -1502,7 +1501,7 @@ bool translateInstNamecall(IrBuilder& build, const Instruction* pc, int pcpos) return false; } - IrOp next = build.blockAtInst(pcpos + getOpLength(LOP_NAMECALL)); + IrOp next = build.blockAtInst(pcpos + getOpLength(LuauOpcode(LOP_NAMECALL))); IrOp fallback = build.block(IrBlockKind::Fallback); IrOp firstFastPathSuccess = build.block(IrBlockKind::Internal); IrOp secondFastPath = build.block(IrBlockKind::Internal); diff --git a/CodeGen/src/IrUtils.cpp b/CodeGen/src/IrUtils.cpp index 54902435..bc04215f 100644 --- a/CodeGen/src/IrUtils.cpp +++ b/CodeGen/src/IrUtils.cpp @@ -16,13 +16,120 @@ #include #include -LUAU_FASTFLAG(LuauVectorLibNativeDot); - namespace Luau { namespace CodeGen { +int getOpLength(LuauOpcode op) +{ + switch (int(op)) + { + case LOP_GETGLOBAL: + case LOP_SETGLOBAL: + case LOP_GETIMPORT: + case LOP_GETTABLEKS: + case LOP_SETTABLEKS: + case LOP_NAMECALL: + case LOP_JUMPIFEQ: + case LOP_JUMPIFLE: + case LOP_JUMPIFLT: + case LOP_JUMPIFNOTEQ: + case LOP_JUMPIFNOTLE: + case LOP_JUMPIFNOTLT: + case LOP_NEWTABLE: + case LOP_SETLIST: + case LOP_FORGLOOP: + case LOP_LOADKX: + case LOP_FASTCALL2: + case LOP_FASTCALL2K: + case LOP_FASTCALL3: + case LOP_JUMPXEQKNIL: + case LOP_JUMPXEQKB: + case LOP_JUMPXEQKN: + case LOP_JUMPXEQKS: + return 2; + + default: + return 1; + } +} + +bool isJumpD(LuauOpcode op) +{ + switch (int(op)) + { + case LOP_JUMP: + case LOP_JUMPIF: + case LOP_JUMPIFNOT: + case LOP_JUMPIFEQ: + case LOP_JUMPIFLE: + case LOP_JUMPIFLT: + case LOP_JUMPIFNOTEQ: + case LOP_JUMPIFNOTLE: + case LOP_JUMPIFNOTLT: + case LOP_FORNPREP: + case LOP_FORNLOOP: + case LOP_FORGPREP: + case LOP_FORGLOOP: + case LOP_FORGPREP_INEXT: + case LOP_FORGPREP_NEXT: + case LOP_JUMPBACK: + case LOP_JUMPXEQKNIL: + case LOP_JUMPXEQKB: + case LOP_JUMPXEQKN: + case LOP_JUMPXEQKS: + return true; + + default: + return false; + } +} + +bool isSkipC(LuauOpcode op) +{ + switch (int(op)) + { + case LOP_LOADB: + return true; + + default: + return false; + } +} + +bool isFastCall(LuauOpcode op) +{ + switch (int(op)) + { + case LOP_FASTCALL: + case LOP_FASTCALL1: + case LOP_FASTCALL2: + case LOP_FASTCALL2K: + case LOP_FASTCALL3: + return true; + + default: + return false; + } +} + +int getJumpTarget(uint32_t insn, uint32_t pc) +{ + LuauOpcode op = LuauOpcode(LUAU_INSN_OP(insn)); + + if (isJumpD(op)) + return int(pc + LUAU_INSN_D(insn) + 1); + else if (isFastCall(op)) + return int(pc + LUAU_INSN_C(insn) + 2); + else if (isSkipC(op) && LUAU_INSN_C(insn)) + return int(pc + LUAU_INSN_C(insn) + 1); + else if (int(op) == LOP_JUMPX) + return int(pc + LUAU_INSN_E(insn) + 1); + else + return -1; +} + IrValueKind getCmdValueKind(IrCmd cmd) { switch (cmd) @@ -83,7 +190,6 @@ IrValueKind getCmdValueKind(IrCmd cmd) case IrCmd::UNM_VEC: return IrValueKind::Tvalue; case IrCmd::DOT_VEC: - LUAU_ASSERT(FFlag::LuauVectorLibNativeDot); return IrValueKind::Double; case IrCmd::NOT_ANY: case IrCmd::CMP_ANY: diff --git a/CodeGen/src/OptimizeConstProp.cpp b/CodeGen/src/OptimizeConstProp.cpp index 8cdd1dc8..11909ec5 100644 --- a/CodeGen/src/OptimizeConstProp.cpp +++ b/CodeGen/src/OptimizeConstProp.cpp @@ -21,7 +21,6 @@ LUAU_FASTINTVARIABLE(LuauCodeGenReuseSlotLimit, 64) LUAU_FASTINTVARIABLE(LuauCodeGenReuseUdataTagLimit, 64) LUAU_FASTINTVARIABLE(LuauCodeGenLiveSlotReuseLimit, 8) LUAU_FASTFLAGVARIABLE(DebugLuauAbortingChecks) -LUAU_FASTFLAG(LuauVectorLibNativeDot) namespace Luau { @@ -1475,9 +1474,6 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& case IrCmd::MUL_VEC: case IrCmd::DIV_VEC: case IrCmd::DOT_VEC: - if (inst.cmd == IrCmd::DOT_VEC) - LUAU_ASSERT(FFlag::LuauVectorLibNativeDot); - if (IrInst* a = function.asInstOp(inst.a); a && a->cmd == IrCmd::TAG_VECTOR) replace(function, inst.a, a->a); diff --git a/Require/Runtime/include/Luau/Require.h b/Require/Runtime/include/Luau/Require.h index 4e3ef555..1765994a 100644 --- a/Require/Runtime/include/Luau/Require.h +++ b/Require/Runtime/include/Luau/Require.h @@ -113,5 +113,9 @@ struct luarequire_Configuration // Populates function pointers in the given luarequire_Configuration. typedef void (*luarequire_Configuration_init)(luarequire_Configuration* config); -// Initializes the require library with the given configuration and context. +// Initializes and pushes the require closure onto the stack without +// registration. +LUALIB_API int lua_pushrequire(lua_State* L, luarequire_Configuration_init config_init, void* ctx); + +// Initializes the require library and registers it globally. LUALIB_API void luaopen_require(lua_State* L, luarequire_Configuration_init config_init, void* ctx); diff --git a/Require/Runtime/src/Require.cpp b/Require/Runtime/src/Require.cpp index 8939ccc2..9884d235 100644 --- a/Require/Runtime/src/Require.cpp +++ b/Require/Runtime/src/Require.cpp @@ -35,7 +35,7 @@ static void validateConfig(lua_State* L, const luarequire_Configuration& config) luaL_error(L, "require configuration is missing required function pointer: load"); } -void luaopen_require(lua_State* L, luarequire_Configuration_init config_init, void* ctx) +int lua_pushrequire(lua_State* L, luarequire_Configuration_init config_init, void* ctx) { luarequire_Configuration* config = static_cast(lua_newuserdata(L, sizeof(luarequire_Configuration))); if (!config) @@ -48,5 +48,11 @@ void luaopen_require(lua_State* L, luarequire_Configuration_init config_init, vo // "require" captures config and ctx as upvalues lua_pushcclosure(L, Luau::Require::lua_require, "require", 2); + return 1; +} + +void luaopen_require(lua_State* L, luarequire_Configuration_init config_init, void* ctx) +{ + lua_pushrequire(L, config_init, ctx); lua_setglobal(L, "require"); } diff --git a/VM/include/lua.h b/VM/include/lua.h index 06f6bd4b..92f84738 100644 --- a/VM/include/lua.h +++ b/VM/include/lua.h @@ -72,14 +72,12 @@ enum lua_Type LUA_TNIL = 0, // must be 0 due to lua_isnoneornil LUA_TBOOLEAN = 1, // must be 1 due to l_isfalse - LUA_TLIGHTUSERDATA, LUA_TNUMBER, LUA_TVECTOR, LUA_TSTRING, // all types above this must be value types, all types below this must be GC types - see iscollectable - LUA_TTABLE, LUA_TFUNCTION, LUA_TUSERDATA, diff --git a/VM/src/ldo.cpp b/VM/src/ldo.cpp index 950e85d6..d3c8de4d 100644 --- a/VM/src/ldo.cpp +++ b/VM/src/ldo.cpp @@ -356,6 +356,7 @@ static void resume(lua_State* L, void* ud) else { // resume from previous yield or break + LUAU_ASSERT(firstArg >= L->base); LUAU_ASSERT(L->status == LUA_YIELD || L->status == LUA_BREAK); L->status = 0; @@ -469,6 +470,8 @@ static void resume_finish(lua_State* L, int status) int lua_resume(lua_State* L, lua_State* from, int nargs) { + api_check(L, L->top - L->base >= nargs); + int status; if (L->status != LUA_YIELD && L->status != LUA_BREAK && (L->status != 0 || L->ci != L->base_ci)) return resume_error(L, "cannot resume non-suspended coroutine", nargs); @@ -498,6 +501,8 @@ int lua_resume(lua_State* L, lua_State* from, int nargs) int lua_resumeerror(lua_State* L, lua_State* from) { + api_check(L, L->top - L->base >= 1); + int status; if (L->status != LUA_YIELD && L->status != LUA_BREAK && (L->status != 0 || L->ci != L->base_ci)) return resume_error(L, "cannot resume non-suspended coroutine", 1); diff --git a/VM/src/lobject.h b/VM/src/lobject.h index 6719faaf..bd2dca94 100644 --- a/VM/src/lobject.h +++ b/VM/src/lobject.h @@ -272,7 +272,7 @@ typedef struct Udata }; } Udata; -typedef struct Buffer +typedef struct LuauBuffer { CommonHeader; diff --git a/VM/src/lstate.h b/VM/src/lstate.h index ad162391..77c674a6 100644 --- a/VM/src/lstate.h +++ b/VM/src/lstate.h @@ -289,7 +289,7 @@ union GCObject struct Proto p; struct UpVal uv; struct lua_State th; // thread - struct Buffer buf; + struct LuauBuffer buf; }; // macros to convert a GCObject into a specific value diff --git a/VM/src/ltm.cpp b/VM/src/ltm.cpp index f6b0079a..58f87260 100644 --- a/VM/src/ltm.cpp +++ b/VM/src/ltm.cpp @@ -16,14 +16,12 @@ const char* const luaT_typenames[] = { "nil", "boolean", - "userdata", "number", "vector", "string", - "table", "function", "userdata", diff --git a/tests/AssemblyBuilderA64.test.cpp b/tests/AssemblyBuilderA64.test.cpp index b730cb1e..ee319a5f 100644 --- a/tests/AssemblyBuilderA64.test.cpp +++ b/tests/AssemblyBuilderA64.test.cpp @@ -10,8 +10,6 @@ using namespace Luau::CodeGen; using namespace Luau::CodeGen::A64; -LUAU_FASTFLAG(LuauVectorLibNativeDot); - static std::string bytecodeAsArray(const std::vector& bytecode) { std::string result = "{"; @@ -389,8 +387,6 @@ TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "FPBasic") TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "FPMath") { - ScopedFastFlag sff{FFlag::LuauVectorLibNativeDot, true}; - SINGLE_COMPARE(fabs(d1, d2), 0x1E60C041); SINGLE_COMPARE(fadd(d1, d2, d3), 0x1E632841); SINGLE_COMPARE(fadd(s29, s29, s28), 0x1E3C2BBD); diff --git a/tests/AssemblyBuilderX64.test.cpp b/tests/AssemblyBuilderX64.test.cpp index fd1deccf..a727ed54 100644 --- a/tests/AssemblyBuilderX64.test.cpp +++ b/tests/AssemblyBuilderX64.test.cpp @@ -10,8 +10,6 @@ using namespace Luau::CodeGen; using namespace Luau::CodeGen::X64; -LUAU_FASTFLAG(LuauVectorLibNativeDot); - static std::string bytecodeAsArray(const std::vector& bytecode) { std::string result = "{"; @@ -571,8 +569,6 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "AVXConversionInstructionForms") TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "AVXTernaryInstructionForms") { - ScopedFastFlag sff{FFlag::LuauVectorLibNativeDot, true}; - SINGLE_COMPARE(vroundsd(xmm7, xmm12, xmm3, RoundingModeX64::RoundToNegativeInfinity), 0xc4, 0xe3, 0x19, 0x0b, 0xfb, 0x09); SINGLE_COMPARE( vroundsd(xmm8, xmm13, xmmword[r13 + rdx], RoundingModeX64::RoundToPositiveInfinity), 0xc4, 0x43, 0x11, 0x0b, 0x44, 0x15, 0x00, 0x0a diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index ad9cf7fd..5419866a 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -34,7 +34,6 @@ void luaC_validate(lua_State* L); LUAU_FASTFLAG(LuauLibWhereErrorAutoreserve) LUAU_FASTFLAG(DebugLuauAbortingChecks) LUAU_FASTINT(CodegenHeuristicsInstructionLimit) -LUAU_FASTFLAG(LuauVectorLibNativeDot) LUAU_DYNAMIC_FASTFLAG(LuauStringFormatFixC) static lua_CompileOptions defaultOptions() @@ -881,8 +880,6 @@ TEST_CASE("Vector") TEST_CASE("VectorLibrary") { - ScopedFastFlag luauVectorLibNativeDot{FFlag::LuauVectorLibNativeDot, true}; - lua_CompileOptions copts = defaultOptions(); SUBCASE("O0") diff --git a/tests/FragmentAutocomplete.test.cpp b/tests/FragmentAutocomplete.test.cpp index 35e78af0..212b9aa6 100644 --- a/tests/FragmentAutocomplete.test.cpp +++ b/tests/FragmentAutocomplete.test.cpp @@ -21,7 +21,6 @@ #include #include - using namespace Luau; LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete) @@ -40,12 +39,12 @@ LUAU_FASTFLAG(LuauClonedTableAndFunctionTypesMustHaveScopes) LUAU_FASTFLAG(LuauDisableNewSolverAssertsInMixedMode) LUAU_FASTFLAG(LuauCloneTypeAliasBindings) LUAU_FASTFLAG(LuauDoNotClonePersistentBindings) -LUAU_FASTFLAG(LuauCloneReturnTypePack) LUAU_FASTFLAG(LuauIncrementalAutocompleteDemandBasedCloning) LUAU_FASTFLAG(LuauUserTypeFunTypecheck) LUAU_FASTFLAG(LuauBetterScopeSelection) LUAU_FASTFLAG(LuauBlockDiffFragmentSelection) LUAU_FASTFLAG(LuauFragmentAcMemoryLeak) +LUAU_FASTFLAG(LuauGlobalVariableModuleIsolation) static std::optional nullCallback(std::string tag, std::optional ptr, std::optional contents) { @@ -83,12 +82,12 @@ struct FragmentAutocompleteFixtureImpl : BaseType ScopedFastFlag luauDisableNewSolverAssertsInMixedMode{FFlag::LuauDisableNewSolverAssertsInMixedMode, true}; ScopedFastFlag luauCloneTypeAliasBindings{FFlag::LuauCloneTypeAliasBindings, true}; ScopedFastFlag luauDoNotClonePersistentBindings{FFlag::LuauDoNotClonePersistentBindings, true}; - ScopedFastFlag luauCloneReturnTypePack{FFlag::LuauCloneReturnTypePack, true}; ScopedFastFlag luauIncrementalAutocompleteDemandBasedCloning{FFlag::LuauIncrementalAutocompleteDemandBasedCloning, true}; ScopedFastFlag luauBetterScopeSelection{FFlag::LuauBetterScopeSelection, true}; ScopedFastFlag luauBlockDiffFragmentSelection{FFlag::LuauBlockDiffFragmentSelection, true}; ScopedFastFlag luauAutocompleteUsesModuleForTypeCompatibility{FFlag::LuauAutocompleteUsesModuleForTypeCompatibility, true}; ScopedFastFlag luauFragmentAcMemoryLeak{FFlag::LuauFragmentAcMemoryLeak, true}; + ScopedFastFlag luauGlobalVariableModuleIsolation{FFlag::LuauGlobalVariableModuleIsolation, true}; FragmentAutocompleteFixtureImpl() : BaseType(true) @@ -3598,6 +3597,30 @@ end } ); } + +TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "NotNull_assertion_caused_by_leaking_free_type_from_stale_module") +{ + const std::string source = R"( +local Players = game:GetService("Players") + +Players.PlayerAdded:Connect(function(Player) + for_,v in script.PlayerValue:GetChildren()do + v + end +end) +)"; + + const std::string dest = R"( +local Players = game:GetService("Players") + +Players.PlayerAdded:Connect(function(Player) + for_,v in script.PlayerValue:GetChildren()do + v:L + end +end) +)"; + autocompleteFragmentInBothSolvers(source, dest, Position{5, 11}, [](auto& result) {}); +} // NOLINTEND(bugprone-unchecked-optional-access) TEST_SUITE_END(); diff --git a/tests/Frontend.test.cpp b/tests/Frontend.test.cpp index 28f479a3..6f115c01 100644 --- a/tests/Frontend.test.cpp +++ b/tests/Frontend.test.cpp @@ -16,8 +16,8 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(DebugLuauFreezeArena) LUAU_FASTFLAG(DebugLuauMagicTypes) -LUAU_FASTFLAG(LuauSelectivelyRetainDFGArena) LUAU_FASTFLAG(LuauImproveTypePathsInErrors) +LUAU_FASTFLAG(LuauNewNonStrictVisitTypes) namespace { @@ -1015,8 +1015,7 @@ TEST_CASE_FIXTURE(FrontendFixture, "environments") LUAU_REQUIRE_NO_ERRORS(resultA); CheckResult resultB = frontend.check("B"); - // In the new non-strict mode, we do not currently support error reporting for unknown symbols in type positions. - if (FFlag::LuauSolverV2) + if (FFlag::LuauSolverV2 && !FFlag::LuauNewNonStrictVisitTypes) LUAU_REQUIRE_NO_ERRORS(resultB); else LUAU_REQUIRE_ERROR_COUNT(1, resultB); @@ -1570,7 +1569,7 @@ TEST_CASE_FIXTURE(FrontendFixture, "check_module_references_correct_ast_root") TEST_CASE_FIXTURE(FrontendFixture, "dfg_data_cleared_on_retain_type_graphs_unset") { - ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauSelectivelyRetainDFGArena, true}}; + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; fileResolver.source["game/A"] = R"( local a = 1 local b = 2 diff --git a/tests/Generalization.test.cpp b/tests/Generalization.test.cpp index 280909d5..8d232981 100644 --- a/tests/Generalization.test.cpp +++ b/tests/Generalization.test.cpp @@ -15,7 +15,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauNonReentrantGeneralization) +LUAU_FASTFLAG(LuauNonReentrantGeneralization2) LUAU_FASTFLAG(DebugLuauForbidInternalTypes) LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope) LUAU_FASTFLAG(LuauTrackInferredFunctionTypeFromCall) @@ -227,7 +227,7 @@ TEST_CASE_FIXTURE(GeneralizationFixture, "('a) -> 'a") TEST_CASE_FIXTURE(GeneralizationFixture, "(t1, (t1 <: 'b)) -> () where t1 = ('a <: (t1 <: 'b) & {number} & {number})") { - ScopedFastFlag sff{FFlag::LuauNonReentrantGeneralization, true}; + ScopedFastFlag sff{FFlag::LuauNonReentrantGeneralization2, true}; TableType tt; tt.indexer = TableIndexer{builtinTypes.numberType, builtinTypes.numberType}; @@ -261,7 +261,7 @@ TEST_CASE_FIXTURE(GeneralizationFixture, "(('a <: number | string)) -> string?") TEST_CASE_FIXTURE(GeneralizationFixture, "(('a <: {'b})) -> ()") { - ScopedFastFlag sff{FFlag::LuauNonReentrantGeneralization, true}; + ScopedFastFlag sff{FFlag::LuauNonReentrantGeneralization2, true}; auto [aTy, aFree] = freshType(); auto [bTy, bFree] = freshType(); diff --git a/tests/InferPolarity.test.cpp b/tests/InferPolarity.test.cpp index aa681a0d..8b1bc040 100644 --- a/tests/InferPolarity.test.cpp +++ b/tests/InferPolarity.test.cpp @@ -8,13 +8,13 @@ using namespace Luau; -LUAU_FASTFLAG(LuauNonReentrantGeneralization); +LUAU_FASTFLAG(LuauNonReentrantGeneralization2); TEST_SUITE_BEGIN("InferPolarity"); TEST_CASE_FIXTURE(Fixture, "T where T = { m: (a) -> T }") { - ScopedFastFlag sff{FFlag::LuauNonReentrantGeneralization, true}; + ScopedFastFlag sff{FFlag::LuauNonReentrantGeneralization2, true}; TypeArena arena; ScopePtr globalScope = std::make_shared(builtinTypes->anyTypePack); diff --git a/tests/NonStrictTypeChecker.test.cpp b/tests/NonStrictTypeChecker.test.cpp index 57e30583..69d02cdc 100644 --- a/tests/NonStrictTypeChecker.test.cpp +++ b/tests/NonStrictTypeChecker.test.cpp @@ -17,8 +17,7 @@ LUAU_FASTFLAG(LuauNewNonStrictWarnOnUnknownGlobals) LUAU_FASTFLAG(LuauNonStrictVisitorImprovements) -LUAU_FASTFLAG(LuauNonStrictFuncDefErrorFix) -LUAU_FASTFLAG(LuauNormalizedBufferIsNotUnknown) +LUAU_FASTFLAG(LuauNewNonStrictVisitTypes) using namespace Luau; @@ -363,7 +362,6 @@ end TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "function_def_sequencing_errors_2") { - ScopedFastFlag luauNonStrictFuncDefErrorFix{FFlag::LuauNonStrictFuncDefErrorFix, true}; ScopedFastFlag luauNonStrictVisitorImprovements{FFlag::LuauNonStrictVisitorImprovements, true}; CheckResult result = checkNonStrict(R"( @@ -668,10 +666,38 @@ TEST_CASE_FIXTURE(Fixture, "unknown_globals_in_non_strict") LUAU_REQUIRE_ERROR_COUNT(2, result); } +TEST_CASE_FIXTURE(BuiltinsFixture, "unknown_types_in_non_strict") +{ + ScopedFastFlag sff{FFlag::LuauNewNonStrictVisitTypes, true}; + + CheckResult result = check(Mode::Nonstrict, R"( + --!nonstrict + local foo: Foo = 1 + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, result); + const UnknownSymbol* err = get(result.errors[0]); + CHECK_EQ(err->name, "Foo"); + CHECK_EQ(err->context, UnknownSymbol::Context::Type); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "unknown_types_in_non_strict_2") +{ + ScopedFastFlag sff{FFlag::LuauNewNonStrictVisitTypes, true}; + + CheckResult result = check(Mode::Nonstrict, R"( + --!nonstrict + local foo = 1 :: Foo + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, result); + const UnknownSymbol* err = get(result.errors[0]); + CHECK_EQ(err->name, "Foo"); + CHECK_EQ(err->context, UnknownSymbol::Context::Type); +} + TEST_CASE_FIXTURE(BuiltinsFixture, "buffer_is_not_unknown") { - ScopedFastFlag luauNormalizedBufferIsNotUnknown{FFlag::LuauNormalizedBufferIsNotUnknown, true}; - CheckResult result = check(Mode::Nonstrict, R"( local function wrap(b: buffer, i: number, v: number) buffer.writeu32(b, i * 4, v) diff --git a/tests/Normalize.test.cpp b/tests/Normalize.test.cpp index e036e459..65448883 100644 --- a/tests/Normalize.test.cpp +++ b/tests/Normalize.test.cpp @@ -10,14 +10,10 @@ #include "Luau/Normalize.h" #include "Luau/BuiltinDefinitions.h" -LUAU_FASTFLAG(LuauNormalizeNegatedErrorToAnError) -LUAU_FASTFLAG(LuauNormalizeIntersectErrorToAnError) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauNormalizeIntersectionLimit) LUAU_FASTINT(LuauNormalizeUnionLimit) -LUAU_FASTFLAG(LuauNormalizeLimitFunctionSet) -LUAU_FASTFLAG(LuauSubtypingStopAtNormFail) LUAU_FASTFLAG(LuauNormalizationCatchMetatableCycles) LUAU_FASTFLAG(LuauSubtypingEnableReasoningLimit) LUAU_FASTFLAG(LuauTypePackDetectCycles) @@ -605,8 +601,6 @@ TEST_CASE_FIXTURE(NormalizeFixture, "intersect_truthy_expressed_as_intersection" TEST_CASE_FIXTURE(NormalizeFixture, "intersect_error") { - ScopedFastFlag luauNormalizeIntersectErrorToAnError{FFlag::LuauNormalizeIntersectErrorToAnError, true}; - std::shared_ptr norm = toNormalizedType(R"(string & AAA)", 1); REQUIRE(norm); CHECK("*error-type*" == toString(normalizer.typeFromNormal(*norm))); @@ -614,9 +608,6 @@ TEST_CASE_FIXTURE(NormalizeFixture, "intersect_error") TEST_CASE_FIXTURE(NormalizeFixture, "intersect_not_error") { - ScopedFastFlag luauNormalizeIntersectErrorToAnError{FFlag::LuauNormalizeIntersectErrorToAnError, true}; - ScopedFastFlag luauNormalizeNegatedErrorToAnError{FFlag::LuauNormalizeNegatedErrorToAnError, true}; - std::shared_ptr norm = toNormalizedType(R"(string & Not<)", 1); REQUIRE(norm); CHECK("*error-type*" == toString(normalizer.typeFromNormal(*norm))); @@ -1199,8 +1190,6 @@ 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 luauNormalizeLimitFunctionSet{FFlag::LuauNormalizeLimitFunctionSet, true}; - ScopedFastFlag luauSubtypingStopAtNormFail{FFlag::LuauSubtypingStopAtNormFail, true}; CheckResult result = check(R"( function _(_).readu32(l0) @@ -1217,8 +1206,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_propagate_normalization_failures") { ScopedFastInt luauNormalizeIntersectionLimit{FInt::LuauNormalizeIntersectionLimit, 50}; ScopedFastInt luauNormalizeUnionLimit{FInt::LuauNormalizeUnionLimit, 20}; - ScopedFastFlag luauNormalizeLimitFunctionSet{FFlag::LuauNormalizeLimitFunctionSet, true}; - ScopedFastFlag luauSubtypingStopAtNormFail{FFlag::LuauSubtypingStopAtNormFail, true}; ScopedFastFlag luauSubtypingEnableReasoningLimit{FFlag::LuauSubtypingEnableReasoningLimit, true}; ScopedFastFlag luauTurnOffNonreentrantGeneralization{FFlag::LuauNonReentrantGeneralization, false}; diff --git a/tests/Parser.test.cpp b/tests/Parser.test.cpp index c0da303e..39fe3662 100644 --- a/tests/Parser.test.cpp +++ b/tests/Parser.test.cpp @@ -16,14 +16,13 @@ LUAU_FASTINT(LuauRecursionLimit) LUAU_FASTINT(LuauTypeLengthLimit) LUAU_FASTINT(LuauParseErrorLimit) LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauAllowComplexTypesInGenericParams) -LUAU_FASTFLAG(LuauErrorRecoveryForTableTypes) LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType) LUAU_FASTFLAG(LuauAstTypeGroup3) LUAU_FASTFLAG(LuauFixDoBlockEndLocation) LUAU_FASTFLAG(LuauParseOptionalAsNode2) LUAU_FASTFLAG(LuauParseStringIndexer) LUAU_FASTFLAG(LuauFixFunctionWithAttributesStartLocation) +LUAU_FASTFLAG(LuauStoreCSTData2) LUAU_DYNAMIC_FASTFLAG(DebugLuauReportReturnTypeVariadicWithTypeSuffix) // Clip with DebugLuauReportReturnTypeVariadicWithTypeSuffix @@ -2579,6 +2578,28 @@ TEST_CASE_FIXTURE(Fixture, "function_start_locations_are_before_attributes") CHECK_EQ(anonymousFunction->location, Location({9, 18}, {10, 11})); } +TEST_CASE_FIXTURE(Fixture, "for_loop_with_single_var_has_comma_positions_of_size_zero") +{ + ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + + ParseOptions parseOptions; + parseOptions.storeCstData = true; + + ParseResult result = parseEx(R"( + for value in tbl do + end + )", parseOptions); + REQUIRE(result.root); + REQUIRE_EQ(1, result.root->body.size); + + auto forLoop = result.root->body.data[0]->as(); + auto baseCstNode = result.cstNodeMap.find(forLoop); + REQUIRE(baseCstNode); + + auto cstNode = (*baseCstNode)->as(); + CHECK_EQ(cstNode->varsCommaPositions.size, 0); +} + TEST_SUITE_END(); TEST_SUITE_BEGIN("ParseErrorRecovery"); @@ -3828,7 +3849,6 @@ TEST_CASE_FIXTURE(Fixture, "mixed_leading_intersection_and_union_not_allowed") TEST_CASE_FIXTURE(Fixture, "grouped_function_type") { - ScopedFastFlag _{FFlag::LuauAllowComplexTypesInGenericParams, true}; const auto root = parse(R"( type X = T local x: X<(() -> ())?> @@ -3865,7 +3885,6 @@ TEST_CASE_FIXTURE(Fixture, "grouped_function_type") TEST_CASE_FIXTURE(Fixture, "complex_union_in_generic_ty") { - ScopedFastFlag _{FFlag::LuauAllowComplexTypesInGenericParams, true}; const auto root = parse(R"( type X = T local x: X< @@ -3902,7 +3921,6 @@ TEST_CASE_FIXTURE(Fixture, "complex_union_in_generic_ty") TEST_CASE_FIXTURE(Fixture, "recover_from_bad_table_type") { - ScopedFastFlag _{FFlag::LuauErrorRecoveryForTableTypes, true}; ParseOptions opts; opts.allowDeclarationSyntax = true; const auto result = tryParse( diff --git a/tests/RequireByString.test.cpp b/tests/RequireByString.test.cpp index 14447c6a..ba3946e4 100644 --- a/tests/RequireByString.test.cpp +++ b/tests/RequireByString.test.cpp @@ -544,7 +544,9 @@ TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireFromLuauBinary") char executable[] = "luau"; std::vector paths = { getLuauDirectory(PathType::Relative) + "/tests/require/without_config/dependency.luau", - getLuauDirectory(PathType::Absolute) + "/tests/require/without_config/dependency.luau" + 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", }; for (const std::string& path : paths) diff --git a/tests/Subtyping.test.cpp b/tests/Subtyping.test.cpp index 46bf5b5f..f5ded76e 100644 --- a/tests/Subtyping.test.cpp +++ b/tests/Subtyping.test.cpp @@ -16,7 +16,6 @@ #include LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauNormalizedBufferIsNotUnknown) using namespace Luau; @@ -967,14 +966,7 @@ TEST_IS_NOT_SUBTYPE(builtinTypes->unknownType, negate(builtinTypes->booleanType) TEST_IS_NOT_SUBTYPE(builtinTypes->unknownType, negate(builtinTypes->numberType)); TEST_IS_NOT_SUBTYPE(builtinTypes->unknownType, negate(builtinTypes->stringType)); TEST_IS_NOT_SUBTYPE(builtinTypes->unknownType, negate(builtinTypes->threadType)); - -TEST_CASE_FIXTURE(SubtypeFixture, "unknown unknownType, negate(builtinTypes->bufferType)); -} +TEST_IS_NOT_SUBTYPE(builtinTypes->unknownType, negate(builtinTypes->bufferType)); TEST_CASE_FIXTURE(SubtypeFixture, "Root <: class") { diff --git a/tests/Transpiler.test.cpp b/tests/Transpiler.test.cpp index e1fe18f8..06a0aa26 100644 --- a/tests/Transpiler.test.cpp +++ b/tests/Transpiler.test.cpp @@ -191,6 +191,13 @@ TEST_CASE("for_in_loop_spaces_around_tokens") CHECK_EQ(five, transpile(five).code); } +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); +} + TEST_CASE("while_loop") { const std::string code = R"( while f(x)do print() end )"; @@ -1997,6 +2004,18 @@ TEST_CASE("transpile_type_table_preserve_property_definition_style") CHECK_EQ(code, transpile(code, {}, true).code); } +TEST_CASE("transpile_type_table_string_properties_spaces_between_tokens") +{ + ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + std::string code = R"( + type Foo = { + [ "$$typeof1"]: string, + ['$$typeof2' ]: string, + } + )"; + CHECK_EQ(code, transpile(code, {}, true).code); +} + TEST_CASE("transpile_types_preserve_parentheses_style") { ScopedFastFlag flags[] = { diff --git a/tests/TypeFunction.user.test.cpp b/tests/TypeFunction.user.test.cpp index a313e039..7277e50e 100644 --- a/tests/TypeFunction.user.test.cpp +++ b/tests/TypeFunction.user.test.cpp @@ -13,6 +13,8 @@ LUAU_FASTFLAG(LuauTypeFunReadWriteParents) LUAU_FASTFLAG(LuauImproveTypePathsInErrors) LUAU_FASTFLAG(LuauUserTypeFunTypecheck) LUAU_FASTFLAG(LuauNewTypeFunReductionChecks2) +LUAU_FASTFLAG(LuauNoTypeFunctionsNamedTypeOf) + TEST_SUITE_BEGIN("UserDefinedTypeFunctionTests"); @@ -462,7 +464,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_negation_methods_work") -- this should never be returned return types.number end - + -- forcing an error here to check the exact type of the negation local function ok(idx: getnegation<>): never return idx end )"); @@ -1113,7 +1115,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_calling_illegal_global") return arg -- this should not be reached end - + local function ok(idx: illegal): nil return idx end )"); @@ -2204,4 +2206,21 @@ local x: wrap<{a: number}> = { a = 2 } CHECK(toString(requireType("x"), ToStringOptions{true}) == "{ a: number }?"); } +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) + return t + end + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, result); + + CHECK("typeof cannot be used as an identifier for a type function or alias" == toString(result.errors[0])); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.aliases.test.cpp b/tests/TypeInfer.aliases.test.cpp index 40592a5e..04d7ae6a 100644 --- a/tests/TypeInfer.aliases.test.cpp +++ b/tests/TypeInfer.aliases.test.cpp @@ -12,9 +12,9 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauFixInfiniteRecursionInNormalization) LUAU_FASTFLAG(LuauImproveTypePathsInErrors) -LUAU_FASTFLAG(LuauBidirectionalInferenceUpcast) LUAU_FASTFLAG(LuauBidirectionalInferenceCollectIndexerTypes) LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations) +LUAU_FASTFLAG(LuauNewNonStrictVisitTypes) TEST_SUITE_BEGIN("TypeAliases"); @@ -256,10 +256,7 @@ TEST_CASE_FIXTURE(Fixture, "dependent_generic_aliases") TEST_CASE_FIXTURE(Fixture, "mutually_recursive_generic_aliases") { - ScopedFastFlag sffs[] = { - {FFlag::LuauBidirectionalInferenceUpcast, true}, - {FFlag::LuauBidirectionalInferenceCollectIndexerTypes, true}, - }; + ScopedFastFlag _{FFlag::LuauBidirectionalInferenceCollectIndexerTypes, true}; CheckResult result = check(R"( --!strict @@ -1103,7 +1100,14 @@ TEST_CASE_FIXTURE(Fixture, "typeof_is_not_a_valid_alias_name") LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK("Type aliases cannot be named typeof" == toString(result.errors[0])); + if (FFlag::LuauSolverV2) + { + CHECK("typeof cannot be used as an identifier for a type function or alias" == toString(result.errors[0])); + } + else + { + CHECK("Type aliases cannot be named typeof" == toString(result.errors[0])); + } } TEST_CASE_FIXTURE(Fixture, "fuzzer_bug_doesnt_crash") @@ -1184,14 +1188,19 @@ TEST_CASE_FIXTURE(Fixture, "bound_type_in_alias_segfault") { ScopedFastFlag sff{FFlag::LuauSolverV2, true}; - LUAU_CHECK_NO_ERRORS(check(R"( + CheckResult result = check(R"( --!nonstrict - type Map = {[ K]: V} + type Map = {[K]: V} function foo:bar(): Config end type Config = Map & { fields: FieldConfigMap} - export type FieldConfig = {[ string]: any} + export type FieldConfig = {[string]: any} export type FieldConfigMap = Map> - )")); + )"); + + if (FFlag::LuauNewNonStrictVisitTypes) + LUAU_CHECK_ERROR_COUNT(2, result); + else + LUAU_CHECK_NO_ERRORS(result); } TEST_CASE_FIXTURE(BuiltinsFixture, "gh1632_no_infinite_recursion_in_normalization") diff --git a/tests/TypeInfer.builtins.test.cpp b/tests/TypeInfer.builtins.test.cpp index 668c7f1b..6a96bd18 100644 --- a/tests/TypeInfer.builtins.test.cpp +++ b/tests/TypeInfer.builtins.test.cpp @@ -12,6 +12,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauTableCloneClonesType3) LUAU_FASTFLAG(LuauImproveTypePathsInErrors) +LUAU_FASTFLAG(LuauArityMismatchOnUndersaturatedUnknownArguments) TEST_SUITE_BEGIN("BuiltinTests"); @@ -719,7 +720,13 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "bad_select_should_not_crash") end )"); - if (FFlag::LuauSolverV2) + if (FFlag::LuauSolverV2 && FFlag::LuauArityMismatchOnUndersaturatedUnknownArguments) + { + LUAU_REQUIRE_ERROR_COUNT(2, result); + CHECK_EQ("Argument count mismatch. Function expects at least 1 argument, but none are specified", toString(result.errors[0])); + CHECK_EQ("Argument count mismatch. Function expects at least 1 argument, but none are specified", toString(result.errors[1])); + } + else if (FFlag::LuauSolverV2) { // Counterintuitively, the parameter l0 is unconstrained and therefore it is valid to pass nil. // The new solver therefore considers that parameter to be optional. diff --git a/tests/TypeInfer.functions.test.cpp b/tests/TypeInfer.functions.test.cpp index eafc78c1..003e574e 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -25,6 +25,9 @@ LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(LuauUngeneralizedTypesForRecursiveFunctions) LUAU_FASTFLAG(LuauImproveTypePathsInErrors) LUAU_FASTFLAG(LuauReduceUnionFollowUnionType) +LUAU_FASTFLAG(LuauArityMismatchOnUndersaturatedUnknownArguments) +LUAU_FASTFLAG(LuauHasPropProperBlock) +LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect) TEST_SUITE_BEGIN("TypeInferFunctions"); @@ -2017,6 +2020,11 @@ TEST_CASE_FIXTURE(Fixture, "free_is_not_bound_to_unknown") TEST_CASE_FIXTURE(Fixture, "dont_infer_parameter_types_for_functions_from_their_call_site") { + ScopedFastFlag sffs[] = { + {FFlag::LuauHasPropProperBlock, true}, + {FFlag::LuauOptimizeFalsyAndTruthyIntersect, true} + }; + CheckResult result = check(R"( local t = {} @@ -2034,13 +2042,20 @@ TEST_CASE_FIXTURE(Fixture, "dont_infer_parameter_types_for_functions_from_their_ local f = t.f )"); - LUAU_REQUIRE_NO_ERRORS(result); CHECK_EQ("(a) -> a", toString(requireType("f"))); + if (FFlag::LuauSolverV2) - CHECK_EQ("({ read p: { read q: unknown } }) -> ~(false?)?", toString(requireType("g"))); + { + // FIXME CLI-143852: Depends on interleaving generalization and type function reduction. + LUAU_REQUIRE_ERRORS(result); + CHECK_EQ("({ read p: unknown }) -> (*error-type* | ~(false?))?", toString(requireType("g"))); + } else + { + LUAU_REQUIRE_NO_ERRORS(result); CHECK_EQ("({+ p: {+ q: nil +} +}) -> nil", toString(requireType("g"))); + } } TEST_CASE_FIXTURE(Fixture, "dont_mutate_the_underlying_head_of_typepack_when_calling_with_self") @@ -2093,7 +2108,10 @@ u.b().foo() CHECK_EQ(toString(result.errors[2]), "Argument count mismatch. Function expects 1 to 3 arguments, but none are specified"); CHECK_EQ(toString(result.errors[3]), "Argument count mismatch. Function expects 2 to 4 arguments, but none are specified"); CHECK_EQ(toString(result.errors[4]), "Argument count mismatch. Function expects at least 1 argument, but none are specified"); - CHECK_EQ(toString(result.errors[5]), "Argument count mismatch. Function expects 2 to 3 arguments, but only 1 is specified"); + if (FFlag::LuauArityMismatchOnUndersaturatedUnknownArguments) + CHECK_EQ(toString(result.errors[5]), "Argument count mismatch. Function expects 3 arguments, but only 1 is specified"); + else + CHECK_EQ(toString(result.errors[5]), "Argument count mismatch. Function expects 2 to 3 arguments, but only 1 is specified"); CHECK_EQ(toString(result.errors[6]), "Argument count mismatch. Function expects at least 1 argument, but none are specified"); CHECK_EQ(toString(result.errors[7]), "Argument count mismatch. Function expects at least 1 argument, but none are specified"); CHECK_EQ(toString(result.errors[8]), "Argument count mismatch. Function expects at least 1 argument, but none are specified"); @@ -2257,7 +2275,7 @@ end LUAU_REQUIRE_ERRORS(result); } -TEST_CASE_FIXTURE(BuiltinsFixture, "dont_assert_when_the_tarjan_limit_is_exceeded_during_generalization") +TEST_CASE_FIXTURE(Fixture, "dont_assert_when_the_tarjan_limit_is_exceeded_during_generalization") { ScopedFastFlag sff{FFlag::LuauSolverV2, true}; ScopedFastInt sfi{FInt::LuauTarjanChildLimit, 1}; @@ -2268,9 +2286,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "dont_assert_when_the_tarjan_limit_is_exceede end )"); - LUAU_REQUIRE_ERROR_COUNT(1, result); - - CHECK_MESSAGE(get(result.errors[0]), "Expected UnificationTooComplex but got: " << toString(result.errors[0])); + LUAU_REQUIRE_ERROR(result, UnificationTooComplex); } /* We had a bug under DCR where instantiated type packs had a nullptr scope. @@ -2986,6 +3002,8 @@ 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}; + CheckResult result = check(R"( function foo(player) local success,result = player:thing() @@ -3013,7 +3031,7 @@ TEST_CASE_FIXTURE(Fixture, "unifier_should_not_bind_free_types") auto tm2 = get(result.errors[1]); REQUIRE(tm2); CHECK(toString(tm2->wantedTp) == "string"); - CHECK(toString(tm2->givenTp) == "(buffer | class | function | number | string | table | thread | true) & unknown"); + CHECK(toString(tm2->givenTp) == "~(false?)"); } else { diff --git a/tests/TypeInfer.generics.test.cpp b/tests/TypeInfer.generics.test.cpp index f9e3f460..9ddf1b6b 100644 --- a/tests/TypeInfer.generics.test.cpp +++ b/tests/TypeInfer.generics.test.cpp @@ -144,8 +144,6 @@ TEST_CASE_FIXTURE(Fixture, "properties_can_be_polytypes") TEST_CASE_FIXTURE(Fixture, "properties_can_be_instantiated_polytypes") { - DOES_NOT_PASS_NEW_SOLVER_GUARD(); - CheckResult result = check(R"( local t: { m: (number)->number } = { m = function(x:number) return x+1 end } local function id(x:a):a return x end @@ -260,10 +258,8 @@ TEST_CASE_FIXTURE(Fixture, "check_mutual_generic_functions_errors") } } -TEST_CASE_FIXTURE(Fixture, "generic_functions_in_types_old_solver") +TEST_CASE_FIXTURE(Fixture, "generic_functions_in_types") { - DOES_NOT_PASS_NEW_SOLVER_GUARD(); - CheckResult result = check(R"( type T = { id: (a) -> a } local x: T = { id = function(x:a):a return x end } @@ -273,19 +269,6 @@ TEST_CASE_FIXTURE(Fixture, "generic_functions_in_types_old_solver") LUAU_REQUIRE_NO_ERRORS(result); } -TEST_CASE_FIXTURE(Fixture, "generic_functions_in_types_new_solver") -{ - ScopedFastFlag sff{FFlag::LuauSolverV2, true}; - - CheckResult result = check(R"( - type T = { read id: (a) -> a } - local x: T = { id = function(x:a):a return x end } - local y: string = x.id("hi") - local z: number = x.id(37) - )"); - LUAU_REQUIRE_NO_ERRORS(result); -} - TEST_CASE_FIXTURE(Fixture, "generic_factories") { DOES_NOT_PASS_NEW_SOLVER_GUARD(); @@ -311,8 +294,6 @@ TEST_CASE_FIXTURE(Fixture, "generic_factories") TEST_CASE_FIXTURE(Fixture, "factories_of_generics") { - DOES_NOT_PASS_NEW_SOLVER_GUARD(); - CheckResult result = check(R"( type T = { id: (a) -> a } type Factory = { build: () -> T } @@ -330,6 +311,7 @@ TEST_CASE_FIXTURE(Fixture, "factories_of_generics") local y: string = x.id("hi") local z: number = x.id(37) )"); + LUAU_REQUIRE_NO_ERRORS(result); } @@ -509,6 +491,7 @@ TEST_CASE_FIXTURE(Fixture, "dont_substitute_bound_types") local x: T = t.m(37) end )"); + LUAU_REQUIRE_NO_ERRORS(result); } @@ -1370,29 +1353,27 @@ TEST_CASE_FIXTURE(Fixture, "substitution_with_bound_table") TEST_CASE_FIXTURE(Fixture, "apply_type_function_nested_generics1") { - // CLI-114507: temporarily changed to have a cast for `object` to silence false positive error - // https://github.com/luau-lang/luau/issues/484 CheckResult result = check(R"( ---!strict -type MyObject = { - getReturnValue: (cb: () -> V) -> V -} -local object: MyObject = { - getReturnValue = function(cb: () -> U): U - return cb() - end, -} :: MyObject + --!strict + type MyObject = { + getReturnValue: (cb: () -> V) -> V + } + local object: MyObject = { + getReturnValue = function(cb: () -> U): U + return cb() + end, + } -type ComplexObject = { - id: T, - nested: MyObject -} + type ComplexObject = { + id: T, + nested: MyObject + } -local complex: ComplexObject = { - id = "Foo", - nested = object, -} + local complex: ComplexObject = { + id = "Foo", + nested = object, + } )"); LUAU_REQUIRE_NO_ERRORS(result); diff --git a/tests/TypeInfer.intersectionTypes.test.cpp b/tests/TypeInfer.intersectionTypes.test.cpp index de5745dd..fb178b6f 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(LuauImproveTypePathsInErrors) +LUAU_FASTFLAG(LuauNarrowIntersectionNevers) TEST_SUITE_BEGIN("IntersectionTypes"); @@ -1779,4 +1780,25 @@ TEST_CASE_FIXTURE(Fixture, "cli_80596_simplify_more_realistic_intersections") LUAU_REQUIRE_NO_ERRORS(result); } +TEST_CASE_FIXTURE(BuiltinsFixture, "narrow_intersection_nevers") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag narrowIntersections{FFlag::LuauNarrowIntersectionNevers, true}; + + loadDefinition(R"( + declare class Player + Character: unknown + end + )"); + LUAU_REQUIRE_NO_ERRORS(check(R"( + local function foo(player: Player?) + if player and player.Character then + print(player.Character) + end + end + )")); + + CHECK_EQ("Player & { Character: ~(false?) }", toString(requireTypeAtPosition({3, 23}))); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.modules.test.cpp b/tests/TypeInfer.modules.test.cpp index 5ee5aba2..c39920c4 100644 --- a/tests/TypeInfer.modules.test.cpp +++ b/tests/TypeInfer.modules.test.cpp @@ -231,7 +231,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "require_module_that_does_not_export") TEST_CASE_FIXTURE(BuiltinsFixture, "warn_if_you_try_to_require_a_non_modulescript") { fileResolver.source["Modules/A"] = ""; - fileResolver.sourceTypes["Modules/A"] = SourceCode::Local; + fileResolver.sourceTypes["Modules/A"] = SourceCode::Script; fileResolver.source["Modules/B"] = R"( local M = require(script.Parent.A) diff --git a/tests/TypeInfer.oop.test.cpp b/tests/TypeInfer.oop.test.cpp index a2143b35..45f54ac2 100644 --- a/tests/TypeInfer.oop.test.cpp +++ b/tests/TypeInfer.oop.test.cpp @@ -12,15 +12,13 @@ using namespace Luau; -LUAU_FASTFLAG(LuauSolverV2); +LUAU_FASTFLAG(LuauSolverV2) +LUAU_FASTFLAG(LuauArityMismatchOnUndersaturatedUnknownArguments) TEST_SUITE_BEGIN("TypeInferOOP"); TEST_CASE_FIXTURE(Fixture, "dont_suggest_using_colon_rather_than_dot_if_not_defined_with_colon") { - // CLI-116571 method calls are missing arity checking? - DOES_NOT_PASS_NEW_SOLVER_GUARD(); - CheckResult result = check(R"( local someTable = {} @@ -30,15 +28,15 @@ TEST_CASE_FIXTURE(Fixture, "dont_suggest_using_colon_rather_than_dot_if_not_defi someTable.Function1() -- Argument count mismatch )"); - LUAU_REQUIRE_ERROR_COUNT(1, result); - REQUIRE(get(result.errors[0])); + if (!FFlag::LuauSolverV2 || FFlag::LuauArityMismatchOnUndersaturatedUnknownArguments) + { + LUAU_REQUIRE_ERROR_COUNT(1, result); + REQUIRE(get(result.errors[0])); + } } TEST_CASE_FIXTURE(Fixture, "dont_suggest_using_colon_rather_than_dot_if_it_wont_help_2") { - // CLI-116571 method calls are missing arity checking? - DOES_NOT_PASS_NEW_SOLVER_GUARD(); - CheckResult result = check(R"( local someTable = {} @@ -48,8 +46,11 @@ TEST_CASE_FIXTURE(Fixture, "dont_suggest_using_colon_rather_than_dot_if_it_wont_ someTable.Function2() -- Argument count mismatch )"); - LUAU_REQUIRE_ERROR_COUNT(1, result); - REQUIRE(get(result.errors[0])); + if (!FFlag::LuauSolverV2 || FFlag::LuauArityMismatchOnUndersaturatedUnknownArguments) + { + LUAU_REQUIRE_ERROR_COUNT(1, result); + REQUIRE(get(result.errors[0])); + } } TEST_CASE_FIXTURE(Fixture, "dont_suggest_using_colon_rather_than_dot_if_another_overload_works") diff --git a/tests/TypeInfer.refinements.test.cpp b/tests/TypeInfer.refinements.test.cpp index 98238bb6..7adfad3b 100644 --- a/tests/TypeInfer.refinements.test.cpp +++ b/tests/TypeInfer.refinements.test.cpp @@ -2567,4 +2567,29 @@ TEST_CASE_FIXTURE(Fixture, "oss_1687_equality_shouldnt_leak_nil") )")); } +TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1451") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauWeakNilRefinementType, true} + }; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + type Part = { + HasTag: (Part, string) -> boolean, + Name: string, + } + local myList = {} :: {Part} + local nextPart = (table.remove(myList)) :: Part + + if nextPart:HasTag("foo") then + return + end + + print(nextPart.Name) + + )")); +} + + TEST_SUITE_END(); diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index 8985cd9f..7e9b607e 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -24,14 +24,14 @@ LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering) LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope) LUAU_FASTFLAG(LuauTrackInteriorFreeTablesOnScope) LUAU_FASTFLAG(LuauFollowTableFreeze) -LUAU_FASTFLAG(LuauNonReentrantGeneralization) -LUAU_FASTFLAG(LuauBidirectionalInferenceUpcast) +LUAU_FASTFLAG(LuauNonReentrantGeneralization2) LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint) LUAU_FASTFLAG(LuauSearchForRefineableType) LUAU_FASTFLAG(LuauImproveTypePathsInErrors) LUAU_FASTFLAG(LuauBidirectionalInferenceCollectIndexerTypes) LUAU_FASTFLAG(LuauBidirectionalFailsafe) LUAU_FASTFLAG(LuauBidirectionalInferenceElideAssert) +LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect) TEST_SUITE_BEGIN("TableTests"); @@ -702,7 +702,7 @@ TEST_CASE_FIXTURE(Fixture, "indexers_get_quantified_too") LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::LuauSolverV2 && FFlag::LuauNonReentrantGeneralization) + if (FFlag::LuauSolverV2 && FFlag::LuauNonReentrantGeneralization2) CHECK("({a}) -> ()" == toString(requireType("swap"))); else if (FFlag::LuauSolverV2) CHECK("({unknown}) -> ()" == toString(requireType("swap"))); @@ -4678,7 +4678,7 @@ TEST_CASE_FIXTURE(Fixture, "table_writes_introduce_write_properties") if (!FFlag::LuauSolverV2) return; - ScopedFastFlag sff[] = {{FFlag::LuauNonReentrantGeneralization, true}}; + ScopedFastFlag sff[] = {{FFlag::LuauNonReentrantGeneralization2, true}}; CheckResult result = check(R"( function oc(player, speaker) @@ -4717,6 +4717,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tables_can_have_both_metatables_and_indexers TEST_CASE_FIXTURE(Fixture, "refined_thing_can_be_an_array") { + ScopedFastFlag _{FFlag::LuauOptimizeFalsyAndTruthyIntersect, true}; + CheckResult result = check(R"( function foo(x, y) if x then @@ -4727,9 +4729,17 @@ TEST_CASE_FIXTURE(Fixture, "refined_thing_can_be_an_array") end )"); - LUAU_REQUIRE_NO_ERRORS(result); - - CHECK("({a}, a) -> a" == toString(requireType("foo"))); + if (FFlag::LuauSolverV2) + { + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK(get(result.errors[0])); + CHECK_EQ("(unknown, *error-type*) -> *error-type*", toString(requireType("foo"))); + } + else + { + LUAU_REQUIRE_NO_ERRORS(result); + CHECK("({a}, a) -> a" == toString(requireType("foo"))); + } } TEST_CASE_FIXTURE(Fixture, "parameter_was_set_an_indexer_and_bounded_by_string") @@ -5395,10 +5405,6 @@ TEST_CASE_FIXTURE(Fixture, "inference_in_constructor") TEST_CASE_FIXTURE(Fixture, "returning_optional_in_table") { - ScopedFastFlag sffs[] = { - {FFlag::LuauBidirectionalInferenceUpcast, true}, - }; - LUAU_CHECK_NO_ERRORS(check(R"( local Numbers = { zero = 0 } local function FuncA(): { Value: number? } @@ -5430,10 +5436,7 @@ TEST_CASE_FIXTURE(Fixture, "returning_mismatched_optional_in_table") TEST_CASE_FIXTURE(Fixture, "optional_function_in_table") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauBidirectionalInferenceUpcast, true}, - }; + ScopedFastFlag _{FFlag::LuauSolverV2, true}; LUAU_CHECK_NO_ERRORS(check(R"( local t: { (() -> ())? } = { @@ -5456,10 +5459,6 @@ TEST_CASE_FIXTURE(Fixture, "optional_function_in_table") TEST_CASE_FIXTURE(Fixture, "oss_1596_expression_in_table") { - ScopedFastFlag sffs[] = { - {FFlag::LuauBidirectionalInferenceUpcast, true}, - }; - LUAU_CHECK_NO_ERRORS(check(R"( type foo = {abc: number?} local x: foo = {abc = 100} @@ -5492,7 +5491,6 @@ TEST_CASE_FIXTURE(Fixture, "missing_fields_bidirectional_inference") { ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, - {FFlag::LuauBidirectionalInferenceUpcast, true}, {FFlag::LuauBidirectionalInferenceCollectIndexerTypes, true}, }; @@ -5524,7 +5522,6 @@ TEST_CASE_FIXTURE(Fixture, "generic_index_syntax_bidirectional_infer_with_tables { ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, - {FFlag::LuauBidirectionalInferenceUpcast, true}, {FFlag::LuauBidirectionalInferenceCollectIndexerTypes, true}, }; @@ -5559,8 +5556,8 @@ TEST_CASE_FIXTURE(Fixture, "deeply_nested_classish_inference") ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, {FFlag::LuauSearchForRefineableType, true}, - {FFlag::DebugLuauAssertOnForcedConstraint, true}, }; + // NOTE: This probably should be revisited after CLI-143852: we end up // cyclic types with *tons* of overlap. LUAU_REQUIRE_NO_ERRORS(check(R"( @@ -5686,5 +5683,26 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_match_literal_type_crash_again") LUAU_REQUIRE_NO_ERRORS(result); } +TEST_CASE_FIXTURE(Fixture, "type_mismatch_in_dict") +{ + ScopedFastFlag sff{FFlag::LuauBidirectionalInferenceCollectIndexerTypes, true}; + + CheckResult result = check(R"( + --!strict + local dict: {[string]: boolean} = { + code1 = true, + code2 = 123, + } + )"); + 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 }"); + } +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.test.cpp b/tests/TypeInfer.test.cpp index 88a98013..4ffd961c 100644 --- a/tests/TypeInfer.test.cpp +++ b/tests/TypeInfer.test.cpp @@ -20,21 +20,24 @@ LUAU_FASTFLAG(LuauFixLocationSpanTableIndexExpr) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTINT(LuauCheckRecursionLimit) -LUAU_FASTFLAG(LuauGlobalSelfAssignmentCycle) LUAU_FASTINT(LuauNormalizeCacheLimit) LUAU_FASTINT(LuauRecursionLimit) +LUAU_FASTINT(LuauTypeInferTypePackLoopLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTFLAG(LuauAstTypeGroup3) LUAU_FASTFLAG(LuauNewNonStrictWarnOnUnknownGlobals) -LUAU_FASTFLAG(LuauInferLocalTypesInMultipleAssignments) -LUAU_FASTFLAG(LuauUnifyMetatableWithAny) -LUAU_FASTFLAG(LuauExtraFollows) LUAU_FASTFLAG(LuauImproveTypePathsInErrors) LUAU_FASTFLAG(LuauTypeCheckerAcceptNumberConcats) LUAU_FASTFLAG(LuauPreprocessTypestatedArgument) LUAU_FASTFLAG(LuauCacheInferencePerAstExpr) +LUAU_FASTFLAG(LuauLimitIterationWhenCheckingArgumentCounts) LUAU_FASTFLAG(LuauMagicFreezeCheckBlocked) -LUAU_FASTFLAG(LuauNonReentrantGeneralization) +LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope) +LUAU_FASTFLAG(LuauNonReentrantGeneralization2) +LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect) +LUAU_FASTFLAG(LuauHasPropProperBlock) +LUAU_FASTFLAG(LuauStringPartLengthLimit) +LUAU_FASTFLAG(LuauSimplificationRecheckAssumption) using namespace Luau; @@ -1822,7 +1825,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "infer_types_of_globals") TEST_CASE_FIXTURE(Fixture, "multiple_assignment") { ScopedFastFlag sff_LuauSolverV2{FFlag::LuauSolverV2, true}; - ScopedFastFlag sff_InferLocalTypesInMultipleAssignments{FFlag::LuauInferLocalTypesInMultipleAssignments, true}; CheckResult result = check(R"( local function requireString(arg: string) end @@ -1842,8 +1844,6 @@ TEST_CASE_FIXTURE(Fixture, "multiple_assignment") TEST_CASE_FIXTURE(Fixture, "fuzz_global_self_assignment") { - ScopedFastFlag luauGlobalSelfAssignmentCycle{FFlag::LuauGlobalSelfAssignmentCycle, true}; - // Shouldn't assert or crash check(R"( _ = _ @@ -1853,8 +1853,6 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_global_self_assignment") TEST_CASE_FIXTURE(BuiltinsFixture, "getmetatable_works_with_any") { - ScopedFastFlag _{FFlag::LuauUnifyMetatableWithAny, true}; - LUAU_REQUIRE_NO_ERRORS(check(R"( return { new = function(name: string) @@ -1872,8 +1870,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "getmetatable_works_with_any") TEST_CASE_FIXTURE(BuiltinsFixture, "getmetatable_infer_any_ret") { - ScopedFastFlag _{FFlag::LuauUnifyMetatableWithAny, true}; - LUAU_REQUIRE_NO_ERRORS(check(R"( local function spooky(x: any) return getmetatable(x) @@ -1885,10 +1881,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "getmetatable_infer_any_ret") TEST_CASE_FIXTURE(BuiltinsFixture, "getmetatable_infer_any_param") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauUnifyMetatableWithAny, true}, - }; + ScopedFastFlag _{FFlag::LuauSolverV2, true}; auto result = check(R"( local function check(x): any @@ -1896,15 +1889,12 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "getmetatable_infer_any_param") end )"); - // CLI-144695: We're leaking the `MT` generic here, this happens regardless - // of if `LuauUnifyMetatableWithAny` is set. + // CLI-144695: We're leaking the `MT` generic here. CHECK_EQ("({ @metatable MT, {+ +} }) -> any", toString(requireType("check"))); } TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_pack_check_missing_follow") { - ScopedFastFlag luauExtraFollows{FFlag::LuauExtraFollows, true}; - // Shouldn't assert or crash check(R"( _ = n255 @@ -1917,8 +1907,6 @@ end TEST_CASE_FIXTURE(Fixture, "fuzzer_unify_with_free_missing_follow") { - ScopedFastFlag luauExtraFollows{FFlag::LuauExtraFollows, true}; - // Shouldn't assert or crash check(R"( for _ in ... do @@ -2035,5 +2023,114 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_assert_table_freeze_constraint_solving" LUAU_REQUIRE_NO_ERROR(results, ConstraintSolvingIncompleteError); } +TEST_CASE_FIXTURE(BuiltinsFixture, "konnichiwa" * doctest::timeout(0.25)) +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, false}, + {FFlag::LuauInstantiateInSubtyping, true}, + {FFlag::LuauLimitIterationWhenCheckingArgumentCounts, true}, + }; + + ScopedFastInt sfi{FInt::LuauTypeInferTypePackLoopLimit, 100}; + + CheckResult result = check(R"(pcall(table.unpack({pcall})))"); + + LUAU_REQUIRE_ERROR(result, CodeTooComplex); +} + +TEST_CASE_FIXTURE(Fixture, "fuzz_generalize_one_remove_type_assert") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauTrackInteriorFreeTypesOnScope, true}, + {FFlag::LuauHasPropProperBlock, true}, + {FFlag::LuauNonReentrantGeneralization2, true}, + {FFlag::LuauOptimizeFalsyAndTruthyIntersect, true} + }; + + auto result = check(R"( + local _ = {_ = _}, l0 + _ += _ + while _ do + while _[_] do + if _.n0 then + _ = _ + else + _ = _ + return _ + end + do + while _ do + _, _ = nil + end + return function() + end + end + while _[_] do + _ = _._VERSION, "" + end + end + local _ + 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::LuauTrackInteriorFreeTypesOnScope, true}, + {FFlag::LuauNonReentrantGeneralization2, true}, + {FFlag::LuauOptimizeFalsyAndTruthyIntersect, true}, + }; + + CheckResult result = check(R"( + local _ = {n0 = _.n0}, -_, _ + _ += _.n0 + _ /= _[_] + while _.n110 do + while _._ do + while _ do + while _ do + _ = _ + end + end + while _[_] do + function _() + end + end + end + while ... do + end + end + )"); + LUAU_REQUIRE_ERRORS(result); + LUAU_REQUIRE_NO_ERROR(result, ConstraintSolvingIncompleteError); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_simplify_combinatorial_explosion") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauTrackInteriorFreeTypesOnScope, true}, + {FFlag::LuauHasPropProperBlock, true}, + {FFlag::LuauNonReentrantGeneralization2, true}, + {FFlag::LuauOptimizeFalsyAndTruthyIntersect, true}, + {FFlag::LuauStringPartLengthLimit, true}, + {FFlag::LuauSimplificationRecheckAssumption, true}, + }; + + LUAU_REQUIRE_ERRORS(check(R"( +_ = {[_[`{_ + ...}`]]=_,_,[{_=nil,[_._G]=false,}]={[_[_[_]][_][_ / ...]]=_,[...]=false,_,},[_[_][_][_]]=l255,},"" +local _ + )")); + + LUAU_REQUIRE_ERRORS(check(R"( +_ = {[(_G)]=_,[_[_[_]][_[_]][nil][_]]={_G=_,},_[_[_]][_][_],n0={[_]=_,_G=_,},248,} +local _ + )")); +} TEST_SUITE_END(); diff --git a/tests/TypeInfer.unionTypes.test.cpp b/tests/TypeInfer.unionTypes.test.cpp index 628dfbdd..17d35fd1 100644 --- a/tests/TypeInfer.unionTypes.test.cpp +++ b/tests/TypeInfer.unionTypes.test.cpp @@ -10,6 +10,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) +LUAU_FASTFLAG(DebugLuauGreedyGeneralization) LUAU_FASTFLAG(LuauImproveTypePathsInErrors) TEST_SUITE_BEGIN("UnionTypes"); @@ -983,9 +984,14 @@ TEST_CASE_FIXTURE(Fixture, "less_greedy_unification_with_union_types") LUAU_REQUIRE_NO_ERRORS(result); - CHECK_EQ( - "(({ read x: unknown } & { x: number }) | ({ read x: unknown } & { x: string })) -> { x: number } | { x: string }", toString(requireType("f")) - ); + if (FFlag::DebugLuauGreedyGeneralization) + CHECK_EQ( + "(({ read x: a } & { x: number }) | ({ read x: a } & { x: string })) -> { x: number } | { x: string }", toString(requireType("f")) + ); + else + CHECK_EQ( + "(({ read x: unknown } & { x: number }) | ({ read x: unknown } & { x: string })) -> { x: number } | { x: string }", toString(requireType("f")) + ); } TEST_CASE_FIXTURE(Fixture, "less_greedy_unification_with_union_types_2")