From e190754565fd9aae95d81007902fe184193b0689 Mon Sep 17 00:00:00 2001 From: Vighnesh-V Date: Fri, 27 Jun 2025 13:14:36 -0700 Subject: [PATCH] Sync to upstream/release/680 (#1894) # What's Changed? This week includes many changes to bring the behaviours of the Old and New Luau Type Solver more in line. * The old solver now stringifies tables identically to the new solver. Sealed tables are stringified as `{ ... }` and unsealed tables are represented by `{| ... |}`, regardless of your choice of solver. ## New Type Solver * Miscellaneous fixes to make the Luau Frontend able to dynamically toggle which solve is used. * Small fixes to reduce instances of nondeterminism of the New Type Solver. * Issue an error when a function that has multiple non-viable overloads is used. * Subtyping now returns more information about the generics for type inference to consume. * Stop stuck type-functions from blocking type inference. This should lead to fewer instances of 'type inference failed to complete'. ## Fragment Autocomplete * Fixed a bug where incremental autocomplete wouldn't be able to provide results directly on a required module script. `require(script.Module).{request completions here}` will now recommend the properties returned by the required object. --- Co-authored-by: Andy Friesen Co-authored-by: Hunter Goldstein Co-authored-by: Sora Kanosue Co-authored-by: Talha Pathan Co-authored-by: Vighnesh Vijay --- Analysis/include/Luau/Constraint.h | 7 +- Analysis/include/Luau/ConstraintSolver.h | 9 +- Analysis/include/Luau/EqSatSimplification.h | 1 - Analysis/include/Luau/Error.h | 11 +- Analysis/include/Luau/Frontend.h | 9 +- Analysis/include/Luau/GlobalTypes.h | 4 +- Analysis/include/Luau/Normalize.h | 17 +- Analysis/include/Luau/OrderedSet.h | 68 +++++ Analysis/include/Luau/OverloadResolution.h | 7 + Analysis/include/Luau/Subtyping.h | 1 + Analysis/include/Luau/Type.h | 25 +- Analysis/include/Luau/TypeChecker2.h | 10 + Analysis/include/Luau/TypePack.h | 2 + Analysis/include/Luau/TypePath.h | 110 +++++++- Analysis/src/Autocomplete.cpp | 4 +- Analysis/src/AutocompleteCore.cpp | 2 +- Analysis/src/BuiltinDefinitions.cpp | 18 +- Analysis/src/Constraint.cpp | 155 ++++++++++- Analysis/src/ConstraintSolver.cpp | 266 +++++++++++------- Analysis/src/Error.cpp | 25 +- Analysis/src/FragmentAutocomplete.cpp | 230 +++++++++++++++- Analysis/src/Frontend.cpp | 43 +-- Analysis/src/Generalization.cpp | 63 +---- Analysis/src/GlobalTypes.cpp | 5 +- Analysis/src/IostreamHelpers.cpp | 2 + Analysis/src/NonStrictTypeChecker.cpp | 2 +- Analysis/src/Normalize.cpp | 99 +++++-- Analysis/src/OverloadResolution.cpp | 120 +++++++- Analysis/src/Simplify.cpp | 8 +- Analysis/src/Subtyping.cpp | 145 ++++++++-- Analysis/src/ToString.cpp | 75 ++++- Analysis/src/Transpiler.cpp | 18 +- Analysis/src/Type.cpp | 5 +- Analysis/src/TypeChecker2.cpp | 143 ++++++++-- Analysis/src/TypeFunction.cpp | 103 ++++++- Analysis/src/TypeInfer.cpp | 2 +- Analysis/src/TypePack.cpp | 27 ++ Analysis/src/TypePath.cpp | 286 ++++++++++++++++++-- Analysis/src/Unifier.cpp | 3 - Analysis/src/Unifier2.cpp | 7 + Ast/src/Parser.cpp | 35 +-- tests/ConstraintGeneratorFixture.h | 2 +- tests/Fixture.cpp | 12 + tests/Fixture.h | 6 + tests/FragmentAutocomplete.test.cpp | 62 ++++- tests/Frontend.test.cpp | 3 +- tests/Normalize.test.cpp | 17 +- tests/Subtyping.test.cpp | 11 +- tests/ToString.test.cpp | 13 +- tests/Transpiler.test.cpp | 10 - tests/TypeFunction.test.cpp | 116 +++++++- tests/TypeFunction.user.test.cpp | 12 +- tests/TypeInfer.builtins.test.cpp | 10 + tests/TypeInfer.functions.test.cpp | 31 ++- tests/TypeInfer.generics.test.cpp | 112 ++++++-- tests/TypeInfer.intersectionTypes.test.cpp | 23 +- tests/TypeInfer.modules.test.cpp | 4 +- tests/TypeInfer.provisional.test.cpp | 19 +- tests/TypeInfer.refinements.test.cpp | 30 +- tests/TypeInfer.tables.test.cpp | 16 +- tests/TypeInfer.test.cpp | 15 +- tests/TypeInfer.tryUnify.test.cpp | 2 +- tests/TypeInfer.unknownnever.test.cpp | 9 +- tests/TypePath.test.cpp | 171 +++++++++--- 64 files changed, 2342 insertions(+), 536 deletions(-) create mode 100644 Analysis/include/Luau/OrderedSet.h diff --git a/Analysis/include/Luau/Constraint.h b/Analysis/include/Luau/Constraint.h index c48c6ea9..40ba150a 100644 --- a/Analysis/include/Luau/Constraint.h +++ b/Analysis/include/Luau/Constraint.h @@ -6,6 +6,7 @@ #include "Luau/NotNull.h" #include "Luau/Variant.h" #include "Luau/TypeFwd.h" +#include "Luau/TypeIds.h" #include #include @@ -316,7 +317,11 @@ struct Constraint std::vector> dependencies; - DenseHashSet getMaybeMutatedFreeTypes() const; + // Clip with LuauUseOrderedTypeSetsInConstraints + DenseHashSet getMaybeMutatedFreeTypes_DEPRECATED() const; + + TypeIds getMaybeMutatedFreeTypes() const; + }; using ConstraintPtr = std::unique_ptr; diff --git a/Analysis/include/Luau/ConstraintSolver.h b/Analysis/include/Luau/ConstraintSolver.h index 7765aa1e..331bc3f8 100644 --- a/Analysis/include/Luau/ConstraintSolver.h +++ b/Analysis/include/Luau/ConstraintSolver.h @@ -11,6 +11,7 @@ #include "Luau/Location.h" #include "Luau/Module.h" #include "Luau/Normalize.h" +#include "Luau/OrderedSet.h" #include "Luau/Substitution.h" #include "Luau/ToString.h" #include "Luau/Type.h" @@ -121,8 +122,12 @@ struct ConstraintSolver // A mapping from free types to the number of unresolved constraints that mention them. DenseHashMap unresolvedConstraints{{}}; - std::unordered_map, DenseHashSet> maybeMutatedFreeTypes; - std::unordered_map> mutatedFreeTypeToConstraint; + // Clip with LuauUseOrderedTypeSetsInConstraints + std::unordered_map, DenseHashSet> maybeMutatedFreeTypes_DEPRECATED; + std::unordered_map> mutatedFreeTypeToConstraint_DEPRECATED; + + std::unordered_map, TypeIds> maybeMutatedFreeTypes; + std::unordered_map> mutatedFreeTypeToConstraint; // Irreducible/uninhabited type functions or type pack functions. DenseHashSet uninhabitedTypeFunctions{{}}; diff --git a/Analysis/include/Luau/EqSatSimplification.h b/Analysis/include/Luau/EqSatSimplification.h index 16d00849..eb33090e 100644 --- a/Analysis/include/Luau/EqSatSimplification.h +++ b/Analysis/include/Luau/EqSatSimplification.h @@ -4,7 +4,6 @@ #include "Luau/TypeFwd.h" #include "Luau/NotNull.h" -#include "Luau/DenseHash.h" #include #include diff --git a/Analysis/include/Luau/Error.h b/Analysis/include/Luau/Error.h index 2ad82fb9..dbf4c9f5 100644 --- a/Analysis/include/Luau/Error.h +++ b/Analysis/include/Luau/Error.h @@ -496,6 +496,14 @@ struct GenericTypePackCountMismatch bool operator==(const GenericTypePackCountMismatch& rhs) const; }; +// Error during subtyping when the number of generic type packs between compared types does not match +struct MultipleNonviableOverloads +{ + size_t attemptedArgCount; + + bool operator==(const MultipleNonviableOverloads& rhs) const; +}; + using TypeErrorData = Variant< TypeMismatch, UnknownSymbol, @@ -550,7 +558,8 @@ using TypeErrorData = Variant< UnexpectedArrayLikeTableItem, CannotCheckDynamicStringFormatCalls, GenericTypeCountMismatch, - GenericTypePackCountMismatch>; + GenericTypePackCountMismatch, + MultipleNonviableOverloads>; struct TypeErrorSummary { diff --git a/Analysis/include/Luau/Frontend.h b/Analysis/include/Luau/Frontend.h index 3b5906b0..983b41d9 100644 --- a/Analysis/include/Luau/Frontend.h +++ b/Analysis/include/Luau/Frontend.h @@ -173,11 +173,10 @@ struct Frontend Frontend(FileResolver* fileResolver, ConfigResolver* configResolver, const FrontendOptions& options = {}); - void setLuauSolverSelectionFromWorkspace(bool newSolverEnabled); - bool getLuauSolverSelection() const; - bool getLuauSolverSelectionFlagged() const; + void setLuauSolverSelectionFromWorkspace(SolverMode mode); + SolverMode getLuauSolverMode() const; // The default value assuming there is no workspace setup yet - std::atomic useNewLuauSolver{FFlag::LuauSolverV2}; + std::atomic useNewLuauSolver{FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old}; // Parse module graph and prepare SourceNode/SourceModule data, including required dependencies without running typechecking void parse(const ModuleName& name); void parseModules(const std::vector& name); @@ -205,6 +204,7 @@ struct Frontend void clearStats(); void clear(); + void clearBuiltinEnvironments(); ScopePtr addEnvironment(const std::string& environmentName); ScopePtr getEnvironmentScope(const std::string& environmentName) const; @@ -272,7 +272,6 @@ private: static LintResult classifyLints(const std::vector& warnings, const Config& config); ScopePtr getModuleEnvironment(const SourceModule& module, const Config& config, bool forAutocomplete) const; - std::unordered_map environments; std::unordered_map> builtinDefinitions; diff --git a/Analysis/include/Luau/GlobalTypes.h b/Analysis/include/Luau/GlobalTypes.h index c9079edd..023667a5 100644 --- a/Analysis/include/Luau/GlobalTypes.h +++ b/Analysis/include/Luau/GlobalTypes.h @@ -13,7 +13,7 @@ namespace Luau struct GlobalTypes { - explicit GlobalTypes(NotNull builtinTypes); + explicit GlobalTypes(NotNull builtinTypes, SolverMode mode); NotNull builtinTypes; // Global types are based on builtin types @@ -22,6 +22,8 @@ struct GlobalTypes ScopePtr globalScope; // shared by all modules ScopePtr globalTypeFunctionScope; // shared by all modules + + SolverMode mode = SolverMode::Old; }; } // namespace Luau diff --git a/Analysis/include/Luau/Normalize.h b/Analysis/include/Luau/Normalize.h index 71062efa..4a79830a 100644 --- a/Analysis/include/Luau/Normalize.h +++ b/Analysis/include/Luau/Normalize.h @@ -28,7 +28,8 @@ bool isSubtype( NotNull scope, NotNull builtinTypes, NotNull simplifier, - InternalErrorReporter& ice + InternalErrorReporter& ice, + SolverMode solverMode ); bool isSubtype( TypePackId subPack, @@ -36,7 +37,8 @@ bool isSubtype( NotNull scope, NotNull builtinTypes, NotNull simplifier, - InternalErrorReporter& ice + InternalErrorReporter& ice, + SolverMode solverMode ); } // namespace Luau @@ -311,14 +313,21 @@ class Normalizer DenseHashMap, bool, TypeIdPairHash> cachedIsInhabitedIntersection{{nullptr, nullptr}}; bool withinResourceLimits(); + bool useNewLuauSolver() const; public: TypeArena* arena; NotNull builtinTypes; NotNull sharedState; bool cacheInhabitance = false; - - Normalizer(TypeArena* arena, NotNull builtinTypes, NotNull sharedState, bool cacheInhabitance = false); + SolverMode solverMode; + Normalizer( + TypeArena* arena, + NotNull builtinTypes, + NotNull sharedState, + SolverMode solver, + bool cacheInhabitance = false + ); Normalizer(const Normalizer&) = delete; Normalizer(Normalizer&&) = delete; Normalizer() = delete; diff --git a/Analysis/include/Luau/OrderedSet.h b/Analysis/include/Luau/OrderedSet.h new file mode 100644 index 00000000..0d70be4e --- /dev/null +++ b/Analysis/include/Luau/OrderedSet.h @@ -0,0 +1,68 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details + +#pragma once + +#include +#include "Luau/DenseHash.h" + +namespace Luau +{ + +template +struct OrderedSet +{ + using iterator = typename std::vector::iterator; + using const_iterator = typename std::vector::const_iterator; + + bool empty() const + { + return elements.empty(); + } + + size_t size() const + { + return elements.size(); + } + + void insert(T t) + { + if (!elementSet.contains(t)) + { + elementSet.insert(t); + elements.push_back(t); + } + } + + iterator begin() + { + return elements.begin(); + } + + const_iterator begin() const + { + return elements.begin(); + } + + iterator end() + { + return elements.end(); + } + + const_iterator end() const + { + return elements.end(); + } + + /// Move the underlying vector out of the OrderedSet. + std::vector takeVector() + { + elementSet.clear(); + return std::move(elements); + } + +private: + std::vector elements; + DenseHashSet elementSet{nullptr}; +}; + +} \ No newline at end of file diff --git a/Analysis/include/Luau/OverloadResolution.h b/Analysis/include/Luau/OverloadResolution.h index 7337a868..8a186e7f 100644 --- a/Analysis/include/Luau/OverloadResolution.h +++ b/Analysis/include/Luau/OverloadResolution.h @@ -87,6 +87,13 @@ private: ); size_t indexof(Analysis analysis); void add(Analysis analysis, TypeId ty, ErrorVec&& errors); + void maybeEmplaceError( + ErrorVec* errors, + Location argLocation, + const SubtypingReasoning* reason, + std::optional failedSubTy, + std::optional failedSuperTy + ) const; }; struct SolveResult diff --git a/Analysis/include/Luau/Subtyping.h b/Analysis/include/Luau/Subtyping.h index 86a6d039..eeeaaba8 100644 --- a/Analysis/include/Luau/Subtyping.h +++ b/Analysis/include/Luau/Subtyping.h @@ -71,6 +71,7 @@ struct SubtypingResult /// The reason for isSubtype to be false. May not be present even if /// isSubtype is false, depending on the input types. SubtypingReasonings reasoning{kEmptyReasoning}; + DenseHashMap mappedGenericPacks{nullptr}; // If this subtype result required testing free types, we might be making // assumptions about what the free type eventually resolves to. If so, diff --git a/Analysis/include/Luau/Type.h b/Analysis/include/Luau/Type.h index 95c0c891..24741061 100644 --- a/Analysis/include/Luau/Type.h +++ b/Analysis/include/Luau/Type.h @@ -39,6 +39,12 @@ struct Constraint; struct Subtyping; struct TypeChecker2; +enum struct SolverMode +{ + Old, + New +}; + /** * There are three kinds of type variables: * - `Free` variables are metavariables, which stand for unconstrained types. @@ -612,6 +618,21 @@ struct UserDefinedFunctionData DenseHashMap> environmentAlias{""}; }; +enum struct TypeFunctionInstanceState +{ + // Indicates that further reduction might be possible. + Unsolved, + + // Further reduction is not possible because one of the parameters is generic. + Solved, + + // Further reduction is not possible because the application is undefined. + // This always indicates an error in the code. + // + // eg add + Stuck, +}; + /** * An instance of a type function that has not yet been reduced to a more concrete * type. The constraint solver receives a constraint to reduce each @@ -629,6 +650,8 @@ struct TypeFunctionInstanceType std::optional userFuncName; // Name of the user-defined type function; only available for UDTFs UserDefinedFunctionData userFuncData; + TypeFunctionInstanceState state = TypeFunctionInstanceState::Unsolved; + TypeFunctionInstanceType( NotNull function, std::vector typeArguments, @@ -970,7 +993,7 @@ struct BuiltinTypes TypeId errorRecoveryType(TypeId guess) const; TypePackId errorRecoveryTypePack(TypePackId guess) const; - friend TypeId makeStringMetatable(NotNull builtinTypes); + friend TypeId makeStringMetatable(NotNull builtinTypes, SolverMode mode); friend struct GlobalTypes; private: diff --git a/Analysis/include/Luau/TypeChecker2.h b/Analysis/include/Luau/TypeChecker2.h index 7e0cb9ea..663f262d 100644 --- a/Analysis/include/Luau/TypeChecker2.h +++ b/Analysis/include/Luau/TypeChecker2.h @@ -225,11 +225,21 @@ private: // Avoid duplicate warnings being emitted for the same global variable. DenseHashSet warnedGlobals{""}; + void suggestAnnotations(AstExprFunction* expr, TypeId ty); + void diagnoseMissingTableKey(UnknownProperty* utk, TypeErrorData& data) const; bool isErrorSuppressing(Location loc, TypeId ty); bool isErrorSuppressing(Location loc1, TypeId ty1, Location loc2, TypeId ty2); bool isErrorSuppressing(Location loc, TypePackId tp); bool isErrorSuppressing(Location loc1, TypePackId tp1, Location loc2, TypePackId tp2); + + // Returns whether we reported any errors + bool reportNonviableOverloadErrors( + std::vector> nonviableOverloads, + Location callFuncLocation, + size_t argHeadSize, + Location callLocation + ); }; } // namespace Luau diff --git a/Analysis/include/Luau/TypePack.h b/Analysis/include/Luau/TypePack.h index a93f6fca..cd917686 100644 --- a/Analysis/include/Luau/TypePack.h +++ b/Analysis/include/Luau/TypePack.h @@ -2,6 +2,7 @@ #pragma once #include "Luau/Common.h" +#include "Luau/DenseHash.h" #include "Luau/NotNull.h" #include "Luau/Polarity.h" #include "Luau/TypeFwd.h" @@ -230,6 +231,7 @@ bool isEmpty(TypePackId tp); /// Flattens out a type pack. Also returns a valid TypePackId tail if the type pack's full size is not known std::pair, std::optional> flatten(TypePackId tp); std::pair, std::optional> flatten(TypePackId tp, const TxnLog& log); +std::pair, std::optional> flatten(TypePackId tp, const DenseHashMap& mappedGenericPacks); /// Returs true if the type pack arose from a function that is declared to be variadic. /// Returns *false* for function argument packs that are inferred to be safe to oversaturate! diff --git a/Analysis/include/Luau/TypePath.h b/Analysis/include/Luau/TypePath.h index b161c8a2..3462a456 100644 --- a/Analysis/include/Luau/TypePath.h +++ b/Analysis/include/Luau/TypePath.h @@ -1,9 +1,11 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #pragma once +#include "Luau/DenseHash.h" +#include "Luau/NotNull.h" +#include "Luau/TypeArena.h" #include "Luau/TypeFwd.h" #include "Luau/Variant.h" -#include "Luau/NotNull.h" #include #include @@ -91,6 +93,15 @@ enum class PackField Tail, }; +/// Represents a one-sided slice of a type pack with a head and a tail. The slice starts at the type at starting index and includes the tail. +struct PackSlice +{ + /// The 0-based index to start the slice at. + size_t start_index; + + bool operator==(const PackSlice& other) const; +}; + /// Component that represents the result of a reduction /// `resultType` is `never` if the reduction could not proceed struct Reduction @@ -102,7 +113,7 @@ struct Reduction /// A single component of a path, representing one inner type or type pack to /// traverse into. -using Component = Luau::Variant; +using Component = Luau::Variant; /// A path through a type or type pack accessing a particular type or type pack /// contained within. @@ -177,6 +188,7 @@ struct PathHash size_t operator()(const Index& idx) const; size_t operator()(const TypeField& field) const; size_t operator()(const PackField& field) const; + size_t operator()(const PackSlice& slice) const; size_t operator()(const Reduction& reduction) const; size_t operator()(const Component& component) const; size_t operator()(const Path& path) const; @@ -205,6 +217,7 @@ struct PathBuilder PathBuilder& args(); PathBuilder& rets(); PathBuilder& tail(); + PathBuilder& packSlice(size_t start_index); }; } // namespace TypePath @@ -218,35 +231,116 @@ std::string toString(const TypePath::Path& path, bool prefixDot = false); /// Converts a Path to a human readable string for error reporting. std::string toStringHuman(const TypePath::Path& path); -std::optional traverse(TypeId root, const Path& path, NotNull builtinTypes); -std::optional traverse(TypePackId root, const Path& path, NotNull builtinTypes); +// TODO: clip traverse_DEPRECATED along with `LuauReturnMappedGenericPacksFromSubtyping` +std::optional traverse_DEPRECATED(TypeId root, const Path& path, NotNull builtinTypes); +std::optional traverse_DEPRECATED(TypePackId root, const Path& path, NotNull builtinTypes); +std::optional traverse( + TypePackId root, + const Path& path, + NotNull builtinTypes, + NotNull> mappedGenericPacks, + NotNull arena +); +std::optional traverse( + TypeId root, + const Path& path, + NotNull builtinTypes, + NotNull> mappedGenericPacks, + NotNull arena +); + +/// Traverses a path from a type to its end point, which must be a type. This overload will fail if the path contains a PackSlice component or a +/// mapped generic pack. +/// @param root the entry point of the traversal +/// @param path the path to traverse +/// @param builtinTypes the built-in types in use (used to acquire the string metatable) +/// @returns the TypeId at the end of the path, or nullopt if the traversal failed. +std::optional traverseForType_DEPRECATED(TypeId root, const Path& path, NotNull builtinTypes); /// Traverses a path from a type to its end point, which must be a type. /// @param root the entry point of the traversal /// @param path the path to traverse /// @param builtinTypes the built-in types in use (used to acquire the string metatable) +/// @param mappedGenericPacks the mapping for any encountered generic packs we want to reify +/// @param arena a TypeArena, required if path has a PackSlice component /// @returns the TypeId at the end of the path, or nullopt if the traversal failed. -std::optional traverseForType(TypeId root, const Path& path, NotNull builtinTypes); +std::optional traverseForType( + TypeId root, + const Path& path, + NotNull builtinTypes, + NotNull> mappedGenericPacks, + NotNull arena +); /// Traverses a path from a type pack to its end point, which must be a type. /// @param root the entry point of the traversal /// @param path the path to traverse /// @param builtinTypes the built-in types in use (used to acquire the string metatable) /// @returns the TypeId at the end of the path, or nullopt if the traversal failed. -std::optional traverseForType(TypePackId root, const Path& path, NotNull builtinTypes); +std::optional traverseForType_DEPRECATED(TypePackId root, const Path& path, NotNull builtinTypes); + +/// Traverses a path from a type pack to its end point, which must be a type. +/// @param root the entry point of the traversal +/// @param path the path to traverse +/// @param builtinTypes the built-in types in use (used to acquire the string metatable) +/// @param mappedGenericPacks the mapping for any encountered generic packs we want to reify +/// @param arena a TypeArena, required if path has a PackSlice component +/// @returns the TypeId at the end of the path, or nullopt if the traversal failed. +std::optional traverseForType( + TypePackId root, + const Path& path, + NotNull builtinTypes, + NotNull> mappedGenericPacks, + NotNull arena +); + +/// Traverses a path from a type to its end point, which must be a type pack. This overload will fail if the path contains a PackSlice component or a +/// mapped generic pack. +/// @param root the entry point of the traversal +/// @param path the path to traverse +/// @param builtinTypes the built-in types in use (used to acquire the string metatable) +/// @returns the TypePackId at the end of the path, or nullopt if the traversal failed. +std::optional traverseForPack_DEPRECATED(TypeId root, const Path& path, NotNull builtinTypes); /// Traverses a path from a type to its end point, which must be a type pack. /// @param root the entry point of the traversal /// @param path the path to traverse /// @param builtinTypes the built-in types in use (used to acquire the string metatable) +/// @param mappedGenericPacks the mapping for any encountered generic packs we want to reify +/// @param arena a TypeArena, required if path has a PackSlice component /// @returns the TypePackId at the end of the path, or nullopt if the traversal failed. -std::optional traverseForPack(TypeId root, const Path& path, NotNull builtinTypes); +std::optional traverseForPack( + TypeId root, + const Path& path, + NotNull builtinTypes, + NotNull> mappedGenericPacks, + NotNull arena +); /// Traverses a path from a type pack to its end point, which must be a type pack. /// @param root the entry point of the traversal /// @param path the path to traverse /// @param builtinTypes the built-in types in use (used to acquire the string metatable) /// @returns the TypePackId at the end of the path, or nullopt if the traversal failed. -std::optional traverseForPack(TypePackId root, const Path& path, NotNull builtinTypes); +std::optional traverseForPack_DEPRECATED(TypePackId root, const Path& path, NotNull builtinTypes); + +/// Traverses a path from a type pack to its end point, which must be a type pack. +/// @param root the entry point of the traversal +/// @param path the path to traverse +/// @param builtinTypes the built-in types in use (used to acquire the string metatable) +/// @param mappedGenericPacks the mapping for any encountered generic packs we want to reify +/// @param arena a TypeArena, required if path has a PackSlice component +/// @returns the TypePackId at the end of the path, or nullopt if the traversal failed. +std::optional traverseForPack( + TypePackId root, + const Path& path, + NotNull builtinTypes, + NotNull> mappedGenericPacks, + NotNull arena +); + +/// Traverses a path of Index and PackSlices to compute the index of the type the path points to +/// Returns std::nullopt if the path isn't n PackSlice components followed by an Index component +std::optional traverseForIndex(const Path& path); } // namespace Luau diff --git a/Analysis/src/Autocomplete.cpp b/Analysis/src/Autocomplete.cpp index 8e0444da..6576fd16 100644 --- a/Analysis/src/Autocomplete.cpp +++ b/Analysis/src/Autocomplete.cpp @@ -24,7 +24,7 @@ AutocompleteResult autocomplete(Frontend& frontend, const ModuleName& moduleName return {}; ModulePtr module; - if (FFlag::LuauSolverV2) + if (frontend.getLuauSolverMode() == SolverMode::New) module = frontend.moduleResolver.getModule(moduleName); else module = frontend.moduleResolverForAutocomplete.getModule(moduleName); @@ -34,7 +34,7 @@ AutocompleteResult autocomplete(Frontend& frontend, const ModuleName& moduleName NotNull builtinTypes = frontend.builtinTypes; Scope* globalScope; - if (FFlag::LuauSolverV2) + if (frontend.getLuauSolverMode() == SolverMode::New) globalScope = frontend.globals.globalScope.get(); else globalScope = frontend.globalsForAutocomplete.globalScope.get(); diff --git a/Analysis/src/AutocompleteCore.cpp b/Analysis/src/AutocompleteCore.cpp index cc108cb1..3ac8915e 100644 --- a/Analysis/src/AutocompleteCore.cpp +++ b/Analysis/src/AutocompleteCore.cpp @@ -162,7 +162,7 @@ static bool checkTypeMatch( InternalErrorReporter iceReporter; UnifierSharedState unifierState(&iceReporter); SimplifierPtr simplifier = newSimplifier(NotNull{typeArena}, builtinTypes); - Normalizer normalizer{typeArena, builtinTypes, NotNull{&unifierState}}; + Normalizer normalizer{typeArena, builtinTypes, NotNull{&unifierState}, module.checkedInNewSolver ? SolverMode::New : SolverMode::Old}; if (module.checkedInNewSolver) { TypeCheckLimits limits; diff --git a/Analysis/src/BuiltinDefinitions.cpp b/Analysis/src/BuiltinDefinitions.cpp index 3954bc25..81f1c738 100644 --- a/Analysis/src/BuiltinDefinitions.cpp +++ b/Analysis/src/BuiltinDefinitions.cpp @@ -10,6 +10,7 @@ #include "Luau/Error.h" #include "Luau/Frontend.h" #include "Luau/InferPolarity.h" +#include "Luau/Module.h" #include "Luau/NotNull.h" #include "Luau/Subtyping.h" #include "Luau/Symbol.h" @@ -37,6 +38,7 @@ LUAU_FASTFLAGVARIABLE(LuauStringFormatImprovements) LUAU_FASTFLAGVARIABLE(LuauMagicFreezeCheckBlocked2) LUAU_FASTFLAGVARIABLE(LuauUpdateSetMetatableTypeSignature) LUAU_FASTFLAGVARIABLE(LuauUpdateGetMetatableTypeSignature) +LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver) namespace Luau { @@ -317,7 +319,7 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC if (FFlag::LuauEagerGeneralization4) globalScope = globals.globalScope.get(); - if (FFlag::LuauSolverV2) + if (frontend.getLuauSolverMode() == SolverMode::New) builtinTypeFunctions().addToScope(NotNull{&arena}, NotNull{globals.globalScope.get()}); LoadDefinitionFileResult loadResult = frontend.loadDefinitionFile( @@ -390,7 +392,7 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC TypeId genericT = arena.addType(GenericType{globalScope, "T"}); - if (FFlag::LuauSolverV2 && FFlag::LuauUpdateGetMetatableTypeSignature) + if ((frontend.getLuauSolverMode() == SolverMode::New) && FFlag::LuauUpdateGetMetatableTypeSignature) { // getmetatable : (T) -> getmetatable TypeId getmtReturn = arena.addType(TypeFunctionInstanceType{builtinTypeFunctions().getmetatableFunc, {genericT}}); @@ -402,7 +404,7 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC addGlobalBinding(globals, "getmetatable", makeFunction(arena, std::nullopt, {genericMT}, {}, {tableMetaMT}, {genericMT}), "@luau"); } - if (FFlag::LuauSolverV2) + if (frontend.getLuauSolverMode() == SolverMode::New) { TypeId tMetaMT = arena.addType(MetatableType{genericT, genericMT}); @@ -452,7 +454,7 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC attachMagicFunction(getGlobalBinding(globals, "assert"), std::make_shared()); - if (FFlag::LuauSolverV2) + if (frontend.getLuauSolverMode() == SolverMode::New) { // declare function assert(value: T, errorMessage: string?): intersect TypeId genericT = arena.addType(GenericType{globalScope, "T"}); @@ -471,7 +473,7 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC if (TableType* ttv = getMutable(getGlobalBinding(globals, "table"))) { - if (FFlag::LuauSolverV2) + if (frontend.getLuauSolverMode() == SolverMode::New) { // CLI-114044 - The new solver does not yet support generic tables, // which act, in an odd way, like generics that are constrained to @@ -1227,7 +1229,7 @@ bool MagicFind::infer(const MagicFunctionCallContext& context) return true; } -TypeId makeStringMetatable(NotNull builtinTypes) +TypeId makeStringMetatable(NotNull builtinTypes, SolverMode mode) { NotNull arena{builtinTypes->arena.get()}; @@ -1243,7 +1245,9 @@ TypeId makeStringMetatable(NotNull builtinTypes) const TypePackId oneStringPack = arena->addTypePack({stringType}); const TypePackId anyTypePack = builtinTypes->anyTypePack; - const TypePackId variadicTailPack = FFlag::LuauSolverV2 ? builtinTypes->unknownTypePack : anyTypePack; + const TypePackId variadicTailPack = (FFlag::LuauUseWorkspacePropToChooseSolver && mode == SolverMode::New) ? builtinTypes->unknownTypePack + : FFlag::LuauSolverV2 ? builtinTypes->unknownTypePack + : anyTypePack; const TypePackId emptyPack = arena->addTypePack({}); const TypePackId stringVariadicList = arena->addTypePack(TypePackVar{VariadicTypePack{stringType}}); const TypePackId numberVariadicList = arena->addTypePack(TypePackVar{VariadicTypePack{numberType}}); diff --git a/Analysis/src/Constraint.cpp b/Analysis/src/Constraint.cpp index 8fc31512..ad3c1e28 100644 --- a/Analysis/src/Constraint.cpp +++ b/Analysis/src/Constraint.cpp @@ -15,12 +15,63 @@ Constraint::Constraint(NotNull scope, const Location& location, Constrain { } -struct ReferenceCountInitializer : TypeOnceVisitor +struct ReferenceCountInitializer_DEPRECATED : TypeOnceVisitor { DenseHashSet* result; bool traverseIntoTypeFunctions = true; - explicit ReferenceCountInitializer(DenseHashSet* result) + explicit ReferenceCountInitializer_DEPRECATED(DenseHashSet* result) + : result(result) + { + } + + bool visit(TypeId ty, const FreeType&) override + { + result->insert(ty); + return false; + } + + bool visit(TypeId ty, const BlockedType&) override + { + result->insert(ty); + return false; + } + + bool visit(TypeId ty, const PendingExpansionType&) override + { + result->insert(ty); + return false; + } + + bool visit(TypeId ty, const TableType& tt) override + { + if (FFlag::LuauEagerGeneralization4) + { + if (tt.state == TableState::Unsealed || tt.state == TableState::Free) + result->insert(ty); + } + + return true; + } + + bool visit(TypeId ty, const ExternType&) override + { + // ExternTypes never contain free types. + return false; + } + + bool visit(TypeId, const TypeFunctionInstanceType&) override + { + return FFlag::LuauEagerGeneralization4 && traverseIntoTypeFunctions; + } +}; + +struct ReferenceCountInitializer : TypeOnceVisitor +{ + NotNull result; + bool traverseIntoTypeFunctions = true; + + explicit ReferenceCountInitializer(NotNull result) : result(result) { } @@ -78,7 +129,7 @@ bool isReferenceCountedType(const TypeId typ) return get(typ) || get(typ) || get(typ); } -DenseHashSet Constraint::getMaybeMutatedFreeTypes() const +DenseHashSet Constraint::getMaybeMutatedFreeTypes_DEPRECATED() const { // For the purpose of this function and reference counting in general, we are only considering // mutations that affect the _bounds_ of the free type, and not something that may bind the free @@ -86,7 +137,103 @@ DenseHashSet Constraint::getMaybeMutatedFreeTypes() const // contribution to the output set here. DenseHashSet types{{}}; - ReferenceCountInitializer rci{&types}; + ReferenceCountInitializer_DEPRECATED rci{&types}; + + if (auto ec = get(*this)) + { + rci.traverse(ec->resultType); + // `EqualityConstraints` should not mutate `assignmentType`. + } + else if (auto sc = get(*this)) + { + rci.traverse(sc->subType); + rci.traverse(sc->superType); + } + else if (auto psc = get(*this)) + { + rci.traverse(psc->subPack); + rci.traverse(psc->superPack); + } + else if (auto itc = get(*this)) + { + for (TypeId ty : itc->variables) + rci.traverse(ty); + // `IterableConstraints` should not mutate `iterator`. + } + else if (auto nc = get(*this)) + { + rci.traverse(nc->namedType); + } + else if (auto taec = get(*this)) + { + rci.traverse(taec->target); + } + else if (auto fchc = get(*this)) + { + rci.traverse(fchc->argsPack); + } + else if (auto fcc = get(*this); fcc && FFlag::LuauEagerGeneralization4) + { + rci.traverseIntoTypeFunctions = false; + rci.traverse(fcc->fn); + rci.traverse(fcc->argsPack); + rci.traverseIntoTypeFunctions = true; + } + else if (auto ptc = get(*this)) + { + rci.traverse(ptc->freeType); + } + else if (auto hpc = get(*this)) + { + rci.traverse(hpc->resultType); + if (FFlag::LuauEagerGeneralization4) + rci.traverse(hpc->subjectType); + } + else if (auto hic = get(*this)) + { + if (FFlag::LuauEagerGeneralization4) + rci.traverse(hic->subjectType); + rci.traverse(hic->resultType); + // `HasIndexerConstraint` should not mutate `indexType`. + } + else if (auto apc = get(*this)) + { + rci.traverse(apc->lhsType); + rci.traverse(apc->rhsType); + } + else if (auto aic = get(*this)) + { + rci.traverse(aic->lhsType); + rci.traverse(aic->indexType); + rci.traverse(aic->rhsType); + } + else if (auto uc = get(*this)) + { + for (TypeId ty : uc->resultPack) + rci.traverse(ty); + // `UnpackConstraint` should not mutate `sourcePack`. + } + else if (auto rpc = get(*this)) + { + rci.traverse(rpc->tp); + } + else if (auto tcc = get(*this)) + { + rci.traverse(tcc->exprType); + } + + return types; +} + +TypeIds Constraint::getMaybeMutatedFreeTypes() const +{ + // For the purpose of this function and reference counting in general, we are only considering + // mutations that affect the _bounds_ of the free type, and not something that may bind the free + // type itself to a new type. As such, `ReduceConstraint` and `GeneralizationConstraint` have no + // contribution to the output set here. + + TypeIds types; + ReferenceCountInitializer rci{NotNull{&types}}; if (auto ec = get(*this)) { diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index d40cac68..adfedacd 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -33,14 +33,15 @@ LUAU_FASTFLAGVARIABLE(DebugLuauLogBindings) LUAU_FASTINTVARIABLE(LuauSolverRecursionLimit, 500) LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification) LUAU_FASTFLAG(LuauEagerGeneralization4) +LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch) LUAU_FASTFLAGVARIABLE(LuauAddCallConstraintForIterableFunctions) LUAU_FASTFLAGVARIABLE(LuauGuardAgainstMalformedTypeAliasExpansion2) LUAU_FASTFLAGVARIABLE(LuauInsertErrorTypesIntoIndexerResult) -LUAU_FASTFLAGVARIABLE(LuauClipVariadicAnysFromArgsToGenericFuncs2) LUAU_FASTFLAGVARIABLE(LuauAvoidGenericsLeakingDuringFunctionCallCheck) LUAU_FASTFLAGVARIABLE(LuauMissingFollowInAssignIndexConstraint) LUAU_FASTFLAGVARIABLE(LuauRemoveTypeCallsForReadWriteProps) LUAU_FASTFLAGVARIABLE(LuauTableLiteralSubtypeCheckFunctionCalls) +LUAU_FASTFLAGVARIABLE(LuauUseOrderedTypeSetsInConstraints) namespace Luau { @@ -418,10 +419,21 @@ void ConstraintSolver::run() // Free types that have no constraints at all can be generalized right away. if (FFlag::LuauEagerGeneralization4) { - for (TypeId ty : constraintSet.freeTypes) + if (FFlag::LuauUseOrderedTypeSetsInConstraints) { - if (auto it = mutatedFreeTypeToConstraint.find(ty); it == mutatedFreeTypeToConstraint.end() || it->second.empty()) - generalizeOneType(ty); + for (TypeId ty : constraintSet.freeTypes) + { + if (auto it = mutatedFreeTypeToConstraint.find(ty); it == mutatedFreeTypeToConstraint.end() || it->second.empty()) + generalizeOneType(ty); + } + } + else + { + for (TypeId ty : constraintSet.freeTypes) + { + if (auto it = mutatedFreeTypeToConstraint_DEPRECATED.find(ty); it == mutatedFreeTypeToConstraint_DEPRECATED.end() || it->second.empty()) + generalizeOneType(ty); + } } } @@ -466,40 +478,80 @@ void ConstraintSolver::run() unblock(c); unsolvedConstraints.erase(unsolvedConstraints.begin() + ptrdiff_t(i)); - const auto maybeMutated = maybeMutatedFreeTypes.find(c); - if (maybeMutated != maybeMutatedFreeTypes.end()) + if (FFlag::LuauUseOrderedTypeSetsInConstraints) { - DenseHashSet seen{nullptr}; - for (auto ty : maybeMutated->second) + if (const auto maybeMutated = maybeMutatedFreeTypes.find(c); maybeMutated != maybeMutatedFreeTypes.end()) { - // There is a high chance that this type has been rebound - // across blocked types, rebound free types, pending - // expansion types, etc, so we need to follow it. - ty = follow(ty); - - if (FFlag::LuauEagerGeneralization4) + DenseHashSet seen{nullptr}; + for (auto ty : maybeMutated->second) { - if (seen.contains(ty)) - continue; - seen.insert(ty); + // There is a high chance that this type has been rebound + // across blocked types, rebound free types, pending + // expansion types, etc, so we need to follow it. + ty = follow(ty); + + if (FFlag::LuauEagerGeneralization4) + { + if (seen.contains(ty)) + continue; + seen.insert(ty); + } + + size_t& refCount = unresolvedConstraints[ty]; + if (refCount > 0) + refCount -= 1; + + // We have two constraints that are designed to wait for the + // refCount on a free type to be equal to 1: the + // PrimitiveTypeConstraint and ReduceConstraint. We + // therefore wake any constraint waiting for a free type's + // refcount to be 1 or 0. + if (refCount <= 1) + unblock(ty, Location{}); + + if (FFlag::LuauEagerGeneralization4 && refCount == 0) + generalizeOneType(ty); } - - size_t& refCount = unresolvedConstraints[ty]; - if (refCount > 0) - refCount -= 1; - - // We have two constraints that are designed to wait for the - // refCount on a free type to be equal to 1: the - // PrimitiveTypeConstraint and ReduceConstraint. We - // therefore wake any constraint waiting for a free type's - // refcount to be 1 or 0. - if (refCount <= 1) - unblock(ty, Location{}); - - if (FFlag::LuauEagerGeneralization4 && refCount == 0) - generalizeOneType(ty); } } + else + { + const auto maybeMutated = maybeMutatedFreeTypes_DEPRECATED.find(c); + if (maybeMutated != maybeMutatedFreeTypes_DEPRECATED.end()) + { + DenseHashSet seen{nullptr}; + for (auto ty : maybeMutated->second) + { + // There is a high chance that this type has been rebound + // across blocked types, rebound free types, pending + // expansion types, etc, so we need to follow it. + ty = follow(ty); + + if (FFlag::LuauEagerGeneralization4) + { + if (seen.contains(ty)) + continue; + seen.insert(ty); + } + + size_t& refCount = unresolvedConstraints[ty]; + if (refCount > 0) + refCount -= 1; + + // We have two constraints that are designed to wait for the + // refCount on a free type to be equal to 1: the + // PrimitiveTypeConstraint and ReduceConstraint. We + // therefore wake any constraint waiting for a free type's + // refcount to be 1 or 0. + if (refCount <= 1) + unblock(ty, Location{}); + + if (FFlag::LuauEagerGeneralization4 && refCount == 0) + generalizeOneType(ty); + } + } + } + if (logger) { @@ -664,27 +716,57 @@ struct TypeSearcher : TypeVisitor void ConstraintSolver::initFreeTypeTracking() { - for (NotNull c : this->constraints) + if (FFlag::LuauUseOrderedTypeSetsInConstraints) { - unsolvedConstraints.emplace_back(c); - - auto maybeMutatedTypesPerConstraint = c->getMaybeMutatedFreeTypes(); - for (auto ty : maybeMutatedTypesPerConstraint) + for (auto c : this->constraints) { - auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0); - refCount += 1; + unsolvedConstraints.emplace_back(c); - if (FFlag::LuauEagerGeneralization4) + auto maybeMutatedTypesPerConstraint = c->getMaybeMutatedFreeTypes(); + for (auto ty : maybeMutatedTypesPerConstraint) { - auto [it, fresh] = mutatedFreeTypeToConstraint.try_emplace(ty, nullptr); - it->second.insert(c.get()); + auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0); + refCount += 1; + + if (FFlag::LuauEagerGeneralization4) + { + auto [it, fresh] = mutatedFreeTypeToConstraint.try_emplace(ty); + it->second.insert(c.get()); + } + } + maybeMutatedFreeTypes.emplace(c, maybeMutatedTypesPerConstraint); + + for (NotNull dep : c->dependencies) + { + block(dep, c); } } - maybeMutatedFreeTypes.emplace(c, maybeMutatedTypesPerConstraint); + } + else + { - for (NotNull dep : c->dependencies) + for (NotNull c : this->constraints) { - block(dep, c); + unsolvedConstraints.emplace_back(c); + + auto maybeMutatedTypesPerConstraint = c->getMaybeMutatedFreeTypes_DEPRECATED(); + for (auto ty : maybeMutatedTypesPerConstraint) + { + auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0); + refCount += 1; + + if (FFlag::LuauEagerGeneralization4) + { + auto [it, fresh] = mutatedFreeTypeToConstraint_DEPRECATED.try_emplace(ty, nullptr); + it->second.insert(c.get()); + } + } + maybeMutatedFreeTypes_DEPRECATED.emplace(c, maybeMutatedTypesPerConstraint); + + for (NotNull dep : c->dependencies) + { + block(dep, c); + } } } } @@ -1538,51 +1620,6 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull(asMutable(c.result), result); - - if (FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2) - { - FunctionType* inferredFuncTy = getMutable(inferredTy); - LUAU_ASSERT(inferredFuncTy); - - // Strip variadic anys from the argTypes of any functionType arguments - const auto [argsHead, argsTail] = flatten(inferredFuncTy->argTypes); - TypePack clippedArgs = {{}, argsTail}; - bool clippedAny = false; - - for (TypeId t : argsHead) - { - const FunctionType* f = get(follow(t)); - if (!f || !f->argTypes) - { - clippedArgs.head.push_back(t); - continue; - } - - const TypePack* argTp = get(follow(f->argTypes)); - if (!argTp || !argTp->tail) - { - clippedArgs.head.push_back(t); - continue; - } - - if (const VariadicTypePack* argTpTail = get(follow(argTp->tail)); - argTpTail && argTpTail->hidden && argTpTail->ty == builtinTypes->anyType) - { - const TypePackId anyLessArgTp = arena->addTypePack(TypePack{argTp->head}); - // Mint a new FunctionType in case the original came from another module - const TypeId newFuncTypeId = arena->addType(FunctionType{anyLessArgTp, f->retTypes}); - FunctionType* newFunc = getMutable(newFuncTypeId); - newFunc->argNames = f->argNames; - clippedArgs.head.push_back(newFuncTypeId); - clippedAny = true; - } - else - clippedArgs.head.push_back(t); - } - - if (clippedAny) - inferredFuncTy->argTypes = arena->addTypePack(std::move(clippedArgs)); - } } for (const auto& [expanded, additions] : u2.expandedFreeTypes) @@ -1922,7 +1959,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNullargTypes) + if (!lambdaTy || !lambdaTy->argTypes) continue; const TypePack* argTp = get(follow(lambdaTy->argTypes)); @@ -3630,7 +3667,11 @@ bool ConstraintSolver::isBlocked(TypeId ty) const ty = follow(ty); if (auto tfit = get(ty)) + { + if (FFlag::LuauStuckTypeFunctionsStillDispatch && tfit->state != TypeFunctionInstanceState::Unsolved) + return false; return uninhabitedTypeFunctions.contains(ty) == false; + } return nullptr != get(ty) || nullptr != get(ty); } @@ -3739,21 +3780,42 @@ void ConstraintSolver::shiftReferences(TypeId source, TypeId target) if (FFlag::LuauEagerGeneralization4) { - auto it = mutatedFreeTypeToConstraint.find(source); - if (it != mutatedFreeTypeToConstraint.end()) + if (FFlag::LuauUseOrderedTypeSetsInConstraints) { - const DenseHashSet& constraintsAffectedBySource = it->second; - auto [it2, fresh2] = mutatedFreeTypeToConstraint.try_emplace(target, DenseHashSet{nullptr}); - DenseHashSet& constraintsAffectedByTarget = it2->second; - - // auto [it2, fresh] = mutatedFreeTypeToConstraint.try_emplace(target, DenseHashSet{nullptr}); - for (const Constraint* constraint : constraintsAffectedBySource) + if (auto it = mutatedFreeTypeToConstraint.find(source); it != mutatedFreeTypeToConstraint.end()) { - constraintsAffectedByTarget.insert(constraint); + const OrderedSet& constraintsAffectedBySource = it->second; + auto [it2, fresh2] = mutatedFreeTypeToConstraint.try_emplace(target); - auto [it3, fresh3] = maybeMutatedFreeTypes.try_emplace(NotNull{constraint}, DenseHashSet{nullptr}); - it3->second.insert(target); + OrderedSet& constraintsAffectedByTarget = it2->second; + + for (const Constraint* constraint : constraintsAffectedBySource) + { + constraintsAffectedByTarget.insert(constraint); + auto [it3, fresh3] = maybeMutatedFreeTypes.try_emplace(NotNull{constraint}, TypeIds{}); + it3->second.insert(target); + } + } + } + else + { + auto it = mutatedFreeTypeToConstraint_DEPRECATED.find(source); + if (it != mutatedFreeTypeToConstraint_DEPRECATED.end()) + { + const DenseHashSet& constraintsAffectedBySource = it->second; + + auto [it2, fresh2] = mutatedFreeTypeToConstraint_DEPRECATED.try_emplace(target, DenseHashSet{nullptr}); + DenseHashSet& constraintsAffectedByTarget = it2->second; + + // auto [it2, fresh] = mutatedFreeTypeToConstraint.try_emplace(target, DenseHashSet{nullptr}); + for (const Constraint* constraint : constraintsAffectedBySource) + { + constraintsAffectedByTarget.insert(constraint); + + auto [it3, fresh3] = maybeMutatedFreeTypes_DEPRECATED.try_emplace(NotNull{constraint}, DenseHashSet{nullptr}); + it3->second.insert(target); + } } } } diff --git a/Analysis/src/Error.cpp b/Analysis/src/Error.cpp index ae3184b3..146ddca2 100644 --- a/Analysis/src/Error.cpp +++ b/Analysis/src/Error.cpp @@ -22,6 +22,7 @@ LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps) LUAU_FASTFLAGVARIABLE(LuauBetterCannotCallFunctionPrimitive) +LUAU_FASTFLAG(LuauSolverAgnosticStringification) static std::string wrongNumberOfArgsString( size_t expectedCount, @@ -420,10 +421,17 @@ struct ErrorConverter auto it = mtt->props.find("__call"); if (it != mtt->props.end()) { - if (FFlag::LuauSolverV2 && FFlag::LuauRemoveTypeCallsForReadWriteProps) + if (FFlag::LuauSolverAgnosticStringification) + { return it->second.readTy; + } else - return it->second.type_DEPRECATED(); + { + if (FFlag::LuauSolverV2 && FFlag::LuauRemoveTypeCallsForReadWriteProps) + return it->second.readTy; + else + return it->second.type_DEPRECATED(); + } } else return std::nullopt; @@ -877,6 +885,11 @@ struct ErrorConverter return "Different number of generic type pack parameters: subtype had " + std::to_string(e.subTyGenericPackCount) + ", supertype had " + std::to_string(e.superTyGenericPackCount) + "."; } + + std::string operator()(const MultipleNonviableOverloads& e) const + { + return "None of the overloads for function that accept " + std::to_string(e.attemptedArgCount) + " arguments are compatible."; + } }; struct InvalidNameChecker @@ -1275,6 +1288,11 @@ bool GenericTypePackCountMismatch::operator==(const GenericTypePackCountMismatch return subTyGenericPackCount == rhs.subTyGenericPackCount && superTyGenericPackCount == rhs.superTyGenericPackCount; } +bool MultipleNonviableOverloads::operator==(const MultipleNonviableOverloads& rhs) const +{ + return attemptedArgCount == rhs.attemptedArgCount; +} + std::string toString(const TypeError& error) { return toString(error, TypeErrorToStringOptions{}); @@ -1495,6 +1513,9 @@ void copyError(T& e, TypeArena& destArena, CloneState& cloneState) else if constexpr (std::is_same_v) { } + 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 20655996..8a07f040 100644 --- a/Analysis/src/FragmentAutocomplete.cpp +++ b/Analysis/src/FragmentAutocomplete.cpp @@ -11,6 +11,7 @@ #include "Luau/Parser.h" #include "Luau/ParseOptions.h" #include "Luau/Module.h" +#include "Luau/RequireTracer.h" #include "Luau/TimeTrace.h" #include "Luau/UnifierSharedState.h" #include "Luau/TypeFunction.h" @@ -37,12 +38,15 @@ LUAU_FASTFLAGVARIABLE(LuauGlobalVariableModuleIsolation) LUAU_FASTFLAGVARIABLE(LuauFragmentAutocompleteIfRecommendations) LUAU_FASTFLAG(LuauExpectedTypeVisitor) LUAU_FASTFLAGVARIABLE(LuauPopulateRefinedTypesInFragmentFromOldSolver) +LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver) +LUAU_FASTFLAGVARIABLE(LuauFragmentRequiresCanBeResolvedToAModule) namespace Luau { static std::pair getDocumentOffsets(std::string_view src, const Position& startPos, const Position& endPos); + // when typing a function partially, get the span of the first line // e.g. local function fn() : ... - typically we want to provide autocomplete results if you're // editing type annotations in this range @@ -761,7 +765,7 @@ void cloneTypesFromFragment( destScope->returnType = Luau::cloneIncremental(staleScope->returnType, *destArena, cloneState, destScope); } -static FrontendModuleResolver& getModuleResolver(Frontend& frontend, std::optional options) +static FrontendModuleResolver& getModuleResolver_DEPRECATED(Frontend& frontend, std::optional options) { if (FFlag::LuauSolverV2 || !options) return frontend.moduleResolver; @@ -769,6 +773,14 @@ static FrontendModuleResolver& getModuleResolver(Frontend& frontend, std::option return options->forAutocomplete ? frontend.moduleResolverForAutocomplete : frontend.moduleResolver; } +static FrontendModuleResolver& getModuleResolver(Frontend& frontend, std::optional options) +{ + if ((frontend.getLuauSolverMode() == SolverMode::New) || !options) + return frontend.moduleResolver; + + return options->forAutocomplete ? frontend.moduleResolverForAutocomplete : frontend.moduleResolver; +} + bool statIsBeforePos(const AstNode* stat, const Position& cursorPos) { return (stat->location.begin < cursorPos); @@ -1066,6 +1078,42 @@ static void reportFragmentString(IFragmentAutocompleteReporter* reporter, std::s reporter->reportFragmentString(fragment); } +struct ScopedExit +{ +public: + explicit ScopedExit(std::function f) + : func(std::move(f)) + { + LUAU_ASSERT(func); + } + + ScopedExit(const ScopedExit&) = delete; + ScopedExit& operator=(const ScopedExit&) = delete; + ScopedExit() = default; + ScopedExit(ScopedExit&& other) noexcept + : ScopedExit() + { + std::swap(func, other.func); + } + + ScopedExit& operator=(ScopedExit&& other) noexcept + { + ScopedExit temp(std::move(other)); + std::swap(func, temp.func); + return *this; + } + + ~ScopedExit() + { + if (func) + func(); + } + +private: + std::function func; +}; + + FragmentTypeCheckResult typecheckFragment_( Frontend& frontend, AstStatBlock* root, @@ -1078,7 +1126,6 @@ FragmentTypeCheckResult typecheckFragment_( ) { LUAU_TIMETRACE_SCOPE("Luau::typecheckFragment_", "FragmentAutocomplete"); - freeze(stale->internalTypes); freeze(stale->interfaceTypes); ModulePtr incrementalModule = std::make_shared(); @@ -1107,7 +1154,7 @@ FragmentTypeCheckResult typecheckFragment_( unifierState.counters.iterationLimit = limits.unifierIterationLimit.value_or(FInt::LuauTypeInferIterationLimit); /// Initialize the normalizer - Normalizer normalizer{&incrementalModule->internalTypes, frontend.builtinTypes, NotNull{&unifierState}}; + Normalizer normalizer{&incrementalModule->internalTypes, frontend.builtinTypes, NotNull{&unifierState}, SolverMode::New}; /// User defined type functions runtime TypeFunctionRuntime typeFunctionRuntime(iceHandler, NotNull{&limits}); @@ -1120,6 +1167,17 @@ FragmentTypeCheckResult typecheckFragment_( SimplifierPtr simplifier = newSimplifier(NotNull{&incrementalModule->internalTypes}, frontend.builtinTypes); + // IncrementalModule gets moved at the end of the function, so capturing it here will cause SIGSEGV. + // We'll capture just the name instead, since that's all we need to clean up the requireTrace at the end + ScopedExit scopedExit{[&, name = incrementalModule->name]() + { + frontend.requireTrace.erase(name); + }}; + + if (FFlag::LuauFragmentRequiresCanBeResolvedToAModule) + frontend.requireTrace[incrementalModule->name] = traceRequires(frontend.fileResolver, root, incrementalModule->name); + + FrontendModuleResolver& resolver = getModuleResolver(frontend, opts); std::shared_ptr freshChildOfNearestScope = std::make_shared(nullptr); /// Contraint Generator @@ -1218,7 +1276,164 @@ FragmentTypeCheckResult typecheckFragment_( // In frontend we would forbid internal types // because this is just for autocomplete, we don't actually care // We also don't even need to typecheck - just synthesize types as best as we can + freeze(incrementalModule->internalTypes); + freeze(incrementalModule->interfaceTypes); + freshChildOfNearestScope->parent = closestScope; + return {std::move(incrementalModule), std::move(freshChildOfNearestScope)}; +} +FragmentTypeCheckResult typecheckFragment__DEPRECATED( + Frontend& frontend, + AstStatBlock* root, + const ModulePtr& stale, + const ScopePtr& closestScope, + const Position& cursorPos, + std::unique_ptr astAllocator, + const FrontendOptions& opts, + IFragmentAutocompleteReporter* reporter +) +{ + LUAU_TIMETRACE_SCOPE("Luau::typecheckFragment_", "FragmentAutocomplete"); + freeze(stale->internalTypes); + freeze(stale->interfaceTypes); + ModulePtr incrementalModule = std::make_shared(); + incrementalModule->name = stale->name; + incrementalModule->humanReadableName = "Incremental$" + stale->humanReadableName; + incrementalModule->internalTypes.owningModule = incrementalModule.get(); + incrementalModule->interfaceTypes.owningModule = incrementalModule.get(); + incrementalModule->allocator = std::move(astAllocator); + incrementalModule->checkedInNewSolver = true; + unfreeze(incrementalModule->internalTypes); + unfreeze(incrementalModule->interfaceTypes); + + /// Setup typecheck limits + TypeCheckLimits limits; + if (opts.moduleTimeLimitSec) + limits.finishTime = TimeTrace::getClock() + *opts.moduleTimeLimitSec; + else + limits.finishTime = std::nullopt; + limits.cancellationToken = opts.cancellationToken; + + /// Icehandler + NotNull iceHandler{&frontend.iceHandler}; + /// Make the shared state for the unifier (recursion + iteration limits) + UnifierSharedState unifierState{iceHandler}; + unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit; + unifierState.counters.iterationLimit = limits.unifierIterationLimit.value_or(FInt::LuauTypeInferIterationLimit); + + /// Initialize the normalizer + Normalizer normalizer{&incrementalModule->internalTypes, frontend.builtinTypes, NotNull{&unifierState}, SolverMode::New}; + + /// User defined type functions runtime + TypeFunctionRuntime typeFunctionRuntime(iceHandler, NotNull{&limits}); + + typeFunctionRuntime.allowEvaluation = false; + + /// Create a DataFlowGraph just for the surrounding context + DataFlowGraph dfg = DataFlowGraphBuilder::build(root, NotNull{&incrementalModule->defArena}, NotNull{&incrementalModule->keyArena}, iceHandler); + reportWaypoint(reporter, FragmentAutocompleteWaypoint::DfgBuildEnd); + + SimplifierPtr simplifier = newSimplifier(NotNull{&incrementalModule->internalTypes}, frontend.builtinTypes); + + FrontendModuleResolver& resolver = + FFlag::LuauUseWorkspacePropToChooseSolver ? getModuleResolver(frontend, opts) : getModuleResolver_DEPRECATED(frontend, opts); + std::shared_ptr freshChildOfNearestScope = std::make_shared(nullptr); + /// Contraint Generator + ConstraintGenerator cg{ + incrementalModule, + NotNull{&normalizer}, + NotNull{simplifier.get()}, + NotNull{&typeFunctionRuntime}, + NotNull{&resolver}, + frontend.builtinTypes, + iceHandler, + FFlag::LuauGlobalVariableModuleIsolation ? freshChildOfNearestScope : stale->getModuleScope(), + frontend.globals.globalTypeFunctionScope, + nullptr, + nullptr, + NotNull{&dfg}, + {} + }; + + CloneState cloneState{frontend.builtinTypes}; + incrementalModule->scopes.emplace_back(root->location, freshChildOfNearestScope); + freshChildOfNearestScope->interiorFreeTypes.emplace(); + freshChildOfNearestScope->interiorFreeTypePacks.emplace(); + cg.rootScope = freshChildOfNearestScope.get(); + + // Create module-local scope for the type function environment + ScopePtr localTypeFunctionScope = std::make_shared(cg.typeFunctionScope); + localTypeFunctionScope->location = root->location; + cg.typeFunctionRuntime->rootScope = localTypeFunctionScope; + + reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneAndSquashScopeStart); + cloneTypesFromFragment( + cloneState, + closestScope.get(), + stale, + NotNull{&incrementalModule->internalTypes}, + NotNull{&dfg}, + frontend.builtinTypes, + root, + freshChildOfNearestScope.get() + ); + reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneAndSquashScopeEnd); + + cg.visitFragmentRoot(freshChildOfNearestScope, root); + + for (auto p : cg.scopes) + incrementalModule->scopes.emplace_back(std::move(p)); + + + reportWaypoint(reporter, FragmentAutocompleteWaypoint::ConstraintSolverStart); + + /// Initialize the constraint solver and run it + ConstraintSolver cs{ + NotNull{&normalizer}, + NotNull{simplifier.get()}, + NotNull{&typeFunctionRuntime}, + NotNull(cg.rootScope), + borrowConstraints(cg.constraints), + NotNull{&cg.scopeToFunction}, + incrementalModule->name, + NotNull{&resolver}, + {}, + nullptr, + NotNull{&dfg}, + std::move(limits) + }; + + try + { + cs.run(); + } + catch (const TimeLimitError&) + { + stale->timeout = true; + } + catch (const UserCancelError&) + { + stale->cancelled = true; + } + + reportWaypoint(reporter, FragmentAutocompleteWaypoint::ConstraintSolverEnd); + + if (FFlag::LuauExpectedTypeVisitor) + { + ExpectedTypeVisitor etv{ + NotNull{&incrementalModule->astTypes}, + NotNull{&incrementalModule->astExpectedTypes}, + NotNull{&incrementalModule->astResolvedTypes}, + NotNull{&incrementalModule->internalTypes}, + frontend.builtinTypes, + NotNull{freshChildOfNearestScope.get()} + }; + root->visit(&etv); + } + + // In frontend we would forbid internal types + // because this is just for autocomplete, we don't actually care + // We also don't even need to typecheck - just synthesize types as best as we can freeze(incrementalModule->internalTypes); freeze(incrementalModule->interfaceTypes); freshChildOfNearestScope->parent = closestScope; @@ -1243,7 +1458,8 @@ std::pair typecheckFragment( if (!frontend.allModuleDependenciesValid(moduleName, opts && opts->forAutocomplete)) return {FragmentTypeCheckStatus::SkipAutocomplete, {}}; - FrontendModuleResolver& resolver = getModuleResolver(frontend, opts); + FrontendModuleResolver& resolver = + FFlag::LuauUseWorkspacePropToChooseSolver ? getModuleResolver(frontend, opts) : getModuleResolver_DEPRECATED(frontend, opts); ModulePtr module = resolver.getModule(moduleName); if (!module) { @@ -1268,7 +1484,11 @@ std::pair typecheckFragment( const ScopePtr& closestScope = FFlag::LuauBetterScopeSelection ? findClosestScope(module, parseResult.scopePos) : findClosestScope_DEPRECATED(module, parseResult.nearestStatement); FragmentTypeCheckResult result = - typecheckFragment_(frontend, parseResult.root, module, closestScope, cursorPos, std::move(parseResult.alloc), frontendOptions, reporter); + FFlag::LuauFragmentRequiresCanBeResolvedToAModule + ? typecheckFragment_(frontend, parseResult.root, module, closestScope, cursorPos, std::move(parseResult.alloc), frontendOptions, reporter) + : typecheckFragment__DEPRECATED( + frontend, parseResult.root, module, closestScope, cursorPos, std::move(parseResult.alloc), frontendOptions, reporter + ); result.ancestry = std::move(parseResult.ancestry); reportFragmentString(reporter, tryParse->fragmentToParse); return {FragmentTypeCheckStatus::Success, result}; diff --git a/Analysis/src/Frontend.cpp b/Analysis/src/Frontend.cpp index a521a062..3adde176 100644 --- a/Analysis/src/Frontend.cpp +++ b/Analysis/src/Frontend.cpp @@ -388,30 +388,31 @@ double getTimestamp() } // namespace Frontend::Frontend(FileResolver* fileResolver, ConfigResolver* configResolver, const FrontendOptions& options) - : builtinTypes(NotNull{&builtinTypes_}) + : useNewLuauSolver(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old) + , builtinTypes(NotNull{&builtinTypes_}) , fileResolver(fileResolver) , moduleResolver(this) , moduleResolverForAutocomplete(this) - , globals(builtinTypes) - , globalsForAutocomplete(builtinTypes) + , globals(builtinTypes, getLuauSolverMode()) + , globalsForAutocomplete(builtinTypes, getLuauSolverMode()) , configResolver(configResolver) , options(options) { } -void Frontend::setLuauSolverSelectionFromWorkspace(bool newSolverEnabled) +void Frontend::setLuauSolverSelectionFromWorkspace(SolverMode mode) { - useNewLuauSolver.store(newSolverEnabled); + useNewLuauSolver.store(mode); } -bool Frontend::getLuauSolverSelection() const +SolverMode Frontend::getLuauSolverMode() const { - return useNewLuauSolver.load(); -} - -bool Frontend::getLuauSolverSelectionFlagged() const -{ - return FFlag::LuauUseWorkspacePropToChooseSolver ? getLuauSolverSelection() : FFlag::LuauSolverV2; + if (FFlag::LuauUseWorkspacePropToChooseSolver) + return useNewLuauSolver.load(); + else if (FFlag::LuauSolverV2) + return SolverMode::New; + else + return SolverMode::Old; } void Frontend::parse(const ModuleName& name) @@ -464,7 +465,7 @@ CheckResult Frontend::check(const ModuleName& name, std::optional result = getCheckResult(name, true, frontendOptions.forAutocomplete)) @@ -524,7 +525,7 @@ std::vector Frontend::checkQueuedModules( ) { FrontendOptions frontendOptions = optionOverride.value_or(options); - if (FFlag::LuauSolverV2) + if (getLuauSolverMode() == SolverMode::New) frontendOptions.forAutocomplete = false; // By taking data into locals, we make sure queue is cleared at the end, even if an ICE or a different exception is thrown @@ -709,7 +710,7 @@ std::vector Frontend::checkQueuedModules( std::optional Frontend::getCheckResult(const ModuleName& name, bool accumulateNested, bool forAutocomplete) { - if (FFlag::LuauSolverV2) + if (getLuauSolverMode() == SolverMode::New) forAutocomplete = false; auto it = sourceNodes.find(name); @@ -1039,7 +1040,7 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item) if (item.options.customModuleCheck) item.options.customModuleCheck(sourceModule, *module); - if (FFlag::LuauSolverV2 && mode == Mode::NoCheck) + if ((getLuauSolverMode() == SolverMode::New) && mode == Mode::NoCheck) module->errors.clear(); if (item.options.runLintChecks) @@ -1461,7 +1462,7 @@ ModulePtr check( unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit; unifierState.counters.iterationLimit = limits.unifierIterationLimit.value_or(FInt::LuauTypeInferIterationLimit); - Normalizer normalizer{&result->internalTypes, builtinTypes, NotNull{&unifierState}}; + Normalizer normalizer{&result->internalTypes, builtinTypes, NotNull{&unifierState}, SolverMode::New}; SimplifierPtr simplifier = newSimplifier(NotNull{&result->internalTypes}, builtinTypes); TypeFunctionRuntime typeFunctionRuntime{iceHandler, NotNull{&limits}}; @@ -1728,7 +1729,7 @@ ModulePtr Frontend::check( TypeCheckLimits typeCheckLimits ) { - if (FFlag::LuauSolverV2) + if (getLuauSolverMode() == SolverMode::New) { auto prepareModuleScopeWrap = [this, forAutocomplete](const ModuleName& name, const ScopePtr& scope) { @@ -2057,4 +2058,10 @@ void Frontend::clear() requireTrace.clear(); } +void Frontend::clearBuiltinEnvironments() +{ + environments.clear(); + builtinDefinitions.clear(); +} + } // namespace Luau diff --git a/Analysis/src/Generalization.cpp b/Analysis/src/Generalization.cpp index a2f6632b..ebf96c61 100644 --- a/Analysis/src/Generalization.cpp +++ b/Analysis/src/Generalization.cpp @@ -5,6 +5,7 @@ #include "Luau/Common.h" #include "Luau/DenseHash.h" #include "Luau/InsertionOrderedMap.h" +#include "Luau/OrderedSet.h" #include "Luau/Polarity.h" #include "Luau/Scope.h" #include "Luau/ToString.h" @@ -23,68 +24,6 @@ LUAU_FASTFLAGVARIABLE(LuauGeneralizationCannotMutateAcrossModules) namespace Luau { -namespace -{ - -template -struct OrderedSet -{ - using iterator = typename std::vector::iterator; - using const_iterator = typename std::vector::const_iterator; - - bool empty() const - { - return elements.empty(); - } - - size_t size() const - { - return elements.size(); - } - - void insert(T t) - { - if (!elementSet.contains(t)) - { - elementSet.insert(t); - elements.push_back(t); - } - } - - iterator begin() - { - return elements.begin(); - } - - const_iterator begin() const - { - return elements.begin(); - } - - iterator end() - { - return elements.end(); - } - - const_iterator end() const - { - return elements.end(); - } - - /// Move the underlying vector out of the OrderedSet. - std::vector takeVector() - { - elementSet.clear(); - return std::move(elements); - } - -private: - std::vector elements; - DenseHashSet elementSet{nullptr}; -}; - -} // namespace - struct MutatingGeneralizer : TypeOnceVisitor { NotNull arena; diff --git a/Analysis/src/GlobalTypes.cpp b/Analysis/src/GlobalTypes.cpp index 04a22b20..8b205f4c 100644 --- a/Analysis/src/GlobalTypes.cpp +++ b/Analysis/src/GlobalTypes.cpp @@ -5,8 +5,9 @@ namespace Luau { -GlobalTypes::GlobalTypes(NotNull builtinTypes) +GlobalTypes::GlobalTypes(NotNull builtinTypes, SolverMode mode) : builtinTypes(builtinTypes) + , mode(mode) { globalScope = std::make_shared(globalTypes.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}})); globalTypeFunctionScope = std::make_shared(globalTypes.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}})); @@ -22,7 +23,7 @@ GlobalTypes::GlobalTypes(NotNull builtinTypes) globalScope->addBuiltinTypeBinding("never", TypeFun{{}, builtinTypes->neverType}); unfreeze(*builtinTypes->arena); - TypeId stringMetatableTy = makeStringMetatable(builtinTypes); + TypeId stringMetatableTy = makeStringMetatable(builtinTypes, mode); asMutable(builtinTypes->stringType)->ty.emplace(PrimitiveType::String, stringMetatableTy); persist(stringMetatableTy); freeze(*builtinTypes->arena); diff --git a/Analysis/src/IostreamHelpers.cpp b/Analysis/src/IostreamHelpers.cpp index d8bdbb8f..a1183d52 100644 --- a/Analysis/src/IostreamHelpers.cpp +++ b/Analysis/src/IostreamHelpers.cpp @@ -265,6 +265,8 @@ static void errorToString(std::ostream& stream, const T& err) stream << "GenericTypePackCountMismatch { subTyGenericPackCount = " << err.subTyGenericPackCount << ", superTyGenericPackCount = " << err.superTyGenericPackCount << " }"; } + else if constexpr (std::is_same_v) + stream << "MultipleNonviableOverloads { attemptedArgCount = " << err.attemptedArgCount << " }"; else static_assert(always_false_v, "Non-exhaustive type switch"); } diff --git a/Analysis/src/NonStrictTypeChecker.cpp b/Analysis/src/NonStrictTypeChecker.cpp index cc8f8aac..9de1dc3c 100644 --- a/Analysis/src/NonStrictTypeChecker.cpp +++ b/Analysis/src/NonStrictTypeChecker.cpp @@ -192,7 +192,7 @@ struct NonStrictTypeChecker , ice(ice) , arena(arena) , module(module) - , normalizer{arena, builtinTypes, unifierState, /* cache inhabitance */ true} + , normalizer{arena, builtinTypes, unifierState, SolverMode::New, /* cache inhabitance */ true} , subtyping{builtinTypes, arena, simplifier, NotNull(&normalizer), typeFunctionRuntime, ice} , dfg(dfg) , limits(limits) diff --git a/Analysis/src/Normalize.cpp b/Analysis/src/Normalize.cpp index ab5d55b6..d4ae66eb 100644 --- a/Analysis/src/Normalize.cpp +++ b/Analysis/src/Normalize.cpp @@ -24,6 +24,7 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAGVARIABLE(LuauNormalizationIntersectTablesPreservesExternTypes) LUAU_FASTFLAGVARIABLE(LuauNormalizationReorderFreeTypeIntersect) LUAU_FASTFLAG(LuauRefineTablesWithReadType) +LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver) namespace Luau { @@ -392,7 +393,7 @@ NormalizationResult Normalizer::isInhabited(TypeId ty, Set& seen) { for (const auto& [_, prop] : ttv->props) { - if (FFlag::LuauSolverV2) + if (useNewLuauSolver()) { // A table enclosing a read property whose type is uninhabitable is also itself uninhabitable, // but not its write property. That just means the write property doesn't exist, and so is readonly. @@ -719,11 +720,18 @@ static void assertInvariant(const NormalizedType& norm) #endif } -Normalizer::Normalizer(TypeArena* arena, NotNull builtinTypes, NotNull sharedState, bool cacheInhabitance) +Normalizer::Normalizer( + TypeArena* arena, + NotNull builtinTypes, + NotNull sharedState, + SolverMode solverMode, + bool cacheInhabitance +) : arena(arena) , builtinTypes(builtinTypes) , sharedState(sharedState) , cacheInhabitance(cacheInhabitance) + , solverMode(solverMode) { } @@ -1583,6 +1591,11 @@ bool Normalizer::withinResourceLimits() return true; } +bool Normalizer::useNewLuauSolver() const +{ + return FFlag::LuauUseWorkspacePropToChooseSolver ? (solverMode == SolverMode::New) : FFlag::LuauSolverV2; +} + NormalizationResult Normalizer::intersectNormalWithNegationTy(TypeId toNegate, NormalizedType& intersect) { @@ -2457,7 +2470,7 @@ std::optional Normalizer::intersectionOfTables(TypeId here, TypeId there { const auto& [_name, tprop] = *tfound; // TODO: variance issues here, which can't be fixed until we have read/write property types - if (FFlag::LuauSolverV2) + if (useNewLuauSolver()) { if (hprop.readTy.has_value()) { @@ -3041,7 +3054,7 @@ NormalizationResult Normalizer::intersectNormalWithTy( } else if (get(there) || get(there)) { - if (FFlag::LuauSolverV2 && FFlag::LuauNormalizationIntersectTablesPreservesExternTypes) + if (useNewLuauSolver() && FFlag::LuauNormalizationIntersectTablesPreservesExternTypes) { NormalizedExternType externTypes = std::move(here.externTypes); TypeIds tables = std::move(here.tables); @@ -3323,7 +3336,7 @@ TypeId Normalizer::typeFromNormal(const NormalizedType& norm) if (!get(norm.buffers)) result.push_back(builtinTypes->bufferType); - if (FFlag::LuauSolverV2) + if (useNewLuauSolver()) { result.reserve(result.size() + norm.tables.size()); for (auto table : norm.tables) @@ -3361,30 +3374,50 @@ bool isSubtype( NotNull scope, NotNull builtinTypes, NotNull simplifier, - InternalErrorReporter& ice + InternalErrorReporter& ice, + SolverMode solverMode ) { UnifierSharedState sharedState{&ice}; TypeArena arena; - Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}}; TypeCheckLimits limits; TypeFunctionRuntime typeFunctionRuntime{ NotNull{&ice}, NotNull{&limits} }; // TODO: maybe subtyping checks should not invoke user-defined type function runtime - // Subtyping under DCR is not implemented using unification! - if (FFlag::LuauSolverV2) + if (FFlag::LuauUseWorkspacePropToChooseSolver) { - Subtyping subtyping{builtinTypes, NotNull{&arena}, simplifier, NotNull{&normalizer}, NotNull{&typeFunctionRuntime}, NotNull{&ice}}; + Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}, solverMode}; + if (solverMode == SolverMode::New) + { + Subtyping subtyping{builtinTypes, NotNull{&arena}, simplifier, NotNull{&normalizer}, NotNull{&typeFunctionRuntime}, NotNull{&ice}}; - return subtyping.isSubtype(subTy, superTy, scope).isSubtype; + return subtyping.isSubtype(subTy, superTy, scope).isSubtype; + } + else + { + Unifier u{NotNull{&normalizer}, scope, Location{}, Covariant}; + + u.tryUnify(subTy, superTy); + return !u.failure; + } } else { - Unifier u{NotNull{&normalizer}, scope, Location{}, Covariant}; + Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}, FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old}; + if (FFlag::LuauSolverV2) + { + Subtyping subtyping{builtinTypes, NotNull{&arena}, simplifier, NotNull{&normalizer}, NotNull{&typeFunctionRuntime}, NotNull{&ice}}; - u.tryUnify(subTy, superTy); - return !u.failure; + return subtyping.isSubtype(subTy, superTy, scope).isSubtype; + } + else + { + Unifier u{NotNull{&normalizer}, scope, Location{}, Covariant}; + + u.tryUnify(subTy, superTy); + return !u.failure; + } } } @@ -3394,30 +3427,50 @@ bool isSubtype( NotNull scope, NotNull builtinTypes, NotNull simplifier, - InternalErrorReporter& ice + InternalErrorReporter& ice, + SolverMode solverMode ) { UnifierSharedState sharedState{&ice}; TypeArena arena; - Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}}; TypeCheckLimits limits; TypeFunctionRuntime typeFunctionRuntime{ NotNull{&ice}, NotNull{&limits} }; // TODO: maybe subtyping checks should not invoke user-defined type function runtime - // Subtyping under DCR is not implemented using unification! - if (FFlag::LuauSolverV2) + if (FFlag::LuauUseWorkspacePropToChooseSolver) { - Subtyping subtyping{builtinTypes, NotNull{&arena}, simplifier, NotNull{&normalizer}, NotNull{&typeFunctionRuntime}, NotNull{&ice}}; + Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}, solverMode}; + if (solverMode == SolverMode::New) + { + Subtyping subtyping{builtinTypes, NotNull{&arena}, simplifier, NotNull{&normalizer}, NotNull{&typeFunctionRuntime}, NotNull{&ice}}; - return subtyping.isSubtype(subPack, superPack, scope).isSubtype; + return subtyping.isSubtype(subPack, superPack, scope).isSubtype; + } + else + { + Unifier u{NotNull{&normalizer}, scope, Location{}, Covariant}; + + u.tryUnify(subPack, superPack); + return !u.failure; + } } else { - Unifier u{NotNull{&normalizer}, scope, Location{}, Covariant}; + Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}, FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old}; + if (FFlag::LuauSolverV2) + { + Subtyping subtyping{builtinTypes, NotNull{&arena}, simplifier, NotNull{&normalizer}, NotNull{&typeFunctionRuntime}, NotNull{&ice}}; - u.tryUnify(subPack, superPack); - return !u.failure; + return subtyping.isSubtype(subPack, superPack, scope).isSubtype; + } + else + { + Unifier u{NotNull{&normalizer}, scope, Location{}, Covariant}; + + u.tryUnify(subPack, superPack); + return !u.failure; + } } } diff --git a/Analysis/src/OverloadResolution.cpp b/Analysis/src/OverloadResolution.cpp index cc925a62..d4609691 100644 --- a/Analysis/src/OverloadResolution.cpp +++ b/Analysis/src/OverloadResolution.cpp @@ -11,7 +11,7 @@ #include "Luau/Unifier2.h" LUAU_FASTFLAGVARIABLE(LuauArityMismatchOnUndersaturatedUnknownArguments) -LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2) +LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping) namespace Luau { @@ -201,6 +201,43 @@ bool OverloadResolver::isLiteral(AstExpr* expr) expr->is() || expr->is() || expr->is(); } +void OverloadResolver::maybeEmplaceError( + ErrorVec* errors, + Location argLocation, + const SubtypingReasoning* reason, + const std::optional failedSubTy, + const std::optional failedSuperTy +) const +{ + if (failedSubTy && failedSuperTy) + { + switch (shouldSuppressErrors(normalizer, *failedSubTy).orElse(shouldSuppressErrors(normalizer, *failedSuperTy))) + { + case ErrorSuppression::Suppress: + break; + case ErrorSuppression::NormalizationFailed: + errors->emplace_back(argLocation, NormalizationTooComplex{}); + // intentionally fallthrough here since we couldn't prove this was error-suppressing + [[fallthrough]]; + case ErrorSuppression::DoNotSuppress: + // TODO extract location from the SubtypingResult path and argExprs + switch (reason->variance) + { + case SubtypingVariance::Covariant: + case SubtypingVariance::Contravariant: + errors->emplace_back(argLocation, TypeMismatch{*failedSubTy, *failedSuperTy, TypeMismatch::CovariantContext}); + break; + case SubtypingVariance::Invariant: + errors->emplace_back(argLocation, TypeMismatch{*failedSubTy, *failedSuperTy, TypeMismatch::InvariantContext}); + break; + default: + LUAU_ASSERT(0); + break; + } + } + } +} + std::pair OverloadResolver::checkOverload_( TypeId fnTy, const FunctionType* fn, @@ -292,14 +329,44 @@ std::pair OverloadResolver::checkOverload_ return {Analysis::Ok, {}}; } - if (FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2) + if (FFlag::LuauReturnMappedGenericPacksFromSubtyping) { - if (reason.subPath == TypePath::Path{{TypePath::PackField::Arguments, TypePath::PackField::Tail}} && reason.superPath == justArguments) + // If we have an arity mismatch with generic type pack parameters, then subPath matches Args :: Tail :: ... + // and superPath matches Args :: ... + if (reason.subPath.components.size() >= 2 && reason.subPath.components[0] == TypePath::PackField::Arguments && + reason.subPath.components[1] == TypePath::PackField::Tail && reason.superPath.components.size() >= 1 && + reason.superPath.components[0] == TypePath::PackField::Arguments) + { + if (const auto [requiredHead, requiredTail] = flatten(fn->argTypes); requiredTail) + { + if (const auto genericTail = get(follow(requiredTail)); genericTail) + { + // Get the concrete type pack the generic is mapped to + const auto mappedGenHead = flatten(*requiredTail, sr.mappedGenericPacks).first; + + const auto prospectiveHead = flatten(typ).first; + + // We're just doing arity checking here + // We've flattened the type packs, so we can check prospectiveHead = requiredHead + mappedGenHead + // Super path reasoning is just args, so we can ignore the tails + const size_t neededHeadSize = requiredHead.size() + mappedGenHead.size(); + const size_t prospectiveHeadSize = prospectiveHead.size(); + if (prospectiveHeadSize != neededHeadSize) + { + TypeError error{fnExpr->location, CountMismatch{neededHeadSize, std::nullopt, prospectiveHeadSize, CountMismatch::Arg}}; + + return {Analysis::ArityMismatch, {error}}; + } + } + } + } + + else if (reason.subPath == TypePath::Path{{TypePath::PackField::Arguments, TypePath::PackField::Tail}} && + reason.superPath == justArguments) { // We have an arity mismatch if the argument tail is a generic type pack if (auto fnArgs = get(fn->argTypes)) { - // TODO: Determine whether arguments have incorrect type, incorrect count, or both (CLI-152070) if (get(fnArgs->tail)) { auto [minParams, optMaxParams] = getParameterExtents(TxnLog::empty(), fn->argTypes); @@ -337,12 +404,18 @@ std::pair OverloadResolver::checkOverload_ : argExprs->size() != 0 ? argExprs->back()->location : fnExpr->location; - std::optional failedSubTy = traverseForType(fnTy, reason.subPath, builtinTypes); - std::optional failedSuperTy = traverseForType(prospectiveFunction, reason.superPath, builtinTypes); + std::optional failedSubTy = FFlag::LuauReturnMappedGenericPacksFromSubtyping + ? traverseForType(fnTy, reason.subPath, builtinTypes, NotNull{&sr.mappedGenericPacks}, arena) + : traverseForType_DEPRECATED(fnTy, reason.subPath, builtinTypes); + std::optional failedSuperTy = + FFlag::LuauReturnMappedGenericPacksFromSubtyping + ? traverseForType(prospectiveFunction, reason.superPath, builtinTypes, NotNull{&sr.mappedGenericPacks}, arena) + : traverseForType_DEPRECATED(prospectiveFunction, reason.superPath, builtinTypes); - if (failedSubTy && failedSuperTy) + if (FFlag::LuauReturnMappedGenericPacksFromSubtyping) + maybeEmplaceError(&errors, argLocation, &reason, failedSubTy, failedSuperTy); + else if (failedSubTy && failedSuperTy) { - switch (shouldSuppressErrors(normalizer, *failedSubTy).orElse(shouldSuppressErrors(normalizer, *failedSuperTy))) { case ErrorSuppression::Suppress: @@ -369,9 +442,36 @@ std::pair OverloadResolver::checkOverload_ } } } + else if (FFlag::LuauReturnMappedGenericPacksFromSubtyping && reason.superPath.components.size() > 1) + { + // traverseForIndex only has a value if path is of form [...PackSlice, Index] + if (const auto index = + traverseForIndex(TypePath::Path{std::vector(reason.superPath.components.begin() + 1, reason.superPath.components.end())})) + { + if (index < argExprs->size()) + argLocation = argExprs->at(*index)->location; + else if (argExprs->size() != 0) + argLocation = argExprs->back()->location; + else + { + // this should never happen + LUAU_ASSERT(false); + argLocation = fnExpr->location; + } + std::optional failedSubTy = traverseForType(fnTy, reason.subPath, builtinTypes, NotNull{&sr.mappedGenericPacks}, arena); + std::optional failedSuperTy = + traverseForType(prospectiveFunction, reason.superPath, builtinTypes, NotNull{&sr.mappedGenericPacks}, arena); + maybeEmplaceError(&errors, argLocation, &reason, failedSubTy, failedSuperTy); + } + } - std::optional failedSubPack = traverseForPack(fnTy, reason.subPath, builtinTypes); - std::optional failedSuperPack = traverseForPack(prospectiveFunction, reason.superPath, builtinTypes); + std::optional failedSubPack = FFlag::LuauReturnMappedGenericPacksFromSubtyping + ? traverseForPack(fnTy, reason.subPath, builtinTypes, NotNull{&sr.mappedGenericPacks}, arena) + : traverseForPack_DEPRECATED(fnTy, reason.subPath, builtinTypes); + std::optional failedSuperPack = + FFlag::LuauReturnMappedGenericPacksFromSubtyping + ? traverseForPack(prospectiveFunction, reason.superPath, builtinTypes, NotNull{&sr.mappedGenericPacks}, arena) + : traverseForPack_DEPRECATED(prospectiveFunction, reason.superPath, builtinTypes); if (failedSubPack && failedSuperPack) { diff --git a/Analysis/src/Simplify.cpp b/Analysis/src/Simplify.cpp index cb52dc1c..5621bf0a 100644 --- a/Analysis/src/Simplify.cpp +++ b/Analysis/src/Simplify.cpp @@ -22,6 +22,7 @@ LUAU_FASTFLAGVARIABLE(LuauSimplificationTableExternType) LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps) LUAU_FASTFLAGVARIABLE(LuauRelateTablesAreNeverDisjoint) LUAU_FASTFLAG(LuauRefineTablesWithReadType) +LUAU_FASTFLAGVARIABLE(LuauMissingSeenSetRelate) namespace Luau { @@ -597,7 +598,12 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen) if (auto propInExternType = re->props.find(name); propInExternType != re->props.end()) { Relation propRel; - if (FFlag::LuauRemoveTypeCallsForReadWriteProps) + if (FFlag::LuauMissingSeenSetRelate) + { + LUAU_ASSERT(prop.readTy && propInExternType->second.readTy); + propRel = relate(*prop.readTy, *propInExternType->second.readTy, seen); + } + else if (FFlag::LuauRemoveTypeCallsForReadWriteProps) { LUAU_ASSERT(prop.readTy && propInExternType->second.readTy); propRel = relate(*prop.readTy, *propInExternType->second.readTy); diff --git a/Analysis/src/Subtyping.cpp b/Analysis/src/Subtyping.cpp index fe586e76..68c207f6 100644 --- a/Analysis/src/Subtyping.cpp +++ b/Analysis/src/Subtyping.cpp @@ -18,10 +18,10 @@ LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity) LUAU_FASTINTVARIABLE(LuauSubtypingReasoningLimit, 100) -LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2) LUAU_FASTFLAGVARIABLE(LuauSubtypingCheckFunctionGenericCounts) LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps) +LUAU_FASTFLAGVARIABLE(LuauReturnMappedGenericPacksFromSubtyping) namespace Luau { @@ -63,20 +63,52 @@ size_t SubtypingReasoningHash::operator()(const SubtypingReasoning& r) const } template -static void assertReasoningValid(TID subTy, TID superTy, const SubtypingResult& result, NotNull builtinTypes) +static void assertReasoningValid_DEPRECATED(TID subTy, TID superTy, const SubtypingResult& result, NotNull builtinTypes) { if (!FFlag::DebugLuauSubtypingCheckPathValidity) return; for (const SubtypingReasoning& reasoning : result.reasoning) { - LUAU_ASSERT(traverse(subTy, reasoning.subPath, builtinTypes)); - LUAU_ASSERT(traverse(superTy, reasoning.superPath, builtinTypes)); + LUAU_ASSERT(traverse_DEPRECATED(subTy, reasoning.subPath, builtinTypes)); + LUAU_ASSERT(traverse_DEPRECATED(superTy, reasoning.superPath, builtinTypes)); + } +} + +template +static void assertReasoningValid(TID subTy, TID superTy, const SubtypingResult& result, NotNull builtinTypes, NotNull arena) +{ + LUAU_ASSERT(FFlag::LuauReturnMappedGenericPacksFromSubtyping); + + if (!FFlag::DebugLuauSubtypingCheckPathValidity) + return; + + for (const SubtypingReasoning& reasoning : result.reasoning) + { + LUAU_ASSERT(traverse(subTy, reasoning.subPath, builtinTypes, NotNull{&result.mappedGenericPacks}, arena)); + LUAU_ASSERT(traverse(superTy, reasoning.superPath, builtinTypes, NotNull{&result.mappedGenericPacks}, arena)); } } template<> -void assertReasoningValid(TableIndexer subIdx, TableIndexer superIdx, const SubtypingResult& result, NotNull builtinTypes) +void assertReasoningValid_DEPRECATED( + TableIndexer subIdx, + TableIndexer superIdx, + const SubtypingResult& result, + NotNull builtinTypes +) +{ + // Empty method to satisfy the compiler. +} + +template<> +void assertReasoningValid( + TableIndexer subIdx, + TableIndexer superIdx, + const SubtypingResult& result, + NotNull builtinTypes, + NotNull arena +) { // Empty method to satisfy the compiler. } @@ -481,6 +513,11 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy, NotNull scope) { SubtypingEnvironment env; - return isCovariantWith(env, subTp, superTp, scope); + + SubtypingResult result = isCovariantWith(env, subTp, superTp, scope); + + if (FFlag::LuauReturnMappedGenericPacksFromSubtyping) + { + if (!env.mappedGenericPacks.empty()) + result.mappedGenericPacks = std::move(env.mappedGenericPacks); + } + + return result; } SubtypingResult Subtyping::cache(SubtypingEnvironment& env, SubtypingResult result, TypeId subTy, TypeId superTy) { const std::pair p{subTy, superTy}; + + if (FFlag::LuauReturnMappedGenericPacksFromSubtyping && !env.mappedGenericPacks.empty()) + result.mappedGenericPacks = env.mappedGenericPacks; + if (result.isCacheable) resultCache[p] = result; else @@ -547,11 +597,27 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub const SubtypingResult* cachedResult = resultCache.find({subTy, superTy}); if (cachedResult) + { + if (FFlag::LuauReturnMappedGenericPacksFromSubtyping) + { + for (const auto& [genericTp, boundTp] : cachedResult->mappedGenericPacks) + env.mappedGenericPacks.try_insert(genericTp, boundTp); + } + return *cachedResult; + } cachedResult = env.tryFindSubtypingResult({subTy, superTy}); if (cachedResult) + { + if (FFlag::LuauReturnMappedGenericPacksFromSubtyping) + { + for (const auto& [genericTp, boundTp] : cachedResult->mappedGenericPacks) + env.mappedGenericPacks.try_insert(genericTp, boundTp); + } + return *cachedResult; + } // TODO: Do we care about returning a proof that this is error-suppressing? // e.g. given `a | error <: a | error` where both operands are pointer equal, @@ -791,7 +857,10 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub else if (auto p = get2(subTy, superTy)) result = isCovariantWith(env, p, scope); - assertReasoningValid(subTy, superTy, result, builtinTypes); + if (FFlag::LuauReturnMappedGenericPacksFromSubtyping) + assertReasoningValid(subTy, superTy, result, builtinTypes, arena); + else + assertReasoningValid_DEPRECATED(subTy, superTy, result, builtinTypes); return cache(env, std::move(result), subTy, superTy); } @@ -840,14 +909,31 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId // (X) -> () <: (T) -> () // Possible optimization: If headSize == 0 then we can just use subTp as-is. - std::vector headSlice = FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2 + std::vector headSlice = FFlag::LuauReturnMappedGenericPacksFromSubtyping ? std::vector(begin(superHead) + headSize, end(superHead)) : std::vector(begin(superHead), begin(superHead) + headSize); TypePackId superTailPack = arena->addTypePack(std::move(headSlice), superTail); if (TypePackId* other = env.getMappedPackBounds(*subTail)) - // TODO: TypePath can't express "slice of a pack + its tail". - results.push_back(isCovariantWith(env, *other, superTailPack, scope).withSubComponent(TypePath::PackField::Tail)); + { + if (FFlag::LuauReturnMappedGenericPacksFromSubtyping) + { + const TypePack* tp = get(*other); + if (const VariadicTypePack* vtp = tp ? get(tp->tail) : nullptr; vtp && vtp->hidden) + { + TypePackId taillessTp = arena->addTypePack(tp->head); + results.push_back(isCovariantWith(env, taillessTp, superTailPack, scope) + .withSubComponent(TypePath::PackField::Tail) + .withSuperComponent(TypePath::PackSlice{headSize})); + } + else + results.push_back(isCovariantWith(env, *other, superTailPack, scope) + .withSubComponent(TypePath::PackField::Tail) + .withSuperComponent(TypePath::PackSlice{headSize})); + } + else + results.push_back(isCovariantWith(env, *other, superTailPack, scope).withSubComponent(TypePath::PackField::Tail)); + } else env.mappedGenericPacks.try_insert(*subTail, superTailPack); @@ -897,17 +983,31 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId // (X...) -> () <: (T) -> () // Possible optimization: If headSize == 0 then we can just use subTp as-is. - std::vector headSlice = FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2 + std::vector headSlice = FFlag::LuauReturnMappedGenericPacksFromSubtyping ? std::vector(begin(subHead) + headSize, end(subHead)) : std::vector(begin(subHead), begin(subHead) + headSize); TypePackId subTailPack = arena->addTypePack(std::move(headSlice), subTail); if (TypePackId* other = env.getMappedPackBounds(*superTail)) - // TODO: TypePath can't express "slice of a pack + its tail". - if (FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2) - results.push_back(isCovariantWith(env, subTailPack, *other, scope).withSuperComponent(TypePath::PackField::Tail)); + { + if (FFlag::LuauReturnMappedGenericPacksFromSubtyping) + { + const TypePack* tp = get(*other); + if (const VariadicTypePack* vtp = tp ? get(tp->tail) : nullptr; vtp && vtp->hidden) + { + TypePackId taillessTp = arena->addTypePack(tp->head); + results.push_back(isCovariantWith(env, subTailPack, taillessTp, scope) + .withSubComponent(TypePath::PackSlice{headSize}) + .withSuperComponent(TypePath::PackField::Tail)); + } + else + results.push_back(isCovariantWith(env, subTailPack, *other, scope) + .withSubComponent(TypePath::PackSlice{headSize}) + .withSuperComponent(TypePath::PackField::Tail)); + } else results.push_back(isContravariantWith(env, subTailPack, *other, scope).withSuperComponent(TypePath::PackField::Tail)); + } else env.mappedGenericPacks.try_insert(*superTail, subTailPack); @@ -1041,7 +1141,11 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId } SubtypingResult result = SubtypingResult::all(results); - assertReasoningValid(subTp, superTp, result, builtinTypes); + + if (FFlag::LuauReturnMappedGenericPacksFromSubtyping) + assertReasoningValid(subTp, superTp, result, builtinTypes, arena); + else + assertReasoningValid_DEPRECATED(subTp, superTp, result, builtinTypes); return result; } @@ -1073,7 +1177,10 @@ SubtypingResult Subtyping::isContravariantWith(SubtypingEnvironment& env, SubTy& } } - assertReasoningValid(subTy, superTy, result, builtinTypes); + if (FFlag::LuauReturnMappedGenericPacksFromSubtyping) + assertReasoningValid(subTy, superTy, result, builtinTypes, arena); + else + assertReasoningValid_DEPRECATED(subTy, superTy, result, builtinTypes); return result; } @@ -1091,7 +1198,11 @@ SubtypingResult Subtyping::isInvariantWith(SubtypingEnvironment& env, SubTy&& su reasoning.variance = SubtypingVariance::Invariant; } - assertReasoningValid(subTy, superTy, result, builtinTypes); + if (FFlag::LuauReturnMappedGenericPacksFromSubtyping) + assertReasoningValid(subTy, superTy, result, builtinTypes, arena); + else + assertReasoningValid_DEPRECATED(subTy, superTy, result, builtinTypes); + return result; } diff --git a/Analysis/src/ToString.cpp b/Analysis/src/ToString.cpp index 1b14fb07..34e50bf0 100644 --- a/Analysis/src/ToString.cpp +++ b/Analysis/src/ToString.cpp @@ -23,6 +23,7 @@ LUAU_FASTFLAGVARIABLE(LuauEnableDenseTableAlias) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps) +LUAU_FASTFLAGVARIABLE(LuauSolverAgnosticStringification) /* * Enables increasing levels of verbosity for Luau type names when stringifying. @@ -86,8 +87,14 @@ struct FindCyclicTypes final : TypeVisitor { if (!visited.insert(ty)) return false; - - if (FFlag::LuauSolverV2) + if (FFlag::LuauSolverAgnosticStringification) + { + LUAU_ASSERT(ft.lowerBound); + LUAU_ASSERT(ft.upperBound); + traverse(ft.lowerBound); + traverse(ft.upperBound); + } + else if (FFlag::LuauSolverV2) { // TODO: Replace these if statements with assert()s when we // delete FFlag::LuauSolverV2. @@ -440,7 +447,7 @@ struct TypeStringifier void stringify(const std::string& name, const Property& prop) { - if (FFlag::LuauSolverV2) + if (FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticStringification) return _newStringify(name, prop); emitKey(name); @@ -503,9 +510,44 @@ struct TypeStringifier { state.result.invalid = true; - // TODO: ftv.lowerBound and ftv.upperBound should always be non-nil when - // the new solver is used. This can be replaced with an assert. - if (FFlag::LuauSolverV2 && ftv.lowerBound && ftv.upperBound) + // Free types are guaranteed to have upper and lower bounds now. + if (FFlag::LuauSolverAgnosticStringification) + { + LUAU_ASSERT(ftv.lowerBound); + LUAU_ASSERT(ftv.upperBound); + const TypeId lowerBound = follow(ftv.lowerBound); + const TypeId upperBound = follow(ftv.upperBound); + if (get(lowerBound) && get(upperBound)) + { + state.emit("'"); + state.emit(state.getName(ty)); + if (FInt::DebugLuauVerboseTypeNames >= 1) + state.emit(ftv.polarity); + } + else + { + state.emit("("); + if (!get(lowerBound)) + { + stringify(lowerBound); + state.emit(" <: "); + } + state.emit("'"); + state.emit(state.getName(ty)); + + if (FInt::DebugLuauVerboseTypeNames >= 1) + state.emit(ftv.polarity); + + if (!get(upperBound)) + { + state.emit(" <: "); + stringify(upperBound); + } + state.emit(")"); + } + return; + } + else if (FFlag::LuauSolverV2 && ftv.lowerBound && ftv.upperBound) { const TypeId lowerBound = follow(ftv.lowerBound); const TypeId upperBound = follow(ftv.upperBound); @@ -545,13 +587,16 @@ struct TypeStringifier state.emit(state.getName(ty)); - if (FFlag::LuauSolverV2 && FInt::DebugLuauVerboseTypeNames >= 1) + if (FFlag::LuauSolverAgnosticStringification && FInt::DebugLuauVerboseTypeNames >= 1) state.emit(ftv.polarity); + else if (FFlag::LuauSolverV2 && FInt::DebugLuauVerboseTypeNames >= 1) + state.emit(ftv.polarity); + if (FInt::DebugLuauVerboseTypeNames >= 2) { state.emit("-"); - if (FFlag::LuauSolverV2) + if (FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticStringification) state.emitLevel(ftv.scope); else state.emit(ftv.level); @@ -583,7 +628,7 @@ struct TypeStringifier if (FInt::DebugLuauVerboseTypeNames >= 2) { state.emit("-"); - if (FFlag::LuauSolverV2) + if (FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticStringification) state.emitLevel(gtv.scope); else state.emit(gtv.level); @@ -686,7 +731,7 @@ struct TypeStringifier state.emit(">"); } - if (FFlag::LuauSolverV2) + if (FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticStringification) { if (ftv.isCheckedFunction) state.emit("@checked "); @@ -783,10 +828,10 @@ struct TypeStringifier std::string openbrace = "@@@"; std::string closedbrace = "@@@?!"; - switch (state.opts.hideTableKind ? (FFlag::LuauSolverV2 ? TableState::Sealed : TableState::Unsealed) : ttv.state) + switch (state.opts.hideTableKind ? ((FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticStringification) ? TableState::Sealed : TableState::Unsealed) : ttv.state) { case TableState::Sealed: - if (FFlag::LuauSolverV2) + if (FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticStringification) { openbrace = "{"; closedbrace = "}"; @@ -799,7 +844,7 @@ struct TypeStringifier } break; case TableState::Unsealed: - if (FFlag::LuauSolverV2) + if (FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticStringification) { state.result.invalid = true; openbrace = "{|"; @@ -1314,7 +1359,7 @@ struct TypePackStringifier if (FInt::DebugLuauVerboseTypeNames >= 2) { state.emit("-"); - if (FFlag::LuauSolverV2) + if (FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticStringification) state.emitLevel(pack.scope); else state.emit(pack.level); @@ -1336,7 +1381,7 @@ struct TypePackStringifier if (FInt::DebugLuauVerboseTypeNames >= 2) { state.emit("-"); - if (FFlag::LuauSolverV2) + if (FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticStringification) state.emitLevel(pack.scope); else state.emit(pack.level); diff --git a/Analysis/src/Transpiler.cpp b/Analysis/src/Transpiler.cpp index 2ec5d920..813771c6 100644 --- a/Analysis/src/Transpiler.cpp +++ b/Analysis/src/Transpiler.cpp @@ -10,8 +10,6 @@ #include #include -LUAU_FASTFLAG(LuauStoreLocalAnnotationColonPositions) - namespace { bool isIdentifierStartChar(char c) @@ -322,8 +320,7 @@ struct Printer writer.identifier(local.name.value); if (writeTypes && local.annotation) { - if (FFlag::LuauStoreLocalAnnotationColonPositions) - advance(colonPosition); + advance(colonPosition); writer.symbol(":"); visualizeTypeAnnotation(*local.annotation); } @@ -928,7 +925,7 @@ struct Printer for (size_t i = 0; i < a->vars.size; i++) { varComma(); - if (FFlag::LuauStoreLocalAnnotationColonPositions && cstNode) + if (cstNode) { LUAU_ASSERT(cstNode->varsAnnotationColonPositions.size > i); visualize(*a->vars.data[i], cstNode->varsAnnotationColonPositions.data[i]); @@ -957,10 +954,7 @@ struct Printer writer.keyword("for"); - if (FFlag::LuauStoreLocalAnnotationColonPositions) - visualize(*a->var, cstNode ? cstNode->annotationColonPosition : Position{0, 0}); - else - visualize(*a->var, Position{0, 0}); + visualize(*a->var, cstNode ? cstNode->annotationColonPosition : Position{0, 0}); if (cstNode) advance(cstNode->equalsPosition); @@ -994,7 +988,7 @@ struct Printer for (size_t i = 0; i < a->vars.size; i++) { varComma(); - if (FFlag::LuauStoreLocalAnnotationColonPositions && cstNode) + if (cstNode) { LUAU_ASSERT(cstNode->varsAnnotationColonPositions.size > i); visualize(*a->vars.data[i], cstNode->varsAnnotationColonPositions.data[i]); @@ -1317,7 +1311,7 @@ struct Printer writer.identifier(local->name.value); if (writeTypes && local->annotation) { - if (FFlag::LuauStoreLocalAnnotationColonPositions && cstNode) + if (cstNode) { LUAU_ASSERT(cstNode->argsAnnotationColonPositions.size > i); advance(cstNode->argsAnnotationColonPositions.data[i]); @@ -1335,7 +1329,7 @@ struct Printer if (func.varargAnnotation) { - if (FFlag::LuauStoreLocalAnnotationColonPositions && cstNode) + if (cstNode) { LUAU_ASSERT(cstNode->varargAnnotationColonPosition != Position({0, 0})); advance(cstNode->varargAnnotationColonPosition); diff --git a/Analysis/src/Type.cpp b/Analysis/src/Type.cpp index 07ae7ab1..cf951fe7 100644 --- a/Analysis/src/Type.cpp +++ b/Analysis/src/Type.cpp @@ -31,6 +31,7 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauSubtypingCheckFunctionGenericCounts) LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps) +LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver) namespace Luau { @@ -711,7 +712,7 @@ Property Property::create(std::optional read, std::optional writ TypeId Property::type_DEPRECATED() const { - if (FFlag::LuauRemoveTypeCallsForReadWriteProps) + if (FFlag::LuauRemoveTypeCallsForReadWriteProps && !FFlag::LuauUseWorkspacePropToChooseSolver) LUAU_ASSERT(!FFlag::LuauSolverV2); LUAU_ASSERT(readTy); @@ -1001,7 +1002,7 @@ TypeId makeFunction( std::initializer_list retTypes ); -TypeId makeStringMetatable(NotNull builtinTypes); // BuiltinDefinitions.cpp +TypeId makeStringMetatable(NotNull builtinTypes, SolverMode mode); // BuiltinDefinitions.cpp BuiltinTypes::BuiltinTypes() : arena(new TypeArena) diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index 08093856..745fee3d 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -36,8 +36,11 @@ LUAU_FASTFLAG(LuauNewNonStrictFixGenericTypePacks) LUAU_FASTFLAGVARIABLE(LuauReportSubtypingErrors) LUAU_FASTFLAGVARIABLE(LuauSkipMalformedTypeAliases) LUAU_FASTFLAGVARIABLE(LuauTableLiteralSubtypeSpecificCheck2) +LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch) LUAU_FASTFLAG(LuauSubtypingCheckFunctionGenericCounts) LUAU_FASTFLAG(LuauTableLiteralSubtypeCheckFunctionCalls) +LUAU_FASTFLAGVARIABLE(LuauSuppressErrorsForMultipleNonviableOverloads) +LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping) namespace Luau { @@ -317,7 +320,7 @@ TypeChecker2::TypeChecker2( , ice(unifierState->iceHandler) , sourceModule(sourceModule) , module(module) - , normalizer{&module->internalTypes, builtinTypes, unifierState, /* cacheInhabitance */ true} + , normalizer{&module->internalTypes, builtinTypes, unifierState, SolverMode::New, /* cacheInhabitance */ true} , _subtyping{builtinTypes, NotNull{&module->internalTypes}, simplifier, NotNull{&normalizer}, typeFunctionRuntime, NotNull{unifierState->iceHandler}} , subtyping(&_subtyping) { @@ -1710,14 +1713,24 @@ void TypeChecker2::visitCall(AstExprCall* call) return; // Ok. Calling an uninhabited type is no-op. else if (!resolver.nonviableOverloads.empty()) { - if (resolver.nonviableOverloads.size() == 1 && !isErrorSuppressing(call->func->location, resolver.nonviableOverloads.front().first)) - reportErrors(resolver.nonviableOverloads.front().second); + if (FFlag::LuauSuppressErrorsForMultipleNonviableOverloads) + { + const bool reportedErrors = + reportNonviableOverloadErrors(resolver.nonviableOverloads, call->func->location, args.head.size(), call->location); + if (!reportedErrors) + return; // We did not report any errors, so we can just return. + } else { - std::string s = "None of the overloads for function that accept "; - s += std::to_string(args.head.size()); - s += " arguments are compatible."; - reportError(GenericError{std::move(s)}, call->location); + if (resolver.nonviableOverloads.size() == 1 && !isErrorSuppressing(call->func->location, resolver.nonviableOverloads.front().first)) + reportErrors(resolver.nonviableOverloads.front().second); + else + { + std::string s = "None of the overloads for function that accept "; + s += std::to_string(args.head.size()); + s += " arguments are compatible."; + reportError(GenericError{std::move(s)}, call->location); + } } } else if (!resolver.arityMismatches.empty()) @@ -2046,19 +2059,24 @@ void TypeChecker2::visit(AstExprFunction* fn) // If the function type has a function annotation, we need to see if we can suggest an annotation if (normalizedFnTy) { - const FunctionType* inferredFtv = get(normalizedFnTy->functions.parts.front()); - LUAU_ASSERT(inferredFtv); - - TypeFunctionReductionGuesser guesser{NotNull{&module->internalTypes}, builtinTypes, NotNull{&normalizer}}; - for (TypeId retTy : inferredFtv->retTypes) + if (FFlag::LuauStuckTypeFunctionsStillDispatch) + suggestAnnotations(fn, normalizedFnTy->functions.parts.front()); + else { - if (get(follow(retTy))) + const FunctionType* inferredFtv = get(normalizedFnTy->functions.parts.front()); + LUAU_ASSERT(inferredFtv); + + TypeFunctionReductionGuesser guesser{NotNull{&module->internalTypes}, builtinTypes, NotNull{&normalizer}}; + for (TypeId retTy : inferredFtv->retTypes) { - TypeFunctionReductionGuessResult result = guesser.guessTypeFunctionReductionForFunctionExpr(*fn, inferredFtv, retTy); - if (result.shouldRecommendAnnotation && !get(result.guessedReturnType)) - reportError( - ExplicitFunctionAnnotationRecommended{std::move(result.guessedFunctionAnnotations), result.guessedReturnType}, fn->location - ); + if (get(follow(retTy))) + { + TypeFunctionReductionGuessResult result = guesser.guessTypeFunctionReductionForFunctionExpr(*fn, inferredFtv, retTy); + if (result.shouldRecommendAnnotation && !get(result.guessedReturnType)) + reportError( + ExplicitFunctionAnnotationRecommended{std::move(result.guessedFunctionAnnotations), result.guessedReturnType}, fn->location + ); + } } } } @@ -2923,8 +2941,14 @@ Reasonings TypeChecker2::explainReasonings_(TID subTy, TID superTy, Location loc if (reasoning.subPath.empty() && reasoning.superPath.empty()) continue; - std::optional optSubLeaf = traverse(subTy, reasoning.subPath, builtinTypes); - std::optional optSuperLeaf = traverse(superTy, reasoning.superPath, builtinTypes); + std::optional optSubLeaf = + FFlag::LuauReturnMappedGenericPacksFromSubtyping + ? traverse(subTy, reasoning.subPath, builtinTypes, NotNull{&r.mappedGenericPacks}, subtyping->arena) + : traverse_DEPRECATED(subTy, reasoning.subPath, builtinTypes); + std::optional optSuperLeaf = + FFlag::LuauReturnMappedGenericPacksFromSubtyping + ? traverse(superTy, reasoning.superPath, builtinTypes, NotNull{&r.mappedGenericPacks}, subtyping->arena) + : traverse_DEPRECATED(superTy, reasoning.superPath, builtinTypes); if (!optSubLeaf || !optSuperLeaf) ice->ice("Subtyping test returned a reasoning with an invalid path", location); @@ -3473,7 +3497,7 @@ PropertyType TypeChecker2::hasIndexTypeFromType( { TypeId indexType = follow(tt->indexer->indexType); TypeId givenType = module->internalTypes.addType(SingletonType{StringSingleton{prop}}); - if (isSubtype(givenType, indexType, NotNull{module->getModuleScope().get()}, builtinTypes, simplifier, *ice)) + if (isSubtype(givenType, indexType, NotNull{module->getModuleScope().get()}, builtinTypes, simplifier, *ice, SolverMode::New)) return {NormalizationResult::True, {tt->indexer->indexResultType}}; } @@ -3550,7 +3574,47 @@ PropertyType TypeChecker2::hasIndexTypeFromType( return {NormalizationResult::False, {}}; } +void TypeChecker2::suggestAnnotations(AstExprFunction* expr, TypeId ty) +{ + const FunctionType* inferredFtv = get(ty); + LUAU_ASSERT(inferredFtv); + VecDeque workList; + DenseHashSet seen{nullptr}; + + TypeFunctionReductionGuesser guesser{NotNull{&module->internalTypes}, builtinTypes, NotNull{&normalizer}}; + for (TypeId retTy : inferredFtv->retTypes) + workList.push_back(retTy); + + while (!workList.empty()) + { + TypeId t = follow(workList.front()); + workList.pop_front(); + + if (seen.contains(t)) + continue; + seen.insert(t); + + if (auto ut = get(t)) + { + for (TypeId t : ut) + workList.push_back(t); + } + else if (auto it = get(t)) + { + for (TypeId t : it) + workList.push_back(t); + } + else if (get(t)) + { + TypeFunctionReductionGuessResult result = guesser.guessTypeFunctionReductionForFunctionExpr(*expr, inferredFtv, t); + if (result.shouldRecommendAnnotation && !get(result.guessedReturnType)) + reportError( + ExplicitFunctionAnnotationRecommended{std::move(result.guessedFunctionAnnotations), result.guessedReturnType}, expr->location + ); + } + } +} void TypeChecker2::diagnoseMissingTableKey(UnknownProperty* utk, TypeErrorData& data) const { @@ -3630,5 +3694,42 @@ bool TypeChecker2::isErrorSuppressing(Location loc1, TypePackId tp1, Location lo return isErrorSuppressing(loc1, tp1) || isErrorSuppressing(loc2, tp2); } +bool TypeChecker2::reportNonviableOverloadErrors( + std::vector> nonviableOverloads, + Location callFuncLocation, + size_t argHeadSize, + Location callLocation +) +{ + // If multiple overloads report errors, we want to return an error reporting that multiple overloads have errors. + // If only one overload has errors, we want to report those errors. + std::optional reportedErrors; + bool multipleOverloadsHaveErrors = false; + for (auto& [ty, errs] : nonviableOverloads) + { + if (!isErrorSuppressing(callFuncLocation, ty) && !errs.empty()) + { + if (reportedErrors) + { + multipleOverloadsHaveErrors = true; + break; + } + reportedErrors.emplace(errs); + } + } + if (multipleOverloadsHaveErrors) + { + reportError(MultipleNonviableOverloads{argHeadSize}, callLocation); + return true; + } + else if (reportedErrors) + { + reportErrors(std::move(*reportedErrors)); + return true; + } + + return false; +} + } // namespace Luau diff --git a/Analysis/src/TypeFunction.cpp b/Analysis/src/TypeFunction.cpp index f4646737..7bd03a09 100644 --- a/Analysis/src/TypeFunction.cpp +++ b/Analysis/src/TypeFunction.cpp @@ -57,6 +57,7 @@ LUAU_FASTFLAG(LuauUserTypeFunctionAliases) LUAU_FASTFLAG(LuauUpdateGetMetatableTypeSignature) LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps) LUAU_FASTFLAGVARIABLE(LuauOccursCheckForRefinement) +LUAU_FASTFLAGVARIABLE(LuauStuckTypeFunctionsStillDispatch) LUAU_FASTFLAG(LuauRefineTablesWithReadType) LUAU_FASTFLAGVARIABLE(LuauEmptyStringInKeyOf) @@ -257,6 +258,10 @@ struct TypeFunctionReducer /// reducible later. Irreducible, + /// A type function that cannot be reduced any further because it has no valid reduction. + /// eg add + Stuck, + /// Some type functions can operate on generic parameters Generic, @@ -313,8 +318,15 @@ struct TypeFunctionReducer if (seen.contains(t)) continue; - if (is(t)) + if (auto tfit = get(t)) { + if (FFlag::LuauStuckTypeFunctionsStillDispatch) + { + if (tfit->state == TypeFunctionInstanceState::Stuck) + return SkipTestResult::Stuck; + else if (tfit->state == TypeFunctionInstanceState::Solved) + return SkipTestResult::Generic; + } for (auto cyclicTy : cyclicTypeFunctions) { if (t == cyclicTy) @@ -382,6 +394,35 @@ struct TypeFunctionReducer result.reducedPacks.insert(subject); } + TypeFunctionInstanceState getState(TypeId ty) const + { + auto tfit = get(ty); + LUAU_ASSERT(tfit); + return tfit->state; + } + + void setState(TypeId ty, TypeFunctionInstanceState state) const + { + if (ty->owningArena != ctx.arena) + return; + + TypeFunctionInstanceType* tfit = getMutable(ty); + LUAU_ASSERT(tfit); + tfit->state = state; + } + + TypeFunctionInstanceState getState(TypePackId tp) const + { + return TypeFunctionInstanceState::Unsolved; + } + + void setState(TypePackId tp, TypeFunctionInstanceState state) const + { + // We do not presently have any type pack functions at all. + (void)tp; + (void)state; + } + template void handleTypeFunctionReduction(T subject, TypeFunctionReductionResult reduction) { @@ -402,6 +443,24 @@ struct TypeFunctionReducer if (FFlag::DebugLuauLogTypeFamilies) printf("%s is uninhabited\n", toString(subject, {true}).c_str()); + if (FFlag::LuauStuckTypeFunctionsStillDispatch) + { + if (getState(subject) == TypeFunctionInstanceState::Unsolved) + { + if (reduction.reductionStatus == Reduction::Erroneous) + setState(subject, TypeFunctionInstanceState::Stuck); + else if (reduction.reductionStatus == Reduction::Irreducible) + setState(subject, TypeFunctionInstanceState::Solved); + else if (reduction.reductionStatus == Reduction::MaybeOk) + { + // We cannot make progress because something is unsolved, but we're also forcing. + setState(subject, TypeFunctionInstanceState::Stuck); + } + else + ctx.ice->ice("Unexpected TypeFunctionInstanceState"); + } + } + if constexpr (std::is_same_v) result.errors.emplace_back(location, UninhabitedTypeFunction{subject}); else if constexpr (std::is_same_v) @@ -409,6 +468,9 @@ struct TypeFunctionReducer } else if (reduction.reductionStatus == Reduction::MaybeOk && !force) { + // We're not forcing and the reduction couldn't proceed, but it isn't obviously busted. + // Report that this type blocks further reduction. + if (FFlag::DebugLuauLogTypeFamilies) printf( "%s is irreducible; blocked on %zu types, %zu packs\n", @@ -423,6 +485,8 @@ struct TypeFunctionReducer for (TypePackId b : reduction.blockedPacks) result.blockedPacks.insert(b); } + else + LUAU_ASSERT(!"Unreachable"); } } @@ -438,12 +502,33 @@ struct TypeFunctionReducer { SkipTestResult skip = testForSkippability(p); + if (skip == SkipTestResult::Stuck) + { + // SkipTestResult::Stuck cannot happen when this flag is unset. + LUAU_ASSERT(FFlag::LuauStuckTypeFunctionsStillDispatch); + if (FFlag::DebugLuauLogTypeFamilies) + printf("%s is stuck!\n", toString(subject, {true}).c_str()); + + irreducible.insert(subject); + setState(subject, TypeFunctionInstanceState::Stuck); + + return false; + } 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()); + { + if (skip == SkipTestResult::Generic) + printf("%s is solved due to a dependency on %s\n", toString(subject, {true}).c_str(), toString(p, {true}).c_str()); + else + printf("%s is irreducible due to a dependency on %s\n", toString(subject, {true}).c_str(), toString(p, {true}).c_str()); + } irreducible.insert(subject); + + if (skip == SkipTestResult::Generic) + setState(subject, TypeFunctionInstanceState::Solved); + return false; } else if (skip == SkipTestResult::Defer) @@ -556,6 +641,9 @@ struct TypeFunctionReducer if (FFlag::DebugLuauLogTypeFamilies) printf("Irreducible due to irreducible/pending and a non-cyclic function\n"); + if (tfit->state == TypeFunctionInstanceState::Stuck || tfit->state == TypeFunctionInstanceState::Solved) + tryGuessing(subject); + return; } @@ -732,7 +820,14 @@ FunctionGraphReductionResult reduceTypeFunctions(TypePackId entrypoint, Location bool isPending(TypeId ty, ConstraintSolver* solver) { - return is(ty) || (solver && solver->hasUnresolvedConstraints(ty)); + if (FFlag::LuauStuckTypeFunctionsStillDispatch) + { + if (auto tfit = get(ty); tfit && tfit->state == TypeFunctionInstanceState::Unsolved) + return true; + return is(ty) || (solver && solver->hasUnresolvedConstraints(ty)); + } + else + return is(ty) || (solver && solver->hasUnresolvedConstraints(ty)); } template @@ -3268,7 +3363,7 @@ bool searchPropsAndIndexer( indexType = follow(tblIndexer->indexResultType); } - if (isSubtype(ty, indexType, ctx->scope, ctx->builtins, ctx->simplifier, *ctx->ice)) + if (isSubtype(ty, indexType, ctx->scope, ctx->builtins, ctx->simplifier, *ctx->ice, SolverMode::New)) { TypeId idxResultTy = follow(tblIndexer->indexResultType); diff --git a/Analysis/src/TypeInfer.cpp b/Analysis/src/TypeInfer.cpp index ba09720d..e283e5dd 100644 --- a/Analysis/src/TypeInfer.cpp +++ b/Analysis/src/TypeInfer.cpp @@ -209,7 +209,7 @@ TypeChecker::TypeChecker(const ScopePtr& globalScope, ModuleResolver* resolver, , builtinTypes(builtinTypes) , iceHandler(iceHandler) , unifierState(iceHandler) - , normalizer(nullptr, builtinTypes, NotNull{&unifierState}) + , normalizer(nullptr, builtinTypes, NotNull{&unifierState}, SolverMode::Old) , reusableInstantiation(TxnLog::empty(), nullptr, builtinTypes, {}, nullptr) , nilType(builtinTypes->nilType) , numberType(builtinTypes->numberType) diff --git a/Analysis/src/TypePack.cpp b/Analysis/src/TypePack.cpp index 6244343f..44d5433c 100644 --- a/Analysis/src/TypePack.cpp +++ b/Analysis/src/TypePack.cpp @@ -453,6 +453,33 @@ std::pair, std::optional> flatten(TypePackId tp, return {flattened, tail}; } +std::pair, std::optional> flatten(TypePackId tp, const DenseHashMap& mappedGenericPacks) +{ + tp = mappedGenericPacks.contains(tp) ? *mappedGenericPacks.find(tp) : tp; + + std::vector flattened; + std::optional tail = std::nullopt; + + while (tp) + { + TypePackIterator it(tp); + + for (; it != end(tp); ++it) + flattened.push_back(*it); + + if (const auto tpTail = it.tail(); tpTail && mappedGenericPacks.contains(*tpTail)) + { + tp = *mappedGenericPacks.find(*tpTail); + continue; + } + + tail = it.tail(); + break; + } + + return {flattened, tail}; +} + bool isVariadic(TypePackId tp) { return isVariadic(tp, *TxnLog::empty()); diff --git a/Analysis/src/TypePath.cpp b/Analysis/src/TypePath.cpp index bc268535..b2b1f407 100644 --- a/Analysis/src/TypePath.cpp +++ b/Analysis/src/TypePath.cpp @@ -1,9 +1,12 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/TypePath.h" + +#include "Luau/Anyification.h" #include "Luau/Common.h" #include "Luau/DenseHash.h" #include "Luau/Type.h" +#include "Luau/TypeArena.h" #include "Luau/TypeFwd.h" #include "Luau/TypePack.h" #include "Luau/TypeOrPack.h" @@ -11,9 +14,9 @@ #include #include #include -#include LUAU_FASTFLAG(LuauSolverV2); +LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping) // Maximum number of steps to follow when traversing a path. May not always // equate to the number of components in a path, depending on the traversal @@ -29,7 +32,6 @@ namespace TypePath Property::Property(std::string name) : name(std::move(name)) { - LUAU_ASSERT(!FFlag::LuauSolverV2); } Property Property::read(std::string name) @@ -52,6 +54,11 @@ bool Index::operator==(const Index& other) const return index == other.index; } +bool PackSlice::operator==(const PackSlice& other) const +{ + return start_index == other.start_index; +} + bool Reduction::operator==(const Reduction& other) const { return resultType == other.resultType; @@ -129,6 +136,11 @@ size_t PathHash::operator()(const PackField& field) const return static_cast(field); } +size_t PathHash::operator()(const PackSlice& slice) const +{ + return slice.start_index; +} + size_t PathHash::operator()(const Reduction& reduction) const { return std::hash()(reduction.resultType); @@ -168,7 +180,6 @@ PathBuilder& PathBuilder::writeProp(std::string name) PathBuilder& PathBuilder::prop(std::string name) { - LUAU_ASSERT(!FFlag::LuauSolverV2); components.push_back(Property{std::move(name)}); return *this; } @@ -239,6 +250,12 @@ PathBuilder& PathBuilder::tail() return *this; } +PathBuilder& PathBuilder::packSlice(size_t start_index) +{ + components.emplace_back(PackSlice{start_index}); + return *this; +} + } // namespace TypePath namespace @@ -246,19 +263,31 @@ namespace struct TraversalState { - TraversalState(TypeId root, NotNull builtinTypes) + TraversalState(TypeId root, NotNull builtinTypes, const DenseHashMap* mappedGenericPacks, TypeArena* arena) : current(root) , builtinTypes(builtinTypes) + , mappedGenericPacks(mappedGenericPacks) + , arena(arena) { } - TraversalState(TypePackId root, NotNull builtinTypes) + TraversalState( + TypePackId root, + NotNull builtinTypes, + const DenseHashMap* mappedGenericPacks, + TypeArena* arena + ) : current(root) , builtinTypes(builtinTypes) + , mappedGenericPacks(mappedGenericPacks) + , arena(arena) { } TypeOrPack current; NotNull builtinTypes; + // TODO: make these NotNull when LuauReturnMappedGenericPacksFromSubtyping is clipped + const DenseHashMap* mappedGenericPacks; + TypeArena* arena; int steps = 0; void updateCurrent(TypeId ty) @@ -388,17 +417,43 @@ struct TraversalState { auto currentPack = get(current); LUAU_ASSERT(currentPack); - if (get(*currentPack)) + if (FFlag::LuauReturnMappedGenericPacksFromSubtyping) { - auto it = begin(*currentPack); - - for (size_t i = 0; i < index.index && it != end(*currentPack); ++i) - ++it; - - if (it != end(*currentPack)) + if (const auto tp = get(*currentPack)) { - updateCurrent(*it); - return true; + auto it = begin(*currentPack); + + size_t i = 0; + for (; i < index.index && it != end(*currentPack); ++i) + ++it; + + if (it != end(*currentPack)) + { + updateCurrent(*it); + return true; + } + else if (tp->tail && mappedGenericPacks && mappedGenericPacks->contains(*tp->tail)) + { + updateCurrent(*mappedGenericPacks->find(*tp->tail)); + LUAU_ASSERT(index.index >= i); + return traverse(TypePath::Index{index.index - i, TypePath::Index::Variant::Pack}); + } + } + } + else + { + if (get(*currentPack)) + { + auto it = begin(*currentPack); + + for (size_t i = 0; i < index.index && it != end(*currentPack); ++i) + ++it; + + if (it != end(*currentPack)) + { + updateCurrent(*it); + return true; + } } } } @@ -521,7 +576,10 @@ struct TraversalState if (auto tail = it.tail()) { - updateCurrent(*tail); + if (FFlag::LuauReturnMappedGenericPacksFromSubtyping && mappedGenericPacks && mappedGenericPacks->contains(*tail)) + updateCurrent(*mappedGenericPacks->find(*tail)); + else + updateCurrent(*tail); return true; } } @@ -531,6 +589,47 @@ struct TraversalState return false; } + + bool traverse(const TypePath::PackSlice slice) + { + if (checkInvariants()) + return false; + + // TODO: clip this check once LuauReturnMappedGenericPacksFromSubtyping is clipped + // arena and mappedGenericPacks should be NonNull once that happens + if (FFlag::LuauReturnMappedGenericPacksFromSubtyping) + LUAU_ASSERT(arena && mappedGenericPacks); + else if (!arena || !mappedGenericPacks) + return false; + + const auto currentPack = get(current); + if (!currentPack) + return false; + + auto [flatHead, flatTail] = flatten(*currentPack, *mappedGenericPacks); + + if (flatHead.size() <= slice.start_index) + return false; + + std::vector headSlice; + headSlice.reserve(flatHead.size() - slice.start_index); + + auto headIter = begin(flatHead); + for (size_t i = 0; i < slice.start_index && headIter != end(flatHead); ++i) + ++headIter; + + while (headIter != end(flatHead)) + { + headSlice.push_back(*headIter); + ++headIter; + } + + TypePackId packSlice = arena->addTypePack(headSlice, flatTail); + + updateCurrent(packSlice); + + return true; + } }; } // namespace @@ -614,6 +713,8 @@ std::string toString(const TypePath::Path& path, bool prefixDot) } result << "()"; } + else if constexpr (std::is_same_v) + result << "[" << std::to_string(c.start_index) << ":]"; else if constexpr (std::is_same_v) { // We need to rework the TypePath system to make subtyping failures easier to understand @@ -829,6 +930,8 @@ std::string toStringHuman(const TypePath::Path& path) state = State::Normal; } } + else if constexpr (std::is_same_v) + result << "the portion of the type pack starting at index " << c.start_index << " to the end"; else if constexpr (std::is_same_v) { if (state == State::Initial) @@ -892,27 +995,57 @@ static bool traverse(TraversalState& state, const Path& path) return true; } -std::optional traverse(TypeId root, const Path& path, NotNull builtinTypes) +std::optional traverse_DEPRECATED(TypeId root, const Path& path, NotNull builtinTypes) { - TraversalState state(follow(root), builtinTypes); + TraversalState state(follow(root), builtinTypes, nullptr, nullptr); if (traverse(state, path)) return state.current; else return std::nullopt; } -std::optional traverse(TypePackId root, const Path& path, NotNull builtinTypes) +std::optional traverse_DEPRECATED(TypePackId root, const Path& path, NotNull builtinTypes) { - TraversalState state(follow(root), builtinTypes); + TraversalState state(follow(root), builtinTypes, nullptr, nullptr); if (traverse(state, path)) return state.current; else return std::nullopt; } -std::optional traverseForType(TypeId root, const Path& path, NotNull builtinTypes) +std::optional traverse( + TypeId root, + const Path& path, + NotNull builtinTypes, + NotNull> mappedGenericPacks, + NotNull arena +) { - TraversalState state(follow(root), builtinTypes); + TraversalState state(follow(root), builtinTypes, mappedGenericPacks, arena); + if (traverse(state, path)) + return state.current; + else + return std::nullopt; +} + +std::optional traverse( + TypePackId root, + const Path& path, + NotNull builtinTypes, + NotNull> mappedGenericPacks, + NotNull arena +) +{ + TraversalState state(follow(root), builtinTypes, mappedGenericPacks, arena); + if (traverse(state, path)) + return state.current; + else + return std::nullopt; +} + +std::optional traverseForType_DEPRECATED(TypeId root, const Path& path, NotNull builtinTypes) +{ + TraversalState state(follow(root), builtinTypes, nullptr, nullptr); if (traverse(state, path)) { auto ty = get(state.current); @@ -922,9 +1055,15 @@ std::optional traverseForType(TypeId root, const Path& path, NotNull traverseForType(TypePackId root, const Path& path, NotNull builtinTypes) +std::optional traverseForType( + TypeId root, + const Path& path, + NotNull builtinTypes, + NotNull> mappedGenericPacks, + NotNull arena +) { - TraversalState state(follow(root), builtinTypes); + TraversalState state(follow(root), builtinTypes, mappedGenericPacks, arena); if (traverse(state, path)) { auto ty = get(state.current); @@ -934,9 +1073,39 @@ std::optional traverseForType(TypePackId root, const Path& path, NotNull return std::nullopt; } -std::optional traverseForPack(TypeId root, const Path& path, NotNull builtinTypes) +std::optional traverseForType_DEPRECATED(TypePackId root, const Path& path, NotNull builtinTypes) { - TraversalState state(follow(root), builtinTypes); + TraversalState state(follow(root), builtinTypes, nullptr, nullptr); + if (traverse(state, path)) + { + auto ty = get(state.current); + return ty ? std::make_optional(*ty) : std::nullopt; + } + else + return std::nullopt; +} + +std::optional traverseForType( + TypePackId root, + const Path& path, + NotNull builtinTypes, + NotNull> mappedGenericPacks, + NotNull arena +) +{ + TraversalState state(follow(root), builtinTypes, mappedGenericPacks, arena); + if (traverse(state, path)) + { + auto ty = get(state.current); + return ty ? std::make_optional(*ty) : std::nullopt; + } + else + return std::nullopt; +} + +std::optional traverseForPack_DEPRECATED(TypeId root, const Path& path, NotNull builtinTypes) +{ + TraversalState state(follow(root), builtinTypes, nullptr, nullptr); if (traverse(state, path)) { auto ty = get(state.current); @@ -946,9 +1115,15 @@ std::optional traverseForPack(TypeId root, const Path& path, NotNull return std::nullopt; } -std::optional traverseForPack(TypePackId root, const Path& path, NotNull builtinTypes) +std::optional traverseForPack( + TypeId root, + const Path& path, + NotNull builtinTypes, + NotNull> mappedGenericPacks, + NotNull arena +) { - TraversalState state(follow(root), builtinTypes); + TraversalState state(follow(root), builtinTypes, mappedGenericPacks, arena); if (traverse(state, path)) { auto ty = get(state.current); @@ -958,4 +1133,61 @@ std::optional traverseForPack(TypePackId root, const Path& path, Not return std::nullopt; } +std::optional traverseForPack_DEPRECATED(TypePackId root, const Path& path, NotNull builtinTypes) +{ + TraversalState state(follow(root), builtinTypes, nullptr, nullptr); + if (traverse(state, path)) + { + auto ty = get(state.current); + return ty ? std::make_optional(*ty) : std::nullopt; + } + else + return std::nullopt; +} + +std::optional traverseForPack( + TypePackId root, + const Path& path, + NotNull builtinTypes, + NotNull> mappedGenericPacks, + NotNull arena +) +{ + TraversalState state(follow(root), builtinTypes, mappedGenericPacks, arena); + if (traverse(state, path)) + { + auto ty = get(state.current); + return ty ? std::make_optional(*ty) : std::nullopt; + } + else + return std::nullopt; +} + +std::optional traverseForIndex(const Path& path) +{ + auto componentIter = begin(path.components); + size_t index = 0; + const auto lastComponent = end(path.components) - 1; + + while (componentIter != lastComponent) + { + if (const auto packSlice = get_if(&*componentIter)) + { + index += packSlice->start_index; + } + else + { + return std::nullopt; + } + ++componentIter; + } + + if (const auto indexComponent = get_if(&*componentIter)) + { + index += indexComponent->index; + return index; + } + return std::nullopt; +} + } // namespace Luau diff --git a/Analysis/src/Unifier.cpp b/Analysis/src/Unifier.cpp index 096974ec..3708628e 100644 --- a/Analysis/src/Unifier.cpp +++ b/Analysis/src/Unifier.cpp @@ -381,9 +381,6 @@ Unifier::Unifier(NotNull normalizer, NotNull scope, const Loc , sharedState(*normalizer->sharedState) { LUAU_ASSERT(sharedState.iceHandler); - - // Unifier is not usable when this flag is enabled! Please consider using Subtyping instead. - LUAU_ASSERT(!FFlag::LuauSolverV2); } void Unifier::tryUnify(TypeId subTy, TypeId superTy, bool isFunctionCall, bool isIntersection, const LiteralProperties* literalProperties) diff --git a/Analysis/src/Unifier2.cpp b/Analysis/src/Unifier2.cpp index 0d9391c9..1708f08d 100644 --- a/Analysis/src/Unifier2.cpp +++ b/Analysis/src/Unifier2.cpp @@ -20,6 +20,7 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTFLAG(LuauEnableWriteOnlyProperties) LUAU_FASTFLAG(LuauEagerGeneralization4) +LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch) LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps) LUAU_FASTFLAG(LuauRefineTablesWithReadType) @@ -105,6 +106,12 @@ static bool areCompatible(TypeId left, TypeId right) // returns `true` if `ty` is irressolvable and should be added to `incompleteSubtypes`. static bool isIrresolvable(TypeId ty) { + if (FFlag::LuauStuckTypeFunctionsStillDispatch) + { + if (auto tfit = get(ty); tfit && tfit->state != TypeFunctionInstanceState::Unsolved) + return false; + } + return get(ty) || get(ty); } diff --git a/Ast/src/Parser.cpp b/Ast/src/Parser.cpp index 70388d99..0d2bf696 100644 --- a/Ast/src/Parser.cpp +++ b/Ast/src/Parser.cpp @@ -20,7 +20,6 @@ LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100) LUAU_FASTFLAGVARIABLE(LuauSolverV2) LUAU_FASTFLAGVARIABLE(LuauDeclareExternType) LUAU_FASTFLAGVARIABLE(LuauParseStringIndexer) -LUAU_FASTFLAGVARIABLE(LuauStoreLocalAnnotationColonPositions) LUAU_FASTFLAGVARIABLE(LuauCSTForReturnTypeFunctionTail) LUAU_FASTFLAGVARIABLE(LuauParseAttributeFixUninit) LUAU_DYNAMIC_FASTFLAGVARIABLE(DebugLuauReportReturnTypeVariadicWithTypeSuffix, false) @@ -691,11 +690,7 @@ AstStat* Parser::parseFor() allocator.alloc(Location(start, end), copy(vars), copy(values), body, hasIn, inLocation, hasDo, matchDo.location); if (options.storeCstData) { - if (FFlag::LuauStoreLocalAnnotationColonPositions) - cstNodeMap[node] = - allocator.alloc(extractAnnotationColonPositions(names), varsCommaPosition, copy(valuesCommaPositions)); - else - cstNodeMap[node] = allocator.alloc(AstArray{}, varsCommaPosition, copy(valuesCommaPositions)); + cstNodeMap[node] = allocator.alloc(extractAnnotationColonPositions(names), varsCommaPosition, copy(valuesCommaPositions)); } return node; } @@ -1019,11 +1014,7 @@ AstStat* Parser::parseLocal(const AstArray& attributes) AstStatLocal* node = allocator.alloc(Location(start, end), copy(vars), copy(values), equalsSignLocation); if (options.storeCstData) { - if (FFlag::LuauStoreLocalAnnotationColonPositions) - cstNodeMap[node] = - allocator.alloc(extractAnnotationColonPositions(names), varsCommaPositions, copy(valuesCommaPositions)); - else - cstNodeMap[node] = allocator.alloc(AstArray{}, varsCommaPositions, copy(valuesCommaPositions)); + cstNodeMap[node] = allocator.alloc(extractAnnotationColonPositions(names), varsCommaPositions, copy(valuesCommaPositions)); } return node; @@ -1608,17 +1599,11 @@ std::pair Parser::parseFunctionBody( if (lexer.current().type != ')') { - if (FFlag::LuauStoreLocalAnnotationColonPositions) - { - if (cstNode) - std::tie(vararg, varargLocation, varargAnnotation) = - parseBindingList(args, /* allowDot3= */ true, &cstNode->argsCommaPositions, nullptr, &cstNode->varargAnnotationColonPosition); - else - std::tie(vararg, varargLocation, varargAnnotation) = parseBindingList(args, /* allowDot3= */ true); - } - else + if (cstNode) std::tie(vararg, varargLocation, varargAnnotation) = - parseBindingList(args, /* allowDot3= */ true, cstNode ? &cstNode->argsCommaPositions : nullptr); + parseBindingList(args, /* allowDot3= */ true, &cstNode->argsCommaPositions, nullptr, &cstNode->varargAnnotationColonPosition); + else + std::tie(vararg, varargLocation, varargAnnotation) = parseBindingList(args, /* allowDot3= */ true); } std::optional argLocation; @@ -1676,8 +1661,7 @@ std::pair Parser::parseFunctionBody( if (options.storeCstData) { cstNode->functionKeywordPosition = matchFunction.location.begin; - if (FFlag::LuauStoreLocalAnnotationColonPositions) - cstNode->argsAnnotationColonPositions = extractAnnotationColonPositions(args); + cstNode->argsAnnotationColonPositions = extractAnnotationColonPositions(args); cstNodeMap[node] = cstNode; } @@ -1716,7 +1700,7 @@ Parser::Binding Parser::parseBinding() Position colonPosition = lexer.current().location.begin; AstType* annotation = parseOptionalType(); - if (FFlag::LuauStoreLocalAnnotationColonPositions && options.storeCstData) + if (options.storeCstData) return Binding(*name, annotation, colonPosition); else return Binding(*name, annotation); @@ -1724,7 +1708,6 @@ Parser::Binding Parser::parseBinding() AstArray Parser::extractAnnotationColonPositions(const TempVector& bindings) { - LUAU_ASSERT(FFlag::LuauStoreLocalAnnotationColonPositions); TempVector annotationColonPositions(scratchPosition); for (size_t i = 0; i < bindings.size(); ++i) annotationColonPositions.push_back(bindings[i].colonPosition); @@ -1755,7 +1738,7 @@ std::tuple Parser::parseBindingList( AstTypePack* tailAnnotation = nullptr; if (lexer.current().type == ':') { - if (FFlag::LuauStoreLocalAnnotationColonPositions && varargAnnotationColonPosition) + if (varargAnnotationColonPosition) *varargAnnotationColonPosition = lexer.current().location.begin; nextLexeme(); diff --git a/tests/ConstraintGeneratorFixture.h b/tests/ConstraintGeneratorFixture.h index 57257eae..4366d804 100644 --- a/tests/ConstraintGeneratorFixture.h +++ b/tests/ConstraintGeneratorFixture.h @@ -20,7 +20,7 @@ struct ConstraintGeneratorFixture : Fixture ModulePtr mainModule; DcrLogger logger; UnifierSharedState sharedState{&ice}; - Normalizer normalizer{&arena, getBuiltins(), NotNull{&sharedState}}; + Normalizer normalizer{&arena, getBuiltins(), NotNull{&sharedState}, SolverMode::New}; SimplifierPtr simplifier; TypeCheckLimits limits; TypeFunctionRuntime typeFunctionRuntime{NotNull{&ice}, NotNull{&limits}}; diff --git a/tests/Fixture.cpp b/tests/Fixture.cpp index 060a32f2..9a318845 100644 --- a/tests/Fixture.cpp +++ b/tests/Fixture.cpp @@ -555,6 +555,18 @@ TypeId Fixture::requireExportedType(const ModuleName& moduleName, const std::str return it->second.type; } +std::string Fixture::canonicalize(TypeId ty) +{ + if (!simplifier) + simplifier = newSimplifier(NotNull{&simplifierArena}, getBuiltins()); + + auto res = eqSatSimplify(NotNull{simplifier.get()}, ty); + if (res) + return toString(res->result); + else + return toString(ty); +} + std::string Fixture::decorateWithTypes(const std::string& code) { fileResolver.source[mainModuleName] = code; diff --git a/tests/Fixture.h b/tests/Fixture.h index e1ee2753..032e7374 100644 --- a/tests/Fixture.h +++ b/tests/Fixture.h @@ -2,6 +2,7 @@ #pragma once #include "Luau/Config.h" +#include "Luau/EqSatSimplification.h" #include "Luau/Error.h" #include "Luau/FileResolver.h" #include "Luau/Frontend.h" @@ -145,6 +146,8 @@ struct Fixture TypeId requireTypeAlias(const std::string& name); TypeId requireExportedType(const ModuleName& moduleName, const std::string& name); + std::string canonicalize(TypeId ty); + // While most flags can be flipped inside the unit test, some code changes affect the state that is part of Fixture initialization // Most often those are changes related to builtin type definitions. // In that case, flag can be forced to 'true' using the example below: @@ -187,6 +190,9 @@ protected: bool forAutocomplete = false; std::optional frontend; BuiltinTypes* builtinTypes = nullptr; + + TypeArena simplifierArena; + SimplifierPtr simplifier{nullptr, nullptr}; }; struct BuiltinsFixture : Fixture diff --git a/tests/FragmentAutocomplete.test.cpp b/tests/FragmentAutocomplete.test.cpp index e5797d6c..b0bff82e 100644 --- a/tests/FragmentAutocomplete.test.cpp +++ b/tests/FragmentAutocomplete.test.cpp @@ -7,6 +7,7 @@ #include "Luau/Autocomplete.h" #include "Luau/BuiltinDefinitions.h" #include "Luau/Common.h" +#include "Luau/FileResolver.h" #include "Luau/Frontend.h" #include "Luau/AutocompleteTypes.h" #include "Luau/ToString.h" @@ -32,6 +33,8 @@ LUAU_FASTFLAG(LuauFragmentAcMemoryLeak) LUAU_FASTFLAG(LuauGlobalVariableModuleIsolation) LUAU_FASTFLAG(LuauFragmentAutocompleteIfRecommendations) LUAU_FASTFLAG(LuauPopulateRefinedTypesInFragmentFromOldSolver) +LUAU_FASTFLAG(LuauSolverAgnosticStringification) +LUAU_FASTFLAG(LuauFragmentRequiresCanBeResolvedToAModule) static std::optional nullCallback(std::string tag, std::optional ptr, std::optional contents) { @@ -53,7 +56,7 @@ static FrontendOptions getOptions() static ModuleResolver& getModuleResolver(Frontend& frontend) { - return FFlag::LuauSolverV2 ?frontend.moduleResolver : frontend.moduleResolverForAutocomplete; + return FFlag::LuauSolverV2 ? frontend.moduleResolver : frontend.moduleResolverForAutocomplete; } template @@ -157,6 +160,7 @@ struct FragmentAutocompleteFixtureImpl : BaseType ) { ScopedFastFlag sff{FFlag::LuauSolverV2, true}; + this->getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::New); this->check(document, getOptions()); FragmentAutocompleteStatusResult result = autocompleteFragment(updated, cursorPos, fragmentEndPosition); @@ -173,6 +177,7 @@ struct FragmentAutocompleteFixtureImpl : BaseType ) { ScopedFastFlag sff{FFlag::LuauSolverV2, false}; + this->getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::Old); this->check(document, getOptions()); FragmentAutocompleteStatusResult result = autocompleteFragment(updated, cursorPos, fragmentEndPosition); @@ -189,6 +194,7 @@ struct FragmentAutocompleteFixtureImpl : BaseType ) { ScopedFastFlag sff{FFlag::LuauSolverV2, true}; + this->getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::New); this->check(document, getOptions()); FragmentAutocompleteStatusResult result = autocompleteFragment(updated, cursorPos, fragmentEndPosition); @@ -196,6 +202,7 @@ struct FragmentAutocompleteFixtureImpl : BaseType assertions(result); ScopedFastFlag _{FFlag::LuauSolverV2, false}; + this->getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::Old); this->check(document, getOptions()); result = autocompleteFragment(updated, cursorPos, fragmentEndPosition); @@ -1317,7 +1324,7 @@ abc("bar") CHECK_EQ(Position{3, 1}, parent->location.end); } -TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "respects_getFrontend().options") +TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "respects_frontend_options") { DOES_NOT_PASS_NEW_SOLVER_GUARD(); @@ -1329,7 +1336,7 @@ t FrontendOptions opts; opts.forAutocomplete = true; - + getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old); getFrontend().check("game/A", opts); CHECK_NE(getFrontend().moduleResolverForAutocomplete.getModule("game/A"), nullptr); CHECK_EQ(getFrontend().moduleResolver.getModule("game/A"), nullptr); @@ -1411,6 +1418,7 @@ TEST_SUITE_BEGIN("MixedModeTests"); TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "mixed_mode_basic_example_append") { ScopedFastFlag sff{FFlag::LuauSolverV2, false}; + getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old); auto res = checkOldSolver( R"( local x = 4 @@ -1437,6 +1445,7 @@ local z = x + y TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "mixed_mode_basic_example_inlined") { ScopedFastFlag sff{FFlag::LuauSolverV2, false}; + getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old); auto res = checkOldSolver( R"( local x = 4 @@ -1461,6 +1470,7 @@ local y = 5 TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "mixed_mode_can_autocomplete_simple_property_access") { ScopedFastFlag sff{FFlag::LuauSolverV2, false}; + getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old); auto res = checkOldSolver( R"( local tbl = { abc = 1234} @@ -1521,6 +1531,7 @@ TEST_SUITE_BEGIN("FragmentAutocompleteTests"); TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "multiple_fragment_autocomplete") { + ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; ToStringOptions opt; opt.exhaustive = true; opt.exhaustive = true; @@ -1574,12 +1585,14 @@ return module)"; { ScopedFastFlag sff{FFlag::LuauSolverV2, false}; - checkAndExamine(source, "module", "{ }"); - fragmentACAndCheck(updated1, Position{1, 17}, "module", "{ }", "{ a: (%error-id%: unknown) -> () }"); - fragmentACAndCheck(updated2, Position{1, 18}, "module", "{ }", "{ ab: (%error-id%: unknown) -> () }"); + getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::Old); + checkAndExamine(source, "module", "{| |}"); + fragmentACAndCheck(updated1, Position{1, 17}, "module", "{| |}", "{| a: (%error-id%: unknown) -> () |}"); + fragmentACAndCheck(updated2, Position{1, 18}, "module", "{| |}", "{| ab: (%error-id%: unknown) -> () |}"); } { ScopedFastFlag sff{FFlag::LuauSolverV2, true}; + getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::New); checkAndExamine(source, "module", "{ }"); // [TODO] CLI-140762 Fragment autocomplete still doesn't return correct result when LuauSolverV2 is on return; @@ -2895,6 +2908,7 @@ end) TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "fragment_autocomplete_ensures_memory_isolation") { + ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; ToStringOptions opt; opt.exhaustive = true; opt.exhaustive = true; @@ -2945,7 +2959,8 @@ return module)"; { ScopedFastFlag sff{FFlag::LuauSolverV2, false}; - checkAndExamine(source, "module", "{ }"); + getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::Old); + checkAndExamine(source, "module", "{| |}"); // [TODO] CLI-140762 we shouldn't mutate stale module in autocompleteFragment // early return since the following checking will fail, which it shouldn't! fragmentACAndCheck(updated1, Position{1, 17}, "module"); @@ -2954,6 +2969,7 @@ return module)"; { ScopedFastFlag sff{FFlag::LuauSolverV2, true}; + getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::New); checkAndExamine(source, "module", "{ }"); // [TODO] CLI-140762 we shouldn't mutate stale module in autocompleteFragment // early return since the following checking will fail, which it shouldn't! @@ -3865,6 +3881,38 @@ end }); } +TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "inline_prop_read_on_requires_provides_results") +{ + ScopedFastFlag sff{FFlag::LuauFragmentRequiresCanBeResolvedToAModule, true}; + const std::string moduleA = R"( +local mod = { prop1 = true} +mod.prop2 = "a" +function mod.foo(a: number) + return a +end +return mod +)"; + + const std::string mainModule = R"( + +)"; + + fileResolver.source["MainModule"] = mainModule; + fileResolver.source["MainModule/A"] = moduleA; + getFrontend().check("MainModule/A", getOptions()); + getFrontend().check("MainModule", getOptions()); + + const std::string updatedMain = R"( +require(script.A). +)"; + + auto result = autocompleteFragment(updatedMain, Position{1, 18}); + CHECK(!result.result->acResults.entryMap.empty()); + CHECK(result.result->acResults.entryMap.count("prop1")); + CHECK(result.result->acResults.entryMap.count("prop2")); + CHECK(result.result->acResults.entryMap.count("foo")); +} + // NOLINTEND(bugprone-unchecked-optional-access) TEST_SUITE_END(); diff --git a/tests/Frontend.test.cpp b/tests/Frontend.test.cpp index 150c1ca5..984198a7 100644 --- a/tests/Frontend.test.cpp +++ b/tests/Frontend.test.cpp @@ -1359,7 +1359,7 @@ TEST_CASE_FIXTURE(FrontendFixture, "separate_caches_for_autocomplete") FrontendOptions opts; opts.forAutocomplete = true; - + getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old); getFrontend().check("game/A", opts); CHECK(nullptr == getFrontend().moduleResolver.getModule("game/A")); @@ -1731,6 +1731,7 @@ TEST_CASE_FIXTURE(FrontendFixture, "test_dependents_stored_on_node_as_graph_upda TEST_CASE_FIXTURE(FrontendFixture, "test_invalid_dependency_tracking_per_module_resolver") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, false}; + getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old); fileResolver.source["game/Gui/Modules/A"] = "return {hello=5, world=true}"; fileResolver.source["game/Gui/Modules/B"] = "return require(game:GetService('Gui').Modules.A)"; diff --git a/tests/Normalize.test.cpp b/tests/Normalize.test.cpp index c5449583..5a98f477 100644 --- a/tests/Normalize.test.cpp +++ b/tests/Normalize.test.cpp @@ -15,9 +15,8 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauNormalizeIntersectionLimit) LUAU_FASTINT(LuauNormalizeUnionLimit) LUAU_FASTFLAG(LuauSimplifyOutOfLine2) -LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2) LUAU_FASTFLAG(LuauNormalizationReorderFreeTypeIntersect) - +LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping) using namespace Luau; namespace @@ -34,7 +33,15 @@ struct IsSubtypeFixture : Fixture SimplifierPtr simplifier = newSimplifier(NotNull{&module->internalTypes}, getBuiltins()); - return ::Luau::isSubtype(a, b, NotNull{module->getModuleScope().get()}, getBuiltins(), NotNull{simplifier.get()}, ice); + return ::Luau::isSubtype( + a, + b, + NotNull{module->getModuleScope().get()}, + getBuiltins(), + NotNull{simplifier.get()}, + ice, + FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old + ); } }; } // namespace @@ -447,7 +454,7 @@ struct NormalizeFixture : Fixture TypeArena arena; InternalErrorReporter iceHandler; UnifierSharedState unifierState{&iceHandler}; - Normalizer normalizer{&arena, getBuiltins(), NotNull{&unifierState}}; + Normalizer normalizer{&arena, getBuiltins(), NotNull{&unifierState}, FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old}; Scope globalScope{getBuiltins()->anyTypePack}; NormalizeFixture() @@ -1216,7 +1223,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_union_type_pack_cycle") ScopedFastFlag sff[] = { {FFlag::LuauSolverV2, true}, {FFlag::LuauSimplifyOutOfLine2, true}, - {FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2, true}, + {FFlag::LuauReturnMappedGenericPacksFromSubtyping, true}, }; ScopedFastInt sfi{FInt::LuauTypeInferRecursionLimit, 0}; diff --git a/tests/Subtyping.test.cpp b/tests/Subtyping.test.cpp index 166220cd..db54bb15 100644 --- a/tests/Subtyping.test.cpp +++ b/tests/Subtyping.test.cpp @@ -17,6 +17,7 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauEagerGeneralization4) +LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping) using namespace Luau; @@ -67,7 +68,7 @@ struct SubtypeFixture : Fixture InternalErrorReporter iceReporter; UnifierSharedState sharedState{&ice}; SimplifierPtr simplifier = newSimplifier(NotNull{&arena}, getBuiltins()); - Normalizer normalizer{&arena, getBuiltins(), NotNull{&sharedState}}; + Normalizer normalizer{&arena, getBuiltins(), NotNull{&sharedState}, FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old}; TypeCheckLimits limits; TypeFunctionRuntime typeFunctionRuntime{NotNull{&iceReporter}, NotNull{&limits}}; @@ -1386,6 +1387,8 @@ TEST_CASE_FIXTURE(SubtypeFixture, "({ x: T }) -> T <: ({ method: ({ x: T } TEST_CASE_FIXTURE(SubtypeFixture, "subtyping_reasonings_to_follow_a_reduced_type_function_instance") { + ScopedFastFlag sff{FFlag::LuauReturnMappedGenericPacksFromSubtyping, true}; + TypeId longTy = arena.addType(UnionType{ {getBuiltins()->booleanType, getBuiltins()->bufferType, @@ -1408,8 +1411,10 @@ TEST_CASE_FIXTURE(SubtypeFixture, "subtyping_reasonings_to_follow_a_reduced_type if (reasoning.subPath.empty() && reasoning.superPath.empty()) continue; - std::optional optSubLeaf = traverse(subTy, reasoning.subPath, getBuiltins()); - std::optional optSuperLeaf = traverse(superTy, reasoning.superPath, getBuiltins()); + std::optional optSubLeaf = + traverse(subTy, reasoning.subPath, getBuiltins(), NotNull{&result.mappedGenericPacks}, NotNull{&arena}); + std::optional optSuperLeaf = + traverse(superTy, reasoning.superPath, getBuiltins(), NotNull{&result.mappedGenericPacks}, NotNull{&arena}); if (!optSubLeaf || !optSuperLeaf) CHECK(false); diff --git a/tests/ToString.test.cpp b/tests/ToString.test.cpp index b7e7d421..e4eae04e 100644 --- a/tests/ToString.test.cpp +++ b/tests/ToString.test.cpp @@ -16,6 +16,7 @@ LUAU_FASTFLAG(LuauRecursiveTypeParameterRestriction) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauFixEmptyTypePackStringification) LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2) +LUAU_FASTFLAG(LuauSolverAgnosticStringification) TEST_SUITE_BEGIN("ToString"); @@ -47,12 +48,22 @@ TEST_CASE_FIXTURE(Fixture, "bound_types") TEST_CASE_FIXTURE(Fixture, "free_types") { + ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; DOES_NOT_PASS_NEW_SOLVER_GUARD(); CheckResult result = check("local a"); LUAU_REQUIRE_NO_ERRORS(result); - CHECK_EQ("a", toString(requireType("a"))); + CHECK_EQ("'a", toString(requireType("a"))); +} + +TEST_CASE_FIXTURE(Fixture, "free_types_stringify_the_same_regardless_of_solver") +{ + ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; + TypeArena a; + TypeId t = a.addType(FreeType{getFrontend().globals.globalScope.get(), getFrontend().builtinTypes->neverType, getFrontend().builtinTypes->unknownType}); + + CHECK_EQ("'a", toString(t)); } TEST_CASE_FIXTURE(Fixture, "cyclic_table") diff --git a/tests/Transpiler.test.cpp b/tests/Transpiler.test.cpp index 0ab36246..e4d415b5 100644 --- a/tests/Transpiler.test.cpp +++ b/tests/Transpiler.test.cpp @@ -12,7 +12,6 @@ using namespace Luau; -LUAU_FASTFLAG(LuauStoreLocalAnnotationColonPositions) LUAU_FASTFLAG(LuauCSTForReturnTypeFunctionTail) TEST_SUITE_BEGIN("TranspilerTests"); @@ -314,9 +313,6 @@ TEST_CASE("function_spaces_around_tokens") TEST_CASE("function_with_types_spaces_around_tokens") { - ScopedFastFlag sffs[] = { - {FFlag::LuauStoreLocalAnnotationColonPositions, true}, - }; std::string code = R"( function p(o: string, m: number, ...: any): string end )"; CHECK_EQ(code, transpile(code, {}, true).code); @@ -1132,9 +1128,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_type_reference_spaces_around_tokens") TEST_CASE_FIXTURE(Fixture, "transpile_type_annotation_spaces_around_tokens") { - ScopedFastFlag sffs[] = { - {FFlag::LuauStoreLocalAnnotationColonPositions, true}, - }; std::string code = R"( local _: Type )"; CHECK_EQ(code, transpile(code, {}, true).code); @@ -1153,9 +1146,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_type_annotation_spaces_around_tokens") TEST_CASE_FIXTURE(Fixture, "transpile_for_loop_annotation_spaces_around_tokens") { - ScopedFastFlag sffs[] = { - {FFlag::LuauStoreLocalAnnotationColonPositions, true}, - }; std::string code = R"( for i: number = 1, 10 do end )"; CHECK_EQ(code, transpile(code, {}, true).code); diff --git a/tests/TypeFunction.test.cpp b/tests/TypeFunction.test.cpp index 068c173f..6deb5d33 100644 --- a/tests/TypeFunction.test.cpp +++ b/tests/TypeFunction.test.cpp @@ -18,6 +18,7 @@ LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauSimplifyOutOfLine2) LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2) LUAU_FASTFLAG(LuauErrorSuppressionTypeFunctionArgs) +LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch) LUAU_FASTFLAG(LuauEmptyStringInKeyOf) struct TypeFunctionFixture : Fixture @@ -743,6 +744,11 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_oss_crash_gh1161") if (!FFlag::LuauSolverV2) return; + ScopedFastFlag sff[] = { + {FFlag::LuauEagerGeneralization4, true}, + {FFlag::LuauStuckTypeFunctionsStillDispatch, true} + }; + CheckResult result = check(R"( local EnumVariants = { ["a"] = 1, ["b"] = 2, ["c"] = 3 @@ -758,9 +764,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_oss_crash_gh1161") fnB(result) )"); - LUAU_REQUIRE_ERROR_COUNT(2, result); - CHECK(get(result.errors[0])); - CHECK(get(result.errors[1])); + LUAU_CHECK_ERROR_COUNT(1, result); + LUAU_CHECK_ERROR(result, FunctionExitsWithoutReturning); } TEST_CASE_FIXTURE(TypeFunctionFixture, "fuzzer_numeric_binop_doesnt_assert_on_generalizeFreeType") @@ -1707,6 +1712,59 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "error_suppression_should_work_on_type_functi CHECK("Unknown type 'Colours'" == toString(result.errors[0])); } +TEST_CASE_FIXTURE(BuiltinsFixture, "fully_dispatch_type_function_that_is_parameterized_on_a_stuck_type_function") +{ + // In this test, we infer + // + // (c + d) : add, *error-type*> + // + // This type function is stuck because it is parameterized on a stuck type + // function. The call constraint must be able to dispatch. + + ScopedFastFlag sff[] = { + {FFlag::LuauEagerGeneralization4, true}, + {FFlag::LuauStuckTypeFunctionsStillDispatch, true}, + }; + + CheckResult result = check(R"( + --!strict + + local function f() + local a + local b + + local c = a + b + + print(c + d) + end + )"); + + LUAU_REQUIRE_ERRORS(result); + LUAU_CHECK_NO_ERROR(result, ConstraintSolvingIncompleteError); + + CHECK("() -> ()" == toString(requireType("f"))); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "undefined_add_application") +{ + ScopedFastFlag sff[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauEagerGeneralization4, true}, + {FFlag::LuauStuckTypeFunctionsStillDispatch, true}, + }; + + CheckResult result = check(R"( + function add(a: A, b: B): add + return a + b + end + + local s = add(5, "hello") + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, result); + LUAU_CHECK_ERROR(result, UninhabitedTypeFunction); +} + TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_should_not_assert_on_empty_string_props") { if (!FFlag::LuauSolverV2) @@ -1745,7 +1803,7 @@ struct TFFixture InternalErrorReporter ice; UnifierSharedState unifierState{&ice}; SimplifierPtr simplifier = EqSatSimplification::newSimplifier(arena, getBuiltins()); - Normalizer normalizer{arena, getBuiltins(), NotNull{&unifierState}}; + Normalizer normalizer{arena, getBuiltins(), NotNull{&unifierState}, SolverMode::New}; TypeCheckLimits limits; TypeFunctionRuntime runtime{NotNull{&ice}, NotNull{&limits}}; @@ -1794,4 +1852,54 @@ TEST_CASE_FIXTURE(TFFixture, "or<'a, 'b>") CHECK(res.reducedTypes.size() == 1); } +TEST_CASE_FIXTURE(TFFixture, "a_type_function_parameterized_on_generics_is_solved") +{ + TypeId a = arena->addType(GenericType{"A"}); + TypeId b = arena->addType(GenericType{"B"}); + + TypeId addTy = arena->addType(TypeFunctionInstanceType{builtinTypeFunctions.addFunc, {a, b}}); + + reduceTypeFunctions(addTy, Location{}, tfc); + + const auto tfit = get(addTy); + REQUIRE(tfit); + + CHECK(tfit->state == TypeFunctionInstanceState::Solved); +} + +TEST_CASE_FIXTURE(TFFixture, "a_tf_parameterized_on_a_solved_tf_is_solved") +{ + ScopedFastFlag sff{FFlag::LuauStuckTypeFunctionsStillDispatch, true}; + + TypeId a = arena->addType(GenericType{"A"}); + TypeId b = arena->addType(GenericType{"B"}); + + TypeId innerAddTy = arena->addType(TypeFunctionInstanceType{builtinTypeFunctions.addFunc, {a, b}}); + + TypeId outerAddTy = arena->addType(TypeFunctionInstanceType{builtinTypeFunctions.addFunc, {builtinTypes_.numberType, innerAddTy}}); + + reduceTypeFunctions(outerAddTy, Location{}, tfc); + + const auto tfit = get(outerAddTy); + REQUIRE(tfit); + + CHECK(tfit->state == TypeFunctionInstanceState::Solved); +} + +TEST_CASE_FIXTURE(TFFixture, "a_tf_parameterized_on_a_stuck_tf_is_stuck") +{ + ScopedFastFlag sff{FFlag::LuauStuckTypeFunctionsStillDispatch, true}; + + TypeId innerAddTy = arena->addType(TypeFunctionInstanceType{builtinTypeFunctions.addFunc, {builtinTypes_.bufferType, builtinTypes_.booleanType}}); + + TypeId outerAddTy = arena->addType(TypeFunctionInstanceType{builtinTypeFunctions.addFunc, {builtinTypes_.numberType, innerAddTy}}); + + reduceTypeFunctions(outerAddTy, Location{}, tfc); + + const auto tfit = get(outerAddTy); + REQUIRE(tfit); + + CHECK(tfit->state == TypeFunctionInstanceState::Stuck); +} + TEST_SUITE_END(); diff --git a/tests/TypeFunction.user.test.cpp b/tests/TypeFunction.user.test.cpp index 89d624a6..4309d606 100644 --- a/tests/TypeFunction.user.test.cpp +++ b/tests/TypeFunction.user.test.cpp @@ -14,6 +14,7 @@ LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2) LUAU_FASTFLAG(LuauUserTypeFunctionAliases) LUAU_FASTFLAG(LuauFollowTypeAlias) LUAU_FASTFLAG(LuauFollowExistingTypeFunction) +LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch) LUAU_FASTFLAG(LuauTypeFunctionSerializeFollowMetatable) TEST_SUITE_BEGIN("UserDefinedTypeFunctionTests"); @@ -2412,7 +2413,11 @@ TEST_CASE_FIXTURE(ExternTypeFixture, "type_alias_reduction_errors") if (!FFlag::LuauSolverV2) return; - ScopedFastFlag luauUserTypeFunctionAliases{FFlag::LuauUserTypeFunctionAliases, true}; + ScopedFastFlag sff[] = { + {FFlag::LuauUserTypeFunctionAliases, true}, + {FFlag::LuauEagerGeneralization4, true}, + {FFlag::LuauStuckTypeFunctionsStillDispatch, true}, + }; CheckResult result = check(R"( type Test = setmetatable @@ -2424,11 +2429,10 @@ end local function ok(idx: get<>): number return idx end )"); - // TODO: type solving fails to complete in this test because of the blocked NameConstraint on the 'Test' alias - LUAU_REQUIRE_ERROR_COUNT(5, result); + LUAU_REQUIRE_ERROR_COUNT(4, result); CHECK( toString(result.errors[1]) == - R"('get' type function errored at runtime: [string "get"]:5: failed to reduce type function with: Type function instance setmetatable is uninhabited)" + R"(Type function instance get<> is uninhabited)" ); } diff --git a/tests/TypeInfer.builtins.test.cpp b/tests/TypeInfer.builtins.test.cpp index f337b5db..c2d99da8 100644 --- a/tests/TypeInfer.builtins.test.cpp +++ b/tests/TypeInfer.builtins.test.cpp @@ -19,6 +19,7 @@ LUAU_FASTFLAG(LuauWriteOnlyPropertyMangling) LUAU_FASTFLAG(LuauEnableWriteOnlyProperties) LUAU_FASTFLAG(LuauTableLiteralSubtypeCheckFunctionCalls) LUAU_FASTFLAG(LuauTypeCheckerStricterIndexCheck) +LUAU_FASTFLAG(LuauSuppressErrorsForMultipleNonviableOverloads) TEST_SUITE_BEGIN("BuiltinTests"); @@ -1746,6 +1747,15 @@ TEST_CASE_FIXTURE(Fixture, "write_only_table_assertion") )")); } +TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_into_any") +{ + ScopedFastFlag _{FFlag::LuauSuppressErrorsForMultipleNonviableOverloads, true}; + + LUAU_REQUIRE_NO_ERRORS(check(R"( +table.insert(1::any, 2::any) + )")); +} + TEST_CASE_FIXTURE(BuiltinsFixture, "read_refinements_on_persistent_tables_known_property_identity") { // This will not result in a real refinement, as we refine `bnot`, a function, to be truthy diff --git a/tests/TypeInfer.functions.test.cpp b/tests/TypeInfer.functions.test.cpp index 40ae20de..599dfed3 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -28,8 +28,11 @@ LUAU_FASTFLAG(LuauFormatUseLastPosition) LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType) LUAU_FASTFLAG(LuauSimplifyOutOfLine2) LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2) +LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch) LUAU_FASTFLAG(LuauAvoidGenericsLeakingDuringFunctionCallCheck) LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps) +LUAU_FASTFLAG(LuauSolverAgnosticStringification) +LUAU_FASTFLAG(LuauSuppressErrorsForMultipleNonviableOverloads) TEST_SUITE_BEGIN("TypeInferFunctions"); @@ -259,6 +262,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "vararg_function_is_quantified") TEST_CASE_FIXTURE(Fixture, "list_only_alternative_overloads_that_match_argument_count") { + ScopedFastFlag _{FFlag::LuauSuppressErrorsForMultipleNonviableOverloads, true}; + CheckResult result = check(R"( local multiply: ((number)->number) & ((number)->string) & ((number, number)->number) multiply("") @@ -268,9 +273,9 @@ TEST_CASE_FIXTURE(Fixture, "list_only_alternative_overloads_that_match_argument_ if (FFlag::LuauSolverV2) { - GenericError* g = get(result.errors[0]); - REQUIRE(g); - CHECK(g->message == "None of the overloads for function that accept 1 arguments are compatible."); + MultipleNonviableOverloads* mno = get(result.errors[0]); + REQUIRE_MESSAGE(mno, "Expected MultipleNonviableOverloads but got " << result.errors[0]); + CHECK_EQ(mno->attemptedArgCount, 1); } else { @@ -1388,6 +1393,7 @@ f(function(x) return x * 2 end) TEST_CASE_FIXTURE(BuiltinsFixture, "infer_generic_function_function_argument") { + ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; // FIXME: CLI-116133 bidirectional type inference needs to push expected types in for higher-order function calls DOES_NOT_PASS_NEW_SOLVER_GUARD(); @@ -1414,7 +1420,7 @@ local r = foldl(a, {s=0,c=0}, function(a, b) return {s = a.s + b, c = a.c + 1} e )"); LUAU_REQUIRE_NO_ERRORS(result); - REQUIRE_EQ("{ c: number, s: number }", toString(requireType("r"))); + REQUIRE_EQ("{| c: number, s: number |}", toString(requireType("r"))); } TEST_CASE_FIXTURE(Fixture, "infer_generic_function_function_argument_overloaded") @@ -1690,6 +1696,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "function_decl_non_self_sealed_overwrite") TEST_CASE_FIXTURE(BuiltinsFixture, "function_decl_non_self_sealed_overwrite_2") { + ScopedFastFlag sff{FFlag::LuauStuckTypeFunctionsStillDispatch, true}; + CheckResult result = check(R"( local t: { f: ((x: number) -> number)? } = {} @@ -1706,9 +1714,7 @@ end if (FFlag::LuauEagerGeneralization4 && FFlag::LuauSolverV2) { - // FIXME CLI-151985 - LUAU_CHECK_ERROR_COUNT(3, result); - LUAU_CHECK_ERROR(result, ConstraintSolvingIncompleteError); + LUAU_CHECK_ERROR_COUNT(2, result); LUAU_CHECK_ERROR(result, WhereClauseNeeded); // x2 } else if (FFlag::LuauSolverV2) @@ -1776,6 +1782,8 @@ TEST_CASE_FIXTURE(Fixture, "inferred_higher_order_functions_are_quantified_at_th TEST_CASE_FIXTURE(BuiltinsFixture, "function_decl_non_self_unsealed_overwrite") { + ScopedFastFlag sff{FFlag::LuauStuckTypeFunctionsStillDispatch, true}; + CheckResult result = check(R"( local t = { f = nil :: ((x: number) -> number)? } @@ -1791,9 +1799,7 @@ end if (FFlag::LuauEagerGeneralization4 && FFlag::LuauSolverV2) { - // FIXME CLI-151985 - LUAU_CHECK_ERROR_COUNT(2, result); - LUAU_CHECK_ERROR(result, ConstraintSolvingIncompleteError); + LUAU_CHECK_ERROR_COUNT(1, result); LUAU_CHECK_ERROR(result, WhereClauseNeeded); } else if (FFlag::LuauSolverV2) @@ -2669,10 +2675,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tf_suggest_arg_type_2") end )"); - LUAU_REQUIRE_ERRORS(result); - auto err = get(result.errors.back()); - REQUIRE(err); - CHECK("a" == toString(err->ty)); + LUAU_REQUIRE_ERROR(result, NotATable); } TEST_CASE_FIXTURE(Fixture, "local_function_fwd_decl_doesnt_crash") diff --git a/tests/TypeInfer.generics.test.cpp b/tests/TypeInfer.generics.test.cpp index 1c55a43b..c015319a 100644 --- a/tests/TypeInfer.generics.test.cpp +++ b/tests/TypeInfer.generics.test.cpp @@ -1,9 +1,6 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/TypeInfer.h" #include "Luau/Type.h" -#include "Luau/Scope.h" - -#include #include "Fixture.h" @@ -13,12 +10,13 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions) -LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2) +LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping) LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2) LUAU_FASTFLAG(LuauIntersectNotNil) LUAU_FASTFLAG(LuauSubtypingCheckFunctionGenericCounts) LUAU_FASTFLAG(LuauReportSubtypingErrors) LUAU_FASTFLAG(LuauEagerGeneralization4) +LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch) LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps) using namespace Luau; @@ -1006,7 +1004,7 @@ local TheDispatcher: Dispatcher = { TEST_CASE_FIXTURE(Fixture, "generic_argument_count_too_few") { - ScopedFastFlag sff{FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2, true}; + ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping, true}; CheckResult result = check(R"( function test(a: number) @@ -1024,8 +1022,7 @@ wrapper(test) { const CountMismatch* cm = get(result.errors[0]); REQUIRE_MESSAGE(cm, "Expected CountMismatch but got " << result.errors[0]); - // TODO: CLI-152070 fix to expect 2 - CHECK_EQ(cm->expected, 1); + CHECK_EQ(cm->expected, 2); CHECK_EQ(cm->actual, 1); CHECK_EQ(cm->context, CountMismatch::Arg); } @@ -1035,7 +1032,7 @@ wrapper(test) TEST_CASE_FIXTURE(Fixture, "generic_argument_count_too_many") { - DOES_NOT_PASS_NEW_SOLVER_GUARD(); + ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping, true}; CheckResult result = check(R"( function test2(a: number, b: string) @@ -1049,7 +1046,16 @@ wrapper(test2, 1, "", 3) )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), R"(Argument count mismatch. Function 'wrapper' expects 3 arguments, but 4 are specified)"); + if (FFlag::LuauSolverV2) + { + const CountMismatch* cm = get(result.errors[0]); + REQUIRE_MESSAGE(cm, "Expected CountMismatch but got " << result.errors[0]); + CHECK_EQ(cm->expected, 3); + CHECK_EQ(cm->actual, 4); + CHECK_EQ(cm->context, CountMismatch::Arg); + } + else + CHECK_EQ(toString(result.errors[0]), R"(Argument count mismatch. Function 'wrapper' expects 3 arguments, but 4 are specified)"); } TEST_CASE_FIXTURE(Fixture, "generic_argument_count_just_right") @@ -1070,6 +1076,8 @@ wrapper(test2, 1, "") TEST_CASE_FIXTURE(Fixture, "generic_argument_pack_type_inferred_from_return") { + ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping, true}; + CheckResult result = check(R"( function test2(a: number) return "hello" @@ -1081,21 +1089,24 @@ end wrapper(test2, 1) )"); + LUAU_REQUIRE_ERROR_COUNT(1, result); + if (FFlag::LuauSolverV2) { - // TODO: CLI-152070 should expect a TypeMismatch, rather than not erroring - LUAU_REQUIRE_NO_ERRORS(result); + const TypeMismatch* tm = get(result.errors[0]); + REQUIRE_MESSAGE(tm, "Expected TypeMismatch but got " << result.errors[0]); + CHECK_EQ(toString(tm->wantedType), "string"); + CHECK_EQ(toString(tm->givenType), "number"); } else { - LUAU_REQUIRE_ERROR_COUNT(1, result); CHECK_EQ(toString(result.errors[0]), R"(Type 'number' could not be converted into 'string')"); } } TEST_CASE_FIXTURE(Fixture, "generic_argument_pack_type_inferred_from_return_no_error") { - ScopedFastFlag _{FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2, true}; + ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping, true}; CheckResult result = check(R"( function test2(a: number) @@ -1111,6 +1122,64 @@ wrapper(test2, "hello") LUAU_REQUIRE_NO_ERRORS(result); } +TEST_CASE_FIXTURE(Fixture, "nested_generic_argument_type_packs") +{ + ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping, true}; + + CheckResult result = check(R"( +function test2(a: number) + return 3 +end + +function foo(f: (B...) -> number, ...: B...) + return f(...) +end + +-- want A... to contain a generic type pack too + +function wrapper(f: (A...) -> number, ...: A...) +end + +-- A... = ((B...) -> number, B...)) +-- B... = (number) +-- A... = ((number) -> number, number) +wrapper(foo, test2, 3) +wrapper(foo, test2, 3, 3) +wrapper(foo, test2) +wrapper(foo, test2, "3") + )"); + + LUAU_REQUIRE_ERROR_COUNT(3, result); + if (FFlag::LuauSolverV2) + { + CHECK_EQ(result.errors[0].location, Location{{18, 0}, {18, 7}}); + CountMismatch* cm = get(result.errors[0]); + REQUIRE_MESSAGE(cm, "Expected CountMismatch but got " << result.errors[0]); + CHECK_EQ(cm->expected, 3); + CHECK_EQ(cm->actual, 4); + CHECK_EQ(cm->context, CountMismatch::Arg); + + CHECK_EQ(result.errors[1].location, Location{{19, 0}, {19, 7}}); + cm = get(result.errors[1]); + REQUIRE_MESSAGE(cm, "Expected CountMismatch but got " << result.errors[1]); + CHECK_EQ(cm->expected, 3); + CHECK_EQ(cm->actual, 2); + CHECK_EQ(cm->context, CountMismatch::Arg); + + CHECK_EQ(result.errors[2].location, Location{{20, 20}, {20, 23}}); + TypeMismatch* tm = get(result.errors[2]); + REQUIRE_MESSAGE(tm, "Expected TypeMismatch but got " << result.errors[2]); + CHECK_EQ(toString(tm->wantedType), "number"); + CHECK_EQ(toString(tm->givenType), "string"); + } + else + { + CHECK_EQ(toString(result.errors[0]), R"(Argument count mismatch. Function 'wrapper' expects 3 arguments, but 4 are specified)"); + CHECK_EQ(toString(result.errors[1]), R"(Argument count mismatch. Function 'wrapper' expects 3 arguments, but only 2 are specified)"); + CHECK_EQ(toString(result.errors[2]), R"(Type 'string' could not be converted into 'number')"); + } +} + TEST_CASE_FIXTURE(Fixture, "generic_function") { CheckResult result = check(R"( @@ -1446,6 +1515,9 @@ TEST_CASE_FIXTURE(Fixture, "infer_generic_function_function_argument_overloaded" LUAU_REQUIRE_NO_ERRORS(result); } +// Important FIXME CLI-158432: This test exposes some problems with overload +// selection and generic type substitution when +// FFlag::LuauStuckTypeFunctionsStillDispatch is set. TEST_CASE_FIXTURE(BuiltinsFixture, "do_not_infer_generic_functions") { ScopedFastFlag _[] = { @@ -1461,15 +1533,20 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "do_not_infer_generic_functions") local function sum(x: a, y: a, f: (a, a) -> a) return f(x, y) end local function sumrec(f: typeof(sum)) - return sum(2, 3, function(a: T, b: T): add return a + b end) + return sum(2, 3, function(a: X, b: X): add return a + b end) end local b = sumrec(sum) -- ok local c = sumrec(function(x, y, f) return f(x, y) end) -- type binders are not inferred )"); - CHECK_EQ("(a, a, (a, a) -> a) -> a", toString(requireType("sum"))); - CHECK_EQ("(a, a, (a, a) -> a) -> a", toString(requireTypeAtPosition({7, 29}))); + if (FFlag::LuauStuckTypeFunctionsStillDispatch) // FIXME CLI-158432 + CHECK("add | number" == toString(requireType("b"))); + else + CHECK("number" == toString(requireType("b"))); + + CHECK("(a, a, (a, a) -> a) -> a" == toString(requireType("sum"))); + CHECK("(a, a, (a, a) -> a) -> a" == toString(requireTypeAtPosition({7, 29}))); } else { @@ -1485,7 +1562,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "do_not_infer_generic_functions") )"); } - LUAU_REQUIRE_NO_ERRORS(result); + if (!FFlag::LuauStuckTypeFunctionsStillDispatch) // FIXME CLI-158432 + LUAU_REQUIRE_NO_ERRORS(result); } diff --git a/tests/TypeInfer.intersectionTypes.test.cpp b/tests/TypeInfer.intersectionTypes.test.cpp index 5cd578d3..c94b8f77 100644 --- a/tests/TypeInfer.intersectionTypes.test.cpp +++ b/tests/TypeInfer.intersectionTypes.test.cpp @@ -12,6 +12,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2) LUAU_FASTFLAG(LuauRefineTablesWithReadType) +LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping) TEST_SUITE_BEGIN("IntersectionTypes"); @@ -1149,6 +1150,8 @@ could not be converted into TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_4") { + ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping, true}; + CheckResult result = check(R"( function f() function g(x : ((a...) -> ()) & ((number,a...) -> number)) @@ -1162,15 +1165,17 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_4") if (FFlag::LuauSolverV2) { - const std::string expected = "Type\n\t" - "'((a...) -> ()) & ((number, a...) -> number)'" - "\ncould not be converted into\n\t" - "'((a...) -> ()) & ((number, a...) -> number)'; \n" - "this is because \n\t" - " * in the 1st component of the intersection, the function returns is `()` in the former type and `number` in " - "the latter type, and `()` is not a subtype of `number`\n\t" - " * in the 2nd component of the intersection, the function takes a tail of `a...` and in the 1st component of " - "the intersection, the function takes a tail of `a...`, and `a...` is not a supertype of `a...`"; + // TODO: CLI-159120 - this error message is bogus + const std::string expected = + "Type\n\t" + "'((a...) -> ()) & ((number, a...) -> number)'" + "\ncould not be converted into\n\t" + "'((a...) -> ()) & ((number, a...) -> number)'; \n" + "this is because \n\t" + " * in the 1st component of the intersection, the function returns is `()` in the former type and `number` in " + "the latter type, and `()` is not a subtype of `number`\n\t" + " * in the 2nd component of the intersection, the function takes a tail of `number, a...` and in the 1st component of " + "the intersection, the function takes a tail of `number, a...`, and `number, a...` is not a supertype of `number, a...`"; CHECK(expected == toString(result.errors[0])); } else diff --git a/tests/TypeInfer.modules.test.cpp b/tests/TypeInfer.modules.test.cpp index d394cc3b..dcd2409c 100644 --- a/tests/TypeInfer.modules.test.cpp +++ b/tests/TypeInfer.modules.test.cpp @@ -14,7 +14,7 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions) LUAU_FASTFLAG(LuauEagerGeneralization4) -LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2) +LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping) using namespace Luau; @@ -824,7 +824,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cycles_dont_make_everything_any") TEST_CASE_FIXTURE(BuiltinsFixture, "cross_module_function_mutation") { - ScopedFastFlag _[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2, true}}; + ScopedFastFlag _[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauReturnMappedGenericPacksFromSubtyping, true}}; fileResolver.source["game/A"] = R"( function test2(a: number, b: string) diff --git a/tests/TypeInfer.provisional.test.cpp b/tests/TypeInfer.provisional.test.cpp index 20da4322..8e20fc00 100644 --- a/tests/TypeInfer.provisional.test.cpp +++ b/tests/TypeInfer.provisional.test.cpp @@ -19,6 +19,7 @@ LUAU_FASTINT(LuauTypeInferIterationLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferTypePackLoopLimit) LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops) +LUAU_FASTFLAG(LuauSolverAgnosticStringification) TEST_SUITE_BEGIN("ProvisionalTests"); @@ -534,6 +535,7 @@ TEST_CASE_FIXTURE(Fixture, "dcr_can_partially_dispatch_a_constraint") TEST_CASE_FIXTURE(Fixture, "free_options_cannot_be_unified_together") { + ScopedFastFlag sff_stringification{FFlag::LuauSolverAgnosticStringification, true}; ScopedFastFlag sff{FFlag::LuauSolverV2, false}; TypeArena arena; @@ -549,7 +551,7 @@ TEST_CASE_FIXTURE(Fixture, "free_options_cannot_be_unified_together") InternalErrorReporter iceHandler; UnifierSharedState sharedState{&iceHandler}; - Normalizer normalizer{&arena, getBuiltins(), NotNull{&sharedState}}; + Normalizer normalizer{&arena, getBuiltins(), NotNull{&sharedState}, SolverMode::Old}; Unifier u{NotNull{&normalizer}, NotNull{scope.get()}, Location{}, Variance::Covariant}; u.tryUnify(option1, option2); @@ -559,10 +561,10 @@ TEST_CASE_FIXTURE(Fixture, "free_options_cannot_be_unified_together") u.log.commit(); ToStringOptions opts; - CHECK("a?" == toString(option1, opts)); + CHECK("'a?" == toString(option1, opts)); // CHECK("a?" == toString(option2, opts)); // This should hold, but does not. - CHECK("b?" == toString(option2, opts)); // This should not hold. + CHECK("'b?" == toString(option2, opts)); // This should not hold. } TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop_with_zero_iterators") @@ -677,7 +679,7 @@ struct IsSubtypeFixture : Fixture if (!module->hasModuleScope()) FAIL("isSubtype: module scope data is not available"); - return ::Luau::isSubtype(a, b, NotNull{module->getModuleScope().get()}, getBuiltins(), NotNull{simplifier.get()}, ice); + return ::Luau::isSubtype(a, b, NotNull{module->getModuleScope().get()}, getBuiltins(), NotNull{simplifier.get()}, ice, SolverMode::New); } }; } // namespace @@ -962,6 +964,7 @@ TEST_CASE_FIXTURE(Fixture, "floating_generics_should_not_be_allowed") TEST_CASE_FIXTURE(Fixture, "free_options_can_be_unified_together") { + ScopedFastFlag sff_stringification{FFlag::LuauSolverAgnosticStringification, true}; ScopedFastFlag sff{FFlag::LuauSolverV2, false}; TypeArena arena; @@ -977,7 +980,7 @@ TEST_CASE_FIXTURE(Fixture, "free_options_can_be_unified_together") InternalErrorReporter iceHandler; UnifierSharedState sharedState{&iceHandler}; - Normalizer normalizer{&arena, getBuiltins(), NotNull{&sharedState}}; + Normalizer normalizer{&arena, getBuiltins(), NotNull{&sharedState}, SolverMode::Old}; Unifier u{NotNull{&normalizer}, NotNull{scope.get()}, Location{}, Variance::Covariant}; u.tryUnify(option1, option2); @@ -987,8 +990,8 @@ TEST_CASE_FIXTURE(Fixture, "free_options_can_be_unified_together") u.log.commit(); ToStringOptions opts; - CHECK("a?" == toString(option1, opts)); - CHECK("b?" == toString(option2, opts)); // should be `a?`. + CHECK("'a?" == toString(option1, opts)); + CHECK("'b?" == toString(option2, opts)); // should be `a?`. } TEST_CASE_FIXTURE(Fixture, "unify_more_complex_unions_that_include_nil") @@ -1279,7 +1282,7 @@ TEST_CASE_FIXTURE(Fixture, "table_containing_non_final_type_is_erroneously_cache TypeArena arena; Scope globalScope(getBuiltins()->anyTypePack); UnifierSharedState sharedState{&ice}; - Normalizer normalizer{&arena, getBuiltins(), NotNull{&sharedState}}; + Normalizer normalizer{&arena, getBuiltins(), NotNull{&sharedState}, SolverMode::New}; TypeId tableTy = arena.addType(TableType{}); TableType* table = getMutable(tableTy); diff --git a/tests/TypeInfer.refinements.test.cpp b/tests/TypeInfer.refinements.test.cpp index 874e4ed9..8cc6f48c 100644 --- a/tests/TypeInfer.refinements.test.cpp +++ b/tests/TypeInfer.refinements.test.cpp @@ -15,8 +15,10 @@ LUAU_FASTFLAG(LuauFunctionCallsAreNotNilable) LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions) LUAU_FASTFLAG(LuauSimplificationTableExternType) LUAU_FASTFLAG(LuauBetterCannotCallFunctionPrimitive) +LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch) LUAU_FASTFLAG(LuauTypeCheckerStricterIndexCheck) LUAU_FASTFLAG(LuauNormalizationIntersectTablesPreservesExternTypes) +LUAU_FASTFLAG(LuauNormalizationReorderFreeTypeIntersect) LUAU_FASTFLAG(LuauAvoidDoubleNegation) LUAU_FASTFLAG(LuauRefineTablesWithReadType) @@ -140,6 +142,7 @@ struct RefinementExternTypeFixture : BuiltinsFixture for (const auto& [name, ty] : getFrontend().globals.globalScope->exportedTypeBindings) persist(ty.type); + getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old); freeze(getFrontend().globals.globalTypes); } @@ -653,6 +656,12 @@ TEST_CASE_FIXTURE(Fixture, "lvalue_is_not_nil") TEST_CASE_FIXTURE(Fixture, "free_type_is_equal_to_an_lvalue") { + ScopedFastFlag sff[] = { + {FFlag::LuauEagerGeneralization4, true}, + {FFlag::LuauStuckTypeFunctionsStillDispatch, true}, + {FFlag::LuauNormalizationReorderFreeTypeIntersect, true}, + }; + CheckResult result = check(R"( local function f(a, b: string?) if a == b then @@ -664,11 +673,20 @@ TEST_CASE_FIXTURE(Fixture, "free_type_is_equal_to_an_lvalue") LUAU_REQUIRE_NO_ERRORS(result); if (FFlag::LuauSolverV2) - CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "unknown"); // a == b - else - CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "a"); // a == b + { + CHECK(toString(requireTypeAtPosition({3, 33})) == "unknown"); // a == b - CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "string?"); // a == b + // FIXME: This type either comes out as string? or (string?) & unknown + // depending on which tests are run and in which order. I'm not sure + // where the nondeterminism is coming from. + // CHECK(toString(requireTypeAtPosition({3, 36})) == "string?"); // a == b + CHECK(canonicalize(requireTypeAtPosition({3, 36})) == "string?"); // a == b + } + else + { + CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "a"); // a == b + CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "string?"); // a == b + } } TEST_CASE_FIXTURE(Fixture, "unknown_lvalue_is_not_synonymous_with_other_on_not_equal") @@ -1424,7 +1442,7 @@ TEST_CASE_FIXTURE(RefinementExternTypeFixture, "typeguard_cast_free_table_to_vec { // CLI-115286 - Refining via type(x) == 'vector' does not work in the new solver DOES_NOT_PASS_NEW_SOLVER_GUARD(); - + getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old); CheckResult result = check(R"( local function f(vec) local X, Y, Z = vec.X, vec.Y, vec.Z @@ -2438,7 +2456,7 @@ end) )")); } -TEST_CASE_FIXTURE(Fixture, "refinements_table_intersection_limits" * doctest::timeout(0.5)) +TEST_CASE_FIXTURE(Fixture, "refinements_table_intersection_limits" * doctest::timeout(1.0)) { CheckResult result = check(R"( --!strict diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index ebe2274c..3a10fa5c 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -33,9 +33,11 @@ LUAU_FASTINT(LuauPrimitiveInferenceInTableLimit) LUAU_FASTFLAG(LuauAutocompleteMissingFollows) LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps) LUAU_FASTFLAG(LuauRelateTablesAreNeverDisjoint) +LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch) LUAU_FASTFLAG(LuauTableLiteralSubtypeCheckFunctionCalls) LUAU_FASTFLAG(LuauAvoidGenericsLeakingDuringFunctionCallCheck) LUAU_FASTFLAG(LuauRefineTablesWithReadType) +LUAU_FASTFLAG(LuauSolverAgnosticStringification) TEST_SUITE_BEGIN("TableTests"); @@ -1721,6 +1723,7 @@ TEST_CASE_FIXTURE(Fixture, "right_table_missing_key") // Could be flaky if the fix has regressed. TEST_CASE_FIXTURE(Fixture, "right_table_missing_key2") { + ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; // CLI-114792 We don't report MissingProperties DOES_NOT_PASS_NEW_SOLVER_GUARD(); @@ -1738,8 +1741,8 @@ TEST_CASE_FIXTURE(Fixture, "right_table_missing_key2") REQUIRE_EQ(1, mp->properties.size()); CHECK_EQ(mp->properties[0], "a"); - CHECK_EQ("{| [string]: string, a: string |}", toString(mp->superType)); - CHECK_EQ("{| |}", toString(mp->subType)); + CHECK_EQ("{ [string]: string, a: string }", toString(mp->superType)); + CHECK_EQ("{ }", toString(mp->subType)); } TEST_CASE_FIXTURE(Fixture, "casting_unsealed_tables_with_props_into_table_with_indexer") @@ -2421,6 +2424,8 @@ local t: { a: {Foo}, b: number } = { // since mutating properties means table properties should be invariant. TEST_CASE_FIXTURE(Fixture, "invariant_table_properties_means_instantiating_tables_in_assignment_is_unsound") { + ScopedFastFlag sff{FFlag::LuauStuckTypeFunctionsStillDispatch, true}; + CheckResult result = check(R"( --!strict local t = {} @@ -2434,10 +2439,8 @@ TEST_CASE_FIXTURE(Fixture, "invariant_table_properties_means_instantiating_table if (FFlag::LuauEagerGeneralization4 && FFlag::LuauSolverV2) { - // FIXME CLI-151985 - LUAU_CHECK_ERROR_COUNT(2, result); + LUAU_CHECK_ERROR_COUNT(1, result); LUAU_CHECK_ERROR(result, ExplicitFunctionAnnotationRecommended); - LUAU_CHECK_ERROR(result, ConstraintSolvingIncompleteError); } else if (FFlag::LuauSolverV2) { @@ -2768,6 +2771,7 @@ TEST_CASE_FIXTURE(Fixture, "explicitly_typed_table_with_indexer") TEST_CASE_FIXTURE(BuiltinsFixture, "recursive_metatable_type_call") { + ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; // CLI-114782 DOES_NOT_PASS_NEW_SOLVER_GUARD(); @@ -2779,7 +2783,7 @@ b() LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), R"(Cannot call a value of type t1 where t1 = { @metatable { __call: t1 }, { } })"); + CHECK_EQ(toString(result.errors[0]), R"(Cannot call a value of type t1 where t1 = { @metatable {| __call: t1 |}, {| |} })"); } TEST_CASE_FIXTURE(Fixture, "table_subtyping_shouldn't_add_optional_properties_to_sealed_tables") diff --git a/tests/TypeInfer.test.cpp b/tests/TypeInfer.test.cpp index 187664a7..b54d4552 100644 --- a/tests/TypeInfer.test.cpp +++ b/tests/TypeInfer.test.cpp @@ -614,7 +614,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tc_after_error_recovery_no_replacement_name_ { { DOES_NOT_PASS_NEW_SOLVER_GUARD(); - + getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old); CheckResult result = check(R"( --!strict local t = { x = 10, y = 20 } @@ -625,6 +625,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tc_after_error_recovery_no_replacement_name_ } { + getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old); CheckResult result = check(R"( --!strict export type = number @@ -636,7 +637,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tc_after_error_recovery_no_replacement_name_ { DOES_NOT_PASS_NEW_SOLVER_GUARD(); - + getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old); CheckResult result = check(R"( --!strict function string.() end @@ -646,6 +647,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tc_after_error_recovery_no_replacement_name_ } { + getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old); CheckResult result = check(R"( --!strict local function () end @@ -656,6 +658,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tc_after_error_recovery_no_replacement_name_ } { + getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old); CheckResult result = check(R"( --!strict local dm = {} @@ -1153,7 +1156,7 @@ TEST_CASE_FIXTURE(Fixture, "cli_50041_committing_txnlog_in_apollo_client_error") "\ncaused by:\n" " Property 'getStoreFieldName' is not compatible.\n" "Type\n\t" - "'(Policies, FieldSpecifier & {| from: number? |}) -> (a, b...)'" + "'(Policies, FieldSpecifier & { from: number? }) -> ('a, b...)'" "\ncould not be converted into\n\t" "'(Policies, FieldSpecifier) -> string'" "\ncaused by:\n" @@ -1161,10 +1164,10 @@ TEST_CASE_FIXTURE(Fixture, "cli_50041_committing_txnlog_in_apollo_client_error") "Type\n\t" "'FieldSpecifier'" "\ncould not be converted into\n\t" - "'FieldSpecifier & {| from: number? |}'" + "'FieldSpecifier & { from: number? }'" "\ncaused by:\n" " Not all intersection parts are compatible.\n" - "Table type 'FieldSpecifier' not compatible with type '{| from: number? |}' because the former has extra field 'fieldName'"; + "Table type 'FieldSpecifier' not compatible with type '{ from: number? }' because the former has extra field 'fieldName'"; CHECK_EQ(expected, toString(result.errors[0])); } else @@ -2381,6 +2384,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "is_safe_integer_example") TEST_CASE_FIXTURE(BuiltinsFixture, "type_remover_heap_use_after_free") { + ScopedFastFlag sff{FFlag::LuauEagerGeneralization4, true}; + LUAU_REQUIRE_ERRORS(check(R"( _ = if l0.n0.n0 then {n4(...,setmetatable(setmetatable(_),_)),_ == _,} elseif _.ceil._ then _ elseif _ then not _ )")); diff --git a/tests/TypeInfer.tryUnify.test.cpp b/tests/TypeInfer.tryUnify.test.cpp index 49dd8494..2b0efd68 100644 --- a/tests/TypeInfer.tryUnify.test.cpp +++ b/tests/TypeInfer.tryUnify.test.cpp @@ -23,7 +23,7 @@ struct TryUnifyFixture : Fixture ScopePtr globalScope{new Scope{arena.addTypePack({TypeId{}})}}; InternalErrorReporter iceHandler; UnifierSharedState unifierState{&iceHandler}; - Normalizer normalizer{&arena, getBuiltins(), NotNull{&unifierState}}; + Normalizer normalizer{&arena, getBuiltins(), NotNull{&unifierState}, SolverMode::Old}; Unifier state{NotNull{&normalizer}, NotNull{globalScope.get()}, Location{}, Variance::Covariant}; }; diff --git a/tests/TypeInfer.unknownnever.test.cpp b/tests/TypeInfer.unknownnever.test.cpp index 4f5feb01..f22d561c 100644 --- a/tests/TypeInfer.unknownnever.test.cpp +++ b/tests/TypeInfer.unknownnever.test.cpp @@ -7,6 +7,8 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2); +LUAU_FASTFLAG(LuauEagerGeneralization4); +LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch); TEST_SUITE_BEGIN("TypeInferUnknownNever"); @@ -346,6 +348,11 @@ TEST_CASE_FIXTURE(Fixture, "dont_unify_operands_if_one_of_the_operand_is_never_i TEST_CASE_FIXTURE(Fixture, "math_operators_and_never") { + ScopedFastFlag sff[] = { + {FFlag::LuauEagerGeneralization4, true}, + {FFlag::LuauStuckTypeFunctionsStillDispatch, true}, + }; + CheckResult result = check(R"( local function mul(x: nil, y) return x ~= nil and x * y -- infers boolean | never, which is normalized into boolean @@ -359,7 +366,7 @@ TEST_CASE_FIXTURE(Fixture, "math_operators_and_never") // CLI-114134 Egraph-based simplification. // CLI-116549 x ~= nil : false when x : nil - CHECK("(nil, a) -> and>" == toString(requireType("mul"))); + CHECK("(nil, a) -> false | mul" == toString(requireType("mul"))); } else { diff --git a/tests/TypePath.test.cpp b/tests/TypePath.test.cpp index 39eeb356..4024a494 100644 --- a/tests/TypePath.test.cpp +++ b/tests/TypePath.test.cpp @@ -21,11 +21,15 @@ LUAU_DYNAMIC_FASTINT(LuauTypePathMaximumTraverseSteps); struct TypePathFixture : Fixture { ScopedFastFlag sff1{FFlag::LuauSolverV2, true}; + TypeArena arena; + const DenseHashMap emptyMap{nullptr}; }; struct TypePathBuiltinsFixture : BuiltinsFixture { ScopedFastFlag sff1{FFlag::LuauSolverV2, true}; + TypeArena arena; + const DenseHashMap emptyMap{nullptr}; }; TEST_SUITE_BEGIN("TypePathManipulation"); @@ -108,7 +112,7 @@ TEST_SUITE_BEGIN("TypePathTraversal"); TEST_CASE_FIXTURE(TypePathFixture, "empty_traversal") { - CHECK(traverseForType(getBuiltins()->numberType, kEmpty, getBuiltins()) == getBuiltins()->numberType); + CHECK(traverseForType(getBuiltins()->numberType, kEmpty, getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}) == getBuiltins()->numberType); } TEST_CASE_FIXTURE(TypePathFixture, "table_property") @@ -117,14 +121,22 @@ TEST_CASE_FIXTURE(TypePathFixture, "table_property") local x = { y = 123 } )"); - CHECK(traverseForType(requireType("x"), Path(TypePath::Property{"y", true}), getBuiltins()) == getBuiltins()->numberType); + CHECK( + traverseForType(requireType("x"), Path(TypePath::Property{"y", true}), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}) == + getBuiltins()->numberType + ); } TEST_CASE_FIXTURE(ExternTypeFixture, "class_property") { // Force this here because vector2InstanceType won't get initialized until the frontend has been forced getFrontend(); - CHECK(traverseForType(vector2InstanceType, Path(TypePath::Property{"X", true}), getBuiltins()) == getBuiltins()->numberType); + const DenseHashMap emptyMap{nullptr}; + TypeArena arena; + CHECK( + traverseForType(vector2InstanceType, Path(TypePath::Property{"X", true}), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}) == + getBuiltins()->numberType + ); } TEST_CASE_FIXTURE(TypePathBuiltinsFixture, "metatable_property") @@ -151,7 +163,10 @@ TEST_CASE_FIXTURE(TypePathBuiltinsFixture, "metatable_property") )"); } - CHECK(traverseForType(requireType("x"), Path(TypePath::Property::read("x")), getBuiltins()) == getBuiltins()->numberType); + CHECK( + traverseForType(requireType("x"), Path(TypePath::Property::read("x")), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}) == + getBuiltins()->numberType + ); } TEST_CASE_FIXTURE(TypePathFixture, "index") @@ -164,12 +179,17 @@ TEST_CASE_FIXTURE(TypePathFixture, "index") SUBCASE("in_bounds") { - CHECK(traverseForType(requireTypeAlias("T"), Path(TypePath::Index{1}), getBuiltins()) == getBuiltins()->stringType); + CHECK( + traverseForType(requireTypeAlias("T"), Path(TypePath::Index{1}), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}) == + getBuiltins()->stringType + ); } SUBCASE("out_of_bounds") { - CHECK(traverseForType(requireTypeAlias("T"), Path(TypePath::Index{97}), getBuiltins()) == std::nullopt); + CHECK( + traverseForType(requireTypeAlias("T"), Path(TypePath::Index{97}), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}) == std::nullopt + ); } } @@ -182,7 +202,7 @@ TEST_CASE_FIXTURE(TypePathFixture, "index") SUBCASE("in_bounds") { - auto result = traverseForType(requireTypeAlias("T"), Path(TypePath::Index{1}), getBuiltins()); + auto result = traverseForType(requireTypeAlias("T"), Path(TypePath::Index{1}), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); CHECK(result); if (result) @@ -191,7 +211,9 @@ TEST_CASE_FIXTURE(TypePathFixture, "index") SUBCASE("out_of_bounds") { - CHECK(traverseForType(requireTypeAlias("T"), Path(TypePath::Index{97}), getBuiltins()) == std::nullopt); + CHECK( + traverseForType(requireTypeAlias("T"), Path(TypePath::Index{97}), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}) == std::nullopt + ); } } @@ -205,14 +227,14 @@ TEST_CASE_FIXTURE(TypePathFixture, "index") SUBCASE("in_bounds") { Path path = Path({TypePath::PackField::Arguments, TypePath::Index{1}}); - auto result = traverseForType(requireTypeAlias("T"), path, getBuiltins()); + auto result = traverseForType(requireTypeAlias("T"), path, getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); CHECK(result == getBuiltins()->stringType); } SUBCASE("out_of_bounds") { Path path = Path({TypePath::PackField::Arguments, TypePath::Index{72}}); - auto result = traverseForType(requireTypeAlias("T"), path, getBuiltins()); + auto result = traverseForType(requireTypeAlias("T"), path, getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); CHECK(result == std::nullopt); } } @@ -221,9 +243,12 @@ TEST_CASE_FIXTURE(TypePathFixture, "index") TEST_CASE_FIXTURE(ExternTypeFixture, "metatables") { getFrontend(); + const DenseHashMap emptyMap{nullptr}; + TypeArena arena; + SUBCASE("string") { - auto result = traverseForType(getBuiltins()->stringType, Path(TypeField::Metatable), getBuiltins()); + auto result = traverseForType(getBuiltins()->stringType, Path(TypeField::Metatable), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); CHECK(result == getMetatable(getBuiltins()->stringType, getBuiltins())); } @@ -233,7 +258,7 @@ TEST_CASE_FIXTURE(ExternTypeFixture, "metatables") type T = "foo" )"); - auto result = traverseForType(requireTypeAlias("T"), Path(TypeField::Metatable), getBuiltins()); + auto result = traverseForType(requireTypeAlias("T"), Path(TypeField::Metatable), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); CHECK(result == getMetatable(getBuiltins()->stringType, getBuiltins())); } @@ -248,7 +273,7 @@ TEST_CASE_FIXTURE(ExternTypeFixture, "metatables") )"); // Tricky test setup because 'setmetatable' mutates the argument 'tbl' type - auto result = traverseForType(requireType("res"), Path(TypeField::Table), getBuiltins()); + auto result = traverseForType(requireType("res"), Path(TypeField::Table), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); auto expected = lookupType("Table"); REQUIRE(expected); CHECK(result == follow(*expected)); @@ -261,13 +286,13 @@ TEST_CASE_FIXTURE(ExternTypeFixture, "metatables") local tbl = setmetatable({}, mt) )"); - auto result = traverseForType(requireType("tbl"), Path(TypeField::Metatable), getBuiltins()); + auto result = traverseForType(requireType("tbl"), Path(TypeField::Metatable), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); CHECK(result == requireType("mt")); } SUBCASE("class") { - auto result = traverseForType(vector2InstanceType, Path(TypeField::Metatable), getBuiltins()); + auto result = traverseForType(vector2InstanceType, Path(TypeField::Metatable), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); // ExternTypeFixture's Vector2 metatable is just an empty table, but it's there. CHECK(result); } @@ -286,22 +311,28 @@ TEST_CASE_FIXTURE(TypePathFixture, "bounds") SUBCASE("upper") { ft->upperBound = getBuiltins()->numberType; - auto result = traverseForType(ty, Path(TypeField::UpperBound), getBuiltins()); + auto result = traverseForType(ty, Path(TypeField::UpperBound), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); CHECK(result == getBuiltins()->numberType); } SUBCASE("lower") { ft->lowerBound = getBuiltins()->booleanType; - auto result = traverseForType(ty, Path(TypeField::LowerBound), getBuiltins()); + auto result = traverseForType(ty, Path(TypeField::LowerBound), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); CHECK(result == getBuiltins()->booleanType); } } SUBCASE("unbounded_type") { - CHECK(traverseForType(getBuiltins()->numberType, Path(TypeField::UpperBound), getBuiltins()) == std::nullopt); - CHECK(traverseForType(getBuiltins()->numberType, Path(TypeField::LowerBound), getBuiltins()) == std::nullopt); + CHECK( + traverseForType(getBuiltins()->numberType, Path(TypeField::UpperBound), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}) == + std::nullopt + ); + CHECK( + traverseForType(getBuiltins()->numberType, Path(TypeField::LowerBound), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}) == + std::nullopt + ); } } @@ -315,8 +346,10 @@ TEST_CASE_FIXTURE(TypePathFixture, "indexers") type T = { [string]: boolean } )"); - auto lookupResult = traverseForType(requireTypeAlias("T"), Path(TypeField::IndexLookup), getBuiltins()); - auto resultResult = traverseForType(requireTypeAlias("T"), Path(TypeField::IndexResult), getBuiltins()); + auto lookupResult = + traverseForType(requireTypeAlias("T"), Path(TypeField::IndexLookup), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); + auto resultResult = + traverseForType(requireTypeAlias("T"), Path(TypeField::IndexResult), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); CHECK(lookupResult == getBuiltins()->stringType); CHECK(resultResult == getBuiltins()->booleanType); @@ -328,8 +361,10 @@ TEST_CASE_FIXTURE(TypePathFixture, "indexers") type T = { y: number } )"); - auto lookupResult = traverseForType(requireTypeAlias("T"), Path(TypeField::IndexLookup), getBuiltins()); - auto resultResult = traverseForType(requireTypeAlias("T"), Path(TypeField::IndexResult), getBuiltins()); + auto lookupResult = + traverseForType(requireTypeAlias("T"), Path(TypeField::IndexLookup), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); + auto resultResult = + traverseForType(requireTypeAlias("T"), Path(TypeField::IndexResult), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); CHECK(lookupResult == std::nullopt); CHECK(resultResult == std::nullopt); @@ -347,13 +382,13 @@ TEST_CASE_FIXTURE(TypePathFixture, "negated") unfreeze(arena); TypeId ty = arena.addType(NegationType{getBuiltins()->numberType}); - auto result = traverseForType(ty, Path(TypeField::Negated), getBuiltins()); + auto result = traverseForType(ty, Path(TypeField::Negated), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); CHECK(result == getBuiltins()->numberType); } SUBCASE("not_negation") { - auto result = traverseForType(getBuiltins()->numberType, Path(TypeField::Negated), getBuiltins()); + auto result = traverseForType(getBuiltins()->numberType, Path(TypeField::Negated), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); CHECK(result == std::nullopt); } } @@ -366,13 +401,13 @@ TEST_CASE_FIXTURE(TypePathFixture, "variadic") unfreeze(arena); TypePackId tp = arena.addTypePack(VariadicTypePack{getBuiltins()->numberType}); - auto result = traverseForType(tp, Path(TypeField::Variadic), getBuiltins()); + auto result = traverseForType(tp, Path(TypeField::Variadic), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); CHECK(result == getBuiltins()->numberType); } SUBCASE("not_variadic") { - auto result = traverseForType(getBuiltins()->numberType, Path(TypeField::Variadic), getBuiltins()); + auto result = traverseForType(getBuiltins()->numberType, Path(TypeField::Variadic), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); CHECK(result == std::nullopt); } } @@ -386,7 +421,7 @@ TEST_CASE_FIXTURE(TypePathFixture, "arguments") end )"); - auto result = traverseForPack(requireType("f"), Path(PackField::Arguments), getBuiltins()); + auto result = traverseForPack(requireType("f"), Path(PackField::Arguments), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); CHECK(result); if (result) CHECK(toString(*result) == "number, string"); @@ -394,7 +429,7 @@ TEST_CASE_FIXTURE(TypePathFixture, "arguments") SUBCASE("not_function") { - auto result = traverseForPack(getBuiltins()->booleanType, Path(PackField::Arguments), getBuiltins()); + auto result = traverseForPack(getBuiltins()->booleanType, Path(PackField::Arguments), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); CHECK(result == std::nullopt); } } @@ -409,7 +444,7 @@ TEST_CASE_FIXTURE(TypePathFixture, "returns") end )"); - auto result = traverseForPack(requireType("f"), Path(PackField::Returns), getBuiltins()); + auto result = traverseForPack(requireType("f"), Path(PackField::Returns), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); CHECK(result); if (result) CHECK(toString(*result) == "number, string"); @@ -417,7 +452,7 @@ TEST_CASE_FIXTURE(TypePathFixture, "returns") SUBCASE("not_function") { - auto result = traverseForPack(getBuiltins()->booleanType, Path(PackField::Returns), getBuiltins()); + auto result = traverseForPack(getBuiltins()->booleanType, Path(PackField::Returns), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); CHECK(result == std::nullopt); } } @@ -430,7 +465,8 @@ TEST_CASE_FIXTURE(TypePathFixture, "tail") type T = (number, string, ...boolean) -> () )"); - auto result = traverseForPack(requireTypeAlias("T"), Path({PackField::Arguments, PackField::Tail}), getBuiltins()); + auto result = + traverseForPack(requireTypeAlias("T"), Path({PackField::Arguments, PackField::Tail}), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); CHECK(result); if (result) CHECK(toString(*result) == "...boolean"); @@ -442,17 +478,62 @@ TEST_CASE_FIXTURE(TypePathFixture, "tail") type T = (number, string) -> () )"); - auto result = traverseForPack(requireTypeAlias("T"), Path({PackField::Arguments, PackField::Tail}), getBuiltins()); + auto result = + traverseForPack(requireTypeAlias("T"), Path({PackField::Arguments, PackField::Tail}), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); CHECK(result == std::nullopt); } SUBCASE("type") { - auto result = traverseForPack(getBuiltins()->stringType, Path({PackField::Arguments, PackField::Tail}), getBuiltins()); + auto result = traverseForPack( + getBuiltins()->stringType, Path({PackField::Arguments, PackField::Tail}), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena} + ); CHECK(result == std::nullopt); } } +TEST_CASE_FIXTURE(TypePathFixture, "pack_slice_has_tail") +{ + TypeArena& arena = getFrontend().globals.globalTypes; + unfreeze(arena); + + TYPESOLVE_CODE(R"( + type T = (number, string, ...boolean) -> () + )"); + + auto result = + traverseForPack(requireTypeAlias("T"), Path({PackField::Arguments, PackSlice{1}}), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); + CHECK(result); + if (result) + CHECK(toString(*result) == "string, ...boolean"); +} + +TEST_CASE_FIXTURE(TypePathFixture, "pack_slice_finite_pack") +{ + TypeArena& arena = getFrontend().globals.globalTypes; + unfreeze(arena); + + TYPESOLVE_CODE(R"( + type T = (number, string) -> () + )"); + + auto result = + traverseForPack(requireTypeAlias("T"), Path({PackField::Arguments, PackSlice{1}}), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); + CHECK(result); + if (result) + CHECK(toString(*result) == "string"); +} + +TEST_CASE_FIXTURE(TypePathFixture, "pack_slice_type") +{ + TypeArena& arena = getFrontend().globals.globalTypes; + unfreeze(arena); + + auto result = + traverseForPack(builtinTypes->stringType, Path({PackField::Arguments, PackSlice{1}}), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); + CHECK(result == std::nullopt); +} + TEST_CASE_FIXTURE(TypePathFixture, "cycles" * doctest::timeout(0.5)) { // This will fail an occurs check, but it's a quick example of a cyclic type @@ -466,7 +547,7 @@ TEST_CASE_FIXTURE(TypePathFixture, "cycles" * doctest::timeout(0.5)) TypeId b = arena.addType(BoundType{a}); asMutable(a)->ty.emplace(b); - CHECK_THROWS(traverseForType(a, Path(TypeField::IndexResult), getBuiltins())); + CHECK_THROWS(traverseForType(a, Path(TypeField::IndexResult), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena})); } SUBCASE("table_contains_itself") @@ -477,7 +558,7 @@ TEST_CASE_FIXTURE(TypePathFixture, "cycles" * doctest::timeout(0.5)) TypeId tbl = arena.addType(TableType{}); getMutable(tbl)->props["a"] = Luau::Property(tbl); - auto result = traverseForType(tbl, Path(TypePath::Property{"a", true}), getBuiltins()); + auto result = traverseForType(tbl, Path(TypePath::Property{"a", true}), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); CHECK(result == tbl); } } @@ -498,7 +579,7 @@ TEST_CASE_FIXTURE(TypePathFixture, "step_limit") TypeId root = requireTypeAlias("T"); Path path = PathBuilder().readProp("x").readProp("y").readProp("z").build(); - auto result = traverseForType(root, path, getBuiltins()); + auto result = traverseForType(root, path, getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); CHECK(!result); } @@ -516,7 +597,7 @@ TEST_CASE_FIXTURE(TypePathBuiltinsFixture, "complex_chains") TypeId root = requireTypeAlias("Tab"); Path path = PathBuilder().mt().readProp("__add").rets().index(0).build(); - auto result = traverseForType(root, path, getBuiltins()); + auto result = traverseForType(root, path, getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); CHECK(result == getBuiltins()->numberType); } @@ -530,7 +611,7 @@ TEST_CASE_FIXTURE(TypePathBuiltinsFixture, "complex_chains") TypeId root = requireTypeAlias("Obj"); Path path = PathBuilder().readProp("method").index(0).args().index(1).build(); - auto result = traverseForType(root, path, getBuiltins()); + auto result = traverseForType(root, path, getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); CHECK(*result == getBuiltins()->falseType); } } @@ -564,6 +645,14 @@ TEST_CASE("human_property_then_metatable_portion") CHECK(toStringHuman(PathBuilder().writeProp("a").mt().build()) == "writing to `a` has the metatable portion as "); } +TEST_CASE("pack_slice") +{ + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; + + CHECK(toString(PathBuilder().packSlice(1).build()) == "[1:]"); + CHECK(toStringHuman(PathBuilder().packSlice(1).build()) == "the portion of the type pack starting at index 1 to the end"); +} + TEST_SUITE_END(); // TypePathToString TEST_SUITE_BEGIN("TypePathBuilder"); @@ -623,5 +712,9 @@ TEST_CASE("chained") Path({Index{0}, TypePath::Property::read("foo"), TypeField::Metatable, TypePath::Property::read("bar"), PackField::Arguments, Index{1}}) ); } +TEST_CASE("pack_slice") +{ + CHECK(PathBuilder().packSlice(3).build() == Path(PackSlice{3})); +} TEST_SUITE_END(); // TypePathBuilder