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 <afriesen@roblox.com>
Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com>
Co-authored-by: Sora Kanosue <skanosue@roblox.com>
Co-authored-by: Talha Pathan <tpathan@roblox.com>
Co-authored-by: Vighnesh Vijay <vvijay@roblox.com>
This commit is contained in:
Vighnesh-V 2025-06-27 13:14:36 -07:00 committed by GitHub
parent 8fe64db609
commit e190754565
Signed by: DevComp
GPG key ID: B5690EEEBB952194
64 changed files with 2342 additions and 536 deletions

View file

@ -6,6 +6,7 @@
#include "Luau/NotNull.h" #include "Luau/NotNull.h"
#include "Luau/Variant.h" #include "Luau/Variant.h"
#include "Luau/TypeFwd.h" #include "Luau/TypeFwd.h"
#include "Luau/TypeIds.h"
#include <string> #include <string>
#include <memory> #include <memory>
@ -316,7 +317,11 @@ struct Constraint
std::vector<NotNull<Constraint>> dependencies; std::vector<NotNull<Constraint>> dependencies;
DenseHashSet<TypeId> getMaybeMutatedFreeTypes() const; // Clip with LuauUseOrderedTypeSetsInConstraints
DenseHashSet<TypeId> getMaybeMutatedFreeTypes_DEPRECATED() const;
TypeIds getMaybeMutatedFreeTypes() const;
}; };
using ConstraintPtr = std::unique_ptr<Constraint>; using ConstraintPtr = std::unique_ptr<Constraint>;

View file

@ -11,6 +11,7 @@
#include "Luau/Location.h" #include "Luau/Location.h"
#include "Luau/Module.h" #include "Luau/Module.h"
#include "Luau/Normalize.h" #include "Luau/Normalize.h"
#include "Luau/OrderedSet.h"
#include "Luau/Substitution.h" #include "Luau/Substitution.h"
#include "Luau/ToString.h" #include "Luau/ToString.h"
#include "Luau/Type.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. // A mapping from free types to the number of unresolved constraints that mention them.
DenseHashMap<TypeId, size_t> unresolvedConstraints{{}}; DenseHashMap<TypeId, size_t> unresolvedConstraints{{}};
std::unordered_map<NotNull<const Constraint>, DenseHashSet<TypeId>> maybeMutatedFreeTypes; // Clip with LuauUseOrderedTypeSetsInConstraints
std::unordered_map<TypeId, DenseHashSet<const Constraint*>> mutatedFreeTypeToConstraint; std::unordered_map<NotNull<const Constraint>, DenseHashSet<TypeId>> maybeMutatedFreeTypes_DEPRECATED;
std::unordered_map<TypeId, DenseHashSet<const Constraint*>> mutatedFreeTypeToConstraint_DEPRECATED;
std::unordered_map<NotNull<const Constraint>, TypeIds> maybeMutatedFreeTypes;
std::unordered_map<TypeId, OrderedSet<const Constraint*>> mutatedFreeTypeToConstraint;
// Irreducible/uninhabited type functions or type pack functions. // Irreducible/uninhabited type functions or type pack functions.
DenseHashSet<const void*> uninhabitedTypeFunctions{{}}; DenseHashSet<const void*> uninhabitedTypeFunctions{{}};

View file

@ -4,7 +4,6 @@
#include "Luau/TypeFwd.h" #include "Luau/TypeFwd.h"
#include "Luau/NotNull.h" #include "Luau/NotNull.h"
#include "Luau/DenseHash.h"
#include <memory> #include <memory>
#include <optional> #include <optional>

View file

@ -496,6 +496,14 @@ struct GenericTypePackCountMismatch
bool operator==(const GenericTypePackCountMismatch& rhs) const; 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< using TypeErrorData = Variant<
TypeMismatch, TypeMismatch,
UnknownSymbol, UnknownSymbol,
@ -550,7 +558,8 @@ using TypeErrorData = Variant<
UnexpectedArrayLikeTableItem, UnexpectedArrayLikeTableItem,
CannotCheckDynamicStringFormatCalls, CannotCheckDynamicStringFormatCalls,
GenericTypeCountMismatch, GenericTypeCountMismatch,
GenericTypePackCountMismatch>; GenericTypePackCountMismatch,
MultipleNonviableOverloads>;
struct TypeErrorSummary struct TypeErrorSummary
{ {

View file

@ -173,11 +173,10 @@ struct Frontend
Frontend(FileResolver* fileResolver, ConfigResolver* configResolver, const FrontendOptions& options = {}); Frontend(FileResolver* fileResolver, ConfigResolver* configResolver, const FrontendOptions& options = {});
void setLuauSolverSelectionFromWorkspace(bool newSolverEnabled); void setLuauSolverSelectionFromWorkspace(SolverMode mode);
bool getLuauSolverSelection() const; SolverMode getLuauSolverMode() const;
bool getLuauSolverSelectionFlagged() const;
// The default value assuming there is no workspace setup yet // The default value assuming there is no workspace setup yet
std::atomic<bool> useNewLuauSolver{FFlag::LuauSolverV2}; std::atomic<SolverMode> useNewLuauSolver{FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old};
// Parse module graph and prepare SourceNode/SourceModule data, including required dependencies without running typechecking // Parse module graph and prepare SourceNode/SourceModule data, including required dependencies without running typechecking
void parse(const ModuleName& name); void parse(const ModuleName& name);
void parseModules(const std::vector<ModuleName>& name); void parseModules(const std::vector<ModuleName>& name);
@ -205,6 +204,7 @@ struct Frontend
void clearStats(); void clearStats();
void clear(); void clear();
void clearBuiltinEnvironments();
ScopePtr addEnvironment(const std::string& environmentName); ScopePtr addEnvironment(const std::string& environmentName);
ScopePtr getEnvironmentScope(const std::string& environmentName) const; ScopePtr getEnvironmentScope(const std::string& environmentName) const;
@ -272,7 +272,6 @@ private:
static LintResult classifyLints(const std::vector<LintWarning>& warnings, const Config& config); static LintResult classifyLints(const std::vector<LintWarning>& warnings, const Config& config);
ScopePtr getModuleEnvironment(const SourceModule& module, const Config& config, bool forAutocomplete) const; ScopePtr getModuleEnvironment(const SourceModule& module, const Config& config, bool forAutocomplete) const;
std::unordered_map<std::string, ScopePtr> environments; std::unordered_map<std::string, ScopePtr> environments;
std::unordered_map<std::string, std::function<void(Frontend&, GlobalTypes&, ScopePtr)>> builtinDefinitions; std::unordered_map<std::string, std::function<void(Frontend&, GlobalTypes&, ScopePtr)>> builtinDefinitions;

View file

@ -13,7 +13,7 @@ namespace Luau
struct GlobalTypes struct GlobalTypes
{ {
explicit GlobalTypes(NotNull<BuiltinTypes> builtinTypes); explicit GlobalTypes(NotNull<BuiltinTypes> builtinTypes, SolverMode mode);
NotNull<BuiltinTypes> builtinTypes; // Global types are based on builtin types NotNull<BuiltinTypes> builtinTypes; // Global types are based on builtin types
@ -22,6 +22,8 @@ struct GlobalTypes
ScopePtr globalScope; // shared by all modules ScopePtr globalScope; // shared by all modules
ScopePtr globalTypeFunctionScope; // shared by all modules ScopePtr globalTypeFunctionScope; // shared by all modules
SolverMode mode = SolverMode::Old;
}; };
} // namespace Luau } // namespace Luau

View file

@ -28,7 +28,8 @@ bool isSubtype(
NotNull<Scope> scope, NotNull<Scope> scope,
NotNull<BuiltinTypes> builtinTypes, NotNull<BuiltinTypes> builtinTypes,
NotNull<Simplifier> simplifier, NotNull<Simplifier> simplifier,
InternalErrorReporter& ice InternalErrorReporter& ice,
SolverMode solverMode
); );
bool isSubtype( bool isSubtype(
TypePackId subPack, TypePackId subPack,
@ -36,7 +37,8 @@ bool isSubtype(
NotNull<Scope> scope, NotNull<Scope> scope,
NotNull<BuiltinTypes> builtinTypes, NotNull<BuiltinTypes> builtinTypes,
NotNull<Simplifier> simplifier, NotNull<Simplifier> simplifier,
InternalErrorReporter& ice InternalErrorReporter& ice,
SolverMode solverMode
); );
} // namespace Luau } // namespace Luau
@ -311,14 +313,21 @@ class Normalizer
DenseHashMap<std::pair<TypeId, TypeId>, bool, TypeIdPairHash> cachedIsInhabitedIntersection{{nullptr, nullptr}}; DenseHashMap<std::pair<TypeId, TypeId>, bool, TypeIdPairHash> cachedIsInhabitedIntersection{{nullptr, nullptr}};
bool withinResourceLimits(); bool withinResourceLimits();
bool useNewLuauSolver() const;
public: public:
TypeArena* arena; TypeArena* arena;
NotNull<BuiltinTypes> builtinTypes; NotNull<BuiltinTypes> builtinTypes;
NotNull<UnifierSharedState> sharedState; NotNull<UnifierSharedState> sharedState;
bool cacheInhabitance = false; bool cacheInhabitance = false;
SolverMode solverMode;
Normalizer(TypeArena* arena, NotNull<BuiltinTypes> builtinTypes, NotNull<UnifierSharedState> sharedState, bool cacheInhabitance = false); Normalizer(
TypeArena* arena,
NotNull<BuiltinTypes> builtinTypes,
NotNull<UnifierSharedState> sharedState,
SolverMode solver,
bool cacheInhabitance = false
);
Normalizer(const Normalizer&) = delete; Normalizer(const Normalizer&) = delete;
Normalizer(Normalizer&&) = delete; Normalizer(Normalizer&&) = delete;
Normalizer() = delete; Normalizer() = delete;

View file

@ -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 <vector>
#include "Luau/DenseHash.h"
namespace Luau
{
template<typename T>
struct OrderedSet
{
using iterator = typename std::vector<T>::iterator;
using const_iterator = typename std::vector<T>::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<T> takeVector()
{
elementSet.clear();
return std::move(elements);
}
private:
std::vector<T> elements;
DenseHashSet<T> elementSet{nullptr};
};
}

View file

@ -87,6 +87,13 @@ private:
); );
size_t indexof(Analysis analysis); size_t indexof(Analysis analysis);
void add(Analysis analysis, TypeId ty, ErrorVec&& errors); void add(Analysis analysis, TypeId ty, ErrorVec&& errors);
void maybeEmplaceError(
ErrorVec* errors,
Location argLocation,
const SubtypingReasoning* reason,
std::optional<TypeId> failedSubTy,
std::optional<TypeId> failedSuperTy
) const;
}; };
struct SolveResult struct SolveResult

View file

@ -71,6 +71,7 @@ struct SubtypingResult
/// The reason for isSubtype to be false. May not be present even if /// The reason for isSubtype to be false. May not be present even if
/// isSubtype is false, depending on the input types. /// isSubtype is false, depending on the input types.
SubtypingReasonings reasoning{kEmptyReasoning}; SubtypingReasonings reasoning{kEmptyReasoning};
DenseHashMap<TypePackId, TypePackId> mappedGenericPacks{nullptr};
// If this subtype result required testing free types, we might be making // If this subtype result required testing free types, we might be making
// assumptions about what the free type eventually resolves to. If so, // assumptions about what the free type eventually resolves to. If so,

View file

@ -39,6 +39,12 @@ struct Constraint;
struct Subtyping; struct Subtyping;
struct TypeChecker2; struct TypeChecker2;
enum struct SolverMode
{
Old,
New
};
/** /**
* There are three kinds of type variables: * There are three kinds of type variables:
* - `Free` variables are metavariables, which stand for unconstrained types. * - `Free` variables are metavariables, which stand for unconstrained types.
@ -612,6 +618,21 @@ struct UserDefinedFunctionData
DenseHashMap<Name, std::pair<TypeFun*, size_t>> environmentAlias{""}; DenseHashMap<Name, std::pair<TypeFun*, size_t>> 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<nil, nil>
Stuck,
};
/** /**
* An instance of a type function that has not yet been reduced to a more concrete * 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 * type. The constraint solver receives a constraint to reduce each
@ -629,6 +650,8 @@ struct TypeFunctionInstanceType
std::optional<AstName> userFuncName; // Name of the user-defined type function; only available for UDTFs std::optional<AstName> userFuncName; // Name of the user-defined type function; only available for UDTFs
UserDefinedFunctionData userFuncData; UserDefinedFunctionData userFuncData;
TypeFunctionInstanceState state = TypeFunctionInstanceState::Unsolved;
TypeFunctionInstanceType( TypeFunctionInstanceType(
NotNull<const TypeFunction> function, NotNull<const TypeFunction> function,
std::vector<TypeId> typeArguments, std::vector<TypeId> typeArguments,
@ -970,7 +993,7 @@ struct BuiltinTypes
TypeId errorRecoveryType(TypeId guess) const; TypeId errorRecoveryType(TypeId guess) const;
TypePackId errorRecoveryTypePack(TypePackId guess) const; TypePackId errorRecoveryTypePack(TypePackId guess) const;
friend TypeId makeStringMetatable(NotNull<BuiltinTypes> builtinTypes); friend TypeId makeStringMetatable(NotNull<BuiltinTypes> builtinTypes, SolverMode mode);
friend struct GlobalTypes; friend struct GlobalTypes;
private: private:

View file

@ -225,11 +225,21 @@ private:
// Avoid duplicate warnings being emitted for the same global variable. // Avoid duplicate warnings being emitted for the same global variable.
DenseHashSet<std::string> warnedGlobals{""}; DenseHashSet<std::string> warnedGlobals{""};
void suggestAnnotations(AstExprFunction* expr, TypeId ty);
void diagnoseMissingTableKey(UnknownProperty* utk, TypeErrorData& data) const; void diagnoseMissingTableKey(UnknownProperty* utk, TypeErrorData& data) const;
bool isErrorSuppressing(Location loc, TypeId ty); bool isErrorSuppressing(Location loc, TypeId ty);
bool isErrorSuppressing(Location loc1, TypeId ty1, Location loc2, TypeId ty2); bool isErrorSuppressing(Location loc1, TypeId ty1, Location loc2, TypeId ty2);
bool isErrorSuppressing(Location loc, TypePackId tp); bool isErrorSuppressing(Location loc, TypePackId tp);
bool isErrorSuppressing(Location loc1, TypePackId tp1, Location loc2, TypePackId tp2); bool isErrorSuppressing(Location loc1, TypePackId tp1, Location loc2, TypePackId tp2);
// Returns whether we reported any errors
bool reportNonviableOverloadErrors(
std::vector<std::pair<TypeId, ErrorVec>> nonviableOverloads,
Location callFuncLocation,
size_t argHeadSize,
Location callLocation
);
}; };
} // namespace Luau } // namespace Luau

View file

@ -2,6 +2,7 @@
#pragma once #pragma once
#include "Luau/Common.h" #include "Luau/Common.h"
#include "Luau/DenseHash.h"
#include "Luau/NotNull.h" #include "Luau/NotNull.h"
#include "Luau/Polarity.h" #include "Luau/Polarity.h"
#include "Luau/TypeFwd.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 /// Flattens out a type pack. Also returns a valid TypePackId tail if the type pack's full size is not known
std::pair<std::vector<TypeId>, std::optional<TypePackId>> flatten(TypePackId tp); std::pair<std::vector<TypeId>, std::optional<TypePackId>> flatten(TypePackId tp);
std::pair<std::vector<TypeId>, std::optional<TypePackId>> flatten(TypePackId tp, const TxnLog& log); std::pair<std::vector<TypeId>, std::optional<TypePackId>> flatten(TypePackId tp, const TxnLog& log);
std::pair<std::vector<TypeId>, std::optional<TypePackId>> flatten(TypePackId tp, const DenseHashMap<TypePackId, TypePackId>& mappedGenericPacks);
/// Returs true if the type pack arose from a function that is declared to be variadic. /// 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! /// Returns *false* for function argument packs that are inferred to be safe to oversaturate!

View file

@ -1,9 +1,11 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once #pragma once
#include "Luau/DenseHash.h"
#include "Luau/NotNull.h"
#include "Luau/TypeArena.h"
#include "Luau/TypeFwd.h" #include "Luau/TypeFwd.h"
#include "Luau/Variant.h" #include "Luau/Variant.h"
#include "Luau/NotNull.h"
#include <optional> #include <optional>
#include <string> #include <string>
@ -91,6 +93,15 @@ enum class PackField
Tail, 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 /// Component that represents the result of a reduction
/// `resultType` is `never` if the reduction could not proceed /// `resultType` is `never` if the reduction could not proceed
struct Reduction struct Reduction
@ -102,7 +113,7 @@ struct Reduction
/// A single component of a path, representing one inner type or type pack to /// A single component of a path, representing one inner type or type pack to
/// traverse into. /// traverse into.
using Component = Luau::Variant<Property, Index, TypeField, PackField, Reduction>; using Component = Luau::Variant<Property, Index, TypeField, PackField, PackSlice, Reduction>;
/// A path through a type or type pack accessing a particular type or type pack /// A path through a type or type pack accessing a particular type or type pack
/// contained within. /// contained within.
@ -177,6 +188,7 @@ struct PathHash
size_t operator()(const Index& idx) const; size_t operator()(const Index& idx) const;
size_t operator()(const TypeField& field) const; size_t operator()(const TypeField& field) const;
size_t operator()(const PackField& 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 Reduction& reduction) const;
size_t operator()(const Component& component) const; size_t operator()(const Component& component) const;
size_t operator()(const Path& path) const; size_t operator()(const Path& path) const;
@ -205,6 +217,7 @@ struct PathBuilder
PathBuilder& args(); PathBuilder& args();
PathBuilder& rets(); PathBuilder& rets();
PathBuilder& tail(); PathBuilder& tail();
PathBuilder& packSlice(size_t start_index);
}; };
} // namespace TypePath } // 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. /// Converts a Path to a human readable string for error reporting.
std::string toStringHuman(const TypePath::Path& path); std::string toStringHuman(const TypePath::Path& path);
std::optional<TypeOrPack> traverse(TypeId root, const Path& path, NotNull<BuiltinTypes> builtinTypes); // TODO: clip traverse_DEPRECATED along with `LuauReturnMappedGenericPacksFromSubtyping`
std::optional<TypeOrPack> traverse(TypePackId root, const Path& path, NotNull<BuiltinTypes> builtinTypes); std::optional<TypeOrPack> traverse_DEPRECATED(TypeId root, const Path& path, NotNull<BuiltinTypes> builtinTypes);
std::optional<TypeOrPack> traverse_DEPRECATED(TypePackId root, const Path& path, NotNull<BuiltinTypes> builtinTypes);
std::optional<TypeOrPack> traverse(
TypePackId root,
const Path& path,
NotNull<BuiltinTypes> builtinTypes,
NotNull<const DenseHashMap<TypePackId, TypePackId>> mappedGenericPacks,
NotNull<TypeArena> arena
);
std::optional<TypeOrPack> traverse(
TypeId root,
const Path& path,
NotNull<BuiltinTypes> builtinTypes,
NotNull<const DenseHashMap<TypePackId, TypePackId>> mappedGenericPacks,
NotNull<TypeArena> 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<TypeId> traverseForType_DEPRECATED(TypeId root, const Path& path, NotNull<BuiltinTypes> builtinTypes);
/// Traverses a path from a type to its end point, which must be a type. /// Traverses a path from a type to its end point, which must be a type.
/// @param root the entry point of the traversal /// @param root the entry point of the traversal
/// @param path the path to traverse /// @param path the path to traverse
/// @param builtinTypes the built-in types in use (used to acquire the string metatable) /// @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. /// @returns the TypeId at the end of the path, or nullopt if the traversal failed.
std::optional<TypeId> traverseForType(TypeId root, const Path& path, NotNull<BuiltinTypes> builtinTypes); std::optional<TypeId> traverseForType(
TypeId root,
const Path& path,
NotNull<BuiltinTypes> builtinTypes,
NotNull<const DenseHashMap<TypePackId, TypePackId>> mappedGenericPacks,
NotNull<TypeArena> arena
);
/// Traverses a path from a type pack to its end point, which must be a type. /// 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 root the entry point of the traversal
/// @param path the path to traverse /// @param path the path to traverse
/// @param builtinTypes the built-in types in use (used to acquire the string metatable) /// @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. /// @returns the TypeId at the end of the path, or nullopt if the traversal failed.
std::optional<TypeId> traverseForType(TypePackId root, const Path& path, NotNull<BuiltinTypes> builtinTypes); std::optional<TypeId> traverseForType_DEPRECATED(TypePackId root, const Path& path, NotNull<BuiltinTypes> 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<TypeId> traverseForType(
TypePackId root,
const Path& path,
NotNull<BuiltinTypes> builtinTypes,
NotNull<const DenseHashMap<TypePackId, TypePackId>> mappedGenericPacks,
NotNull<TypeArena> 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<TypePackId> traverseForPack_DEPRECATED(TypeId root, const Path& path, NotNull<BuiltinTypes> builtinTypes);
/// Traverses a path from a type to its end point, which must be a type pack. /// 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 root the entry point of the traversal
/// @param path the path to traverse /// @param path the path to traverse
/// @param builtinTypes the built-in types in use (used to acquire the string metatable) /// @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. /// @returns the TypePackId at the end of the path, or nullopt if the traversal failed.
std::optional<TypePackId> traverseForPack(TypeId root, const Path& path, NotNull<BuiltinTypes> builtinTypes); std::optional<TypePackId> traverseForPack(
TypeId root,
const Path& path,
NotNull<BuiltinTypes> builtinTypes,
NotNull<const DenseHashMap<TypePackId, TypePackId>> mappedGenericPacks,
NotNull<TypeArena> arena
);
/// Traverses a path from a type pack to its end point, which must be a type pack. /// 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 root the entry point of the traversal
/// @param path the path to traverse /// @param path the path to traverse
/// @param builtinTypes the built-in types in use (used to acquire the string metatable) /// @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. /// @returns the TypePackId at the end of the path, or nullopt if the traversal failed.
std::optional<TypePackId> traverseForPack(TypePackId root, const Path& path, NotNull<BuiltinTypes> builtinTypes); std::optional<TypePackId> traverseForPack_DEPRECATED(TypePackId root, const Path& path, NotNull<BuiltinTypes> 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<TypePackId> traverseForPack(
TypePackId root,
const Path& path,
NotNull<BuiltinTypes> builtinTypes,
NotNull<const DenseHashMap<TypePackId, TypePackId>> mappedGenericPacks,
NotNull<TypeArena> 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<size_t> traverseForIndex(const Path& path);
} // namespace Luau } // namespace Luau

View file

@ -24,7 +24,7 @@ AutocompleteResult autocomplete(Frontend& frontend, const ModuleName& moduleName
return {}; return {};
ModulePtr module; ModulePtr module;
if (FFlag::LuauSolverV2) if (frontend.getLuauSolverMode() == SolverMode::New)
module = frontend.moduleResolver.getModule(moduleName); module = frontend.moduleResolver.getModule(moduleName);
else else
module = frontend.moduleResolverForAutocomplete.getModule(moduleName); module = frontend.moduleResolverForAutocomplete.getModule(moduleName);
@ -34,7 +34,7 @@ AutocompleteResult autocomplete(Frontend& frontend, const ModuleName& moduleName
NotNull<BuiltinTypes> builtinTypes = frontend.builtinTypes; NotNull<BuiltinTypes> builtinTypes = frontend.builtinTypes;
Scope* globalScope; Scope* globalScope;
if (FFlag::LuauSolverV2) if (frontend.getLuauSolverMode() == SolverMode::New)
globalScope = frontend.globals.globalScope.get(); globalScope = frontend.globals.globalScope.get();
else else
globalScope = frontend.globalsForAutocomplete.globalScope.get(); globalScope = frontend.globalsForAutocomplete.globalScope.get();

View file

@ -162,7 +162,7 @@ static bool checkTypeMatch(
InternalErrorReporter iceReporter; InternalErrorReporter iceReporter;
UnifierSharedState unifierState(&iceReporter); UnifierSharedState unifierState(&iceReporter);
SimplifierPtr simplifier = newSimplifier(NotNull{typeArena}, builtinTypes); 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) if (module.checkedInNewSolver)
{ {
TypeCheckLimits limits; TypeCheckLimits limits;

View file

@ -10,6 +10,7 @@
#include "Luau/Error.h" #include "Luau/Error.h"
#include "Luau/Frontend.h" #include "Luau/Frontend.h"
#include "Luau/InferPolarity.h" #include "Luau/InferPolarity.h"
#include "Luau/Module.h"
#include "Luau/NotNull.h" #include "Luau/NotNull.h"
#include "Luau/Subtyping.h" #include "Luau/Subtyping.h"
#include "Luau/Symbol.h" #include "Luau/Symbol.h"
@ -37,6 +38,7 @@ LUAU_FASTFLAGVARIABLE(LuauStringFormatImprovements)
LUAU_FASTFLAGVARIABLE(LuauMagicFreezeCheckBlocked2) LUAU_FASTFLAGVARIABLE(LuauMagicFreezeCheckBlocked2)
LUAU_FASTFLAGVARIABLE(LuauUpdateSetMetatableTypeSignature) LUAU_FASTFLAGVARIABLE(LuauUpdateSetMetatableTypeSignature)
LUAU_FASTFLAGVARIABLE(LuauUpdateGetMetatableTypeSignature) LUAU_FASTFLAGVARIABLE(LuauUpdateGetMetatableTypeSignature)
LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver)
namespace Luau namespace Luau
{ {
@ -317,7 +319,7 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
if (FFlag::LuauEagerGeneralization4) if (FFlag::LuauEagerGeneralization4)
globalScope = globals.globalScope.get(); globalScope = globals.globalScope.get();
if (FFlag::LuauSolverV2) if (frontend.getLuauSolverMode() == SolverMode::New)
builtinTypeFunctions().addToScope(NotNull{&arena}, NotNull{globals.globalScope.get()}); builtinTypeFunctions().addToScope(NotNull{&arena}, NotNull{globals.globalScope.get()});
LoadDefinitionFileResult loadResult = frontend.loadDefinitionFile( LoadDefinitionFileResult loadResult = frontend.loadDefinitionFile(
@ -390,7 +392,7 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
TypeId genericT = arena.addType(GenericType{globalScope, "T"}); TypeId genericT = arena.addType(GenericType{globalScope, "T"});
if (FFlag::LuauSolverV2 && FFlag::LuauUpdateGetMetatableTypeSignature) if ((frontend.getLuauSolverMode() == SolverMode::New) && FFlag::LuauUpdateGetMetatableTypeSignature)
{ {
// getmetatable : <T>(T) -> getmetatable<T> // getmetatable : <T>(T) -> getmetatable<T>
TypeId getmtReturn = arena.addType(TypeFunctionInstanceType{builtinTypeFunctions().getmetatableFunc, {genericT}}); 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"); 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}); 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<MagicAssert>()); attachMagicFunction(getGlobalBinding(globals, "assert"), std::make_shared<MagicAssert>());
if (FFlag::LuauSolverV2) if (frontend.getLuauSolverMode() == SolverMode::New)
{ {
// declare function assert<T>(value: T, errorMessage: string?): intersect<T, ~(false?)> // declare function assert<T>(value: T, errorMessage: string?): intersect<T, ~(false?)>
TypeId genericT = arena.addType(GenericType{globalScope, "T"}); TypeId genericT = arena.addType(GenericType{globalScope, "T"});
@ -471,7 +473,7 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
if (TableType* ttv = getMutable<TableType>(getGlobalBinding(globals, "table"))) if (TableType* ttv = getMutable<TableType>(getGlobalBinding(globals, "table")))
{ {
if (FFlag::LuauSolverV2) if (frontend.getLuauSolverMode() == SolverMode::New)
{ {
// CLI-114044 - The new solver does not yet support generic tables, // CLI-114044 - The new solver does not yet support generic tables,
// which act, in an odd way, like generics that are constrained to // which act, in an odd way, like generics that are constrained to
@ -1227,7 +1229,7 @@ bool MagicFind::infer(const MagicFunctionCallContext& context)
return true; return true;
} }
TypeId makeStringMetatable(NotNull<BuiltinTypes> builtinTypes) TypeId makeStringMetatable(NotNull<BuiltinTypes> builtinTypes, SolverMode mode)
{ {
NotNull<TypeArena> arena{builtinTypes->arena.get()}; NotNull<TypeArena> arena{builtinTypes->arena.get()};
@ -1243,7 +1245,9 @@ TypeId makeStringMetatable(NotNull<BuiltinTypes> builtinTypes)
const TypePackId oneStringPack = arena->addTypePack({stringType}); const TypePackId oneStringPack = arena->addTypePack({stringType});
const TypePackId anyTypePack = builtinTypes->anyTypePack; 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 emptyPack = arena->addTypePack({});
const TypePackId stringVariadicList = arena->addTypePack(TypePackVar{VariadicTypePack{stringType}}); const TypePackId stringVariadicList = arena->addTypePack(TypePackVar{VariadicTypePack{stringType}});
const TypePackId numberVariadicList = arena->addTypePack(TypePackVar{VariadicTypePack{numberType}}); const TypePackId numberVariadicList = arena->addTypePack(TypePackVar{VariadicTypePack{numberType}});

View file

@ -15,12 +15,63 @@ Constraint::Constraint(NotNull<Scope> scope, const Location& location, Constrain
{ {
} }
struct ReferenceCountInitializer : TypeOnceVisitor struct ReferenceCountInitializer_DEPRECATED : TypeOnceVisitor
{ {
DenseHashSet<TypeId>* result; DenseHashSet<TypeId>* result;
bool traverseIntoTypeFunctions = true; bool traverseIntoTypeFunctions = true;
explicit ReferenceCountInitializer(DenseHashSet<TypeId>* result) explicit ReferenceCountInitializer_DEPRECATED(DenseHashSet<TypeId>* 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<TypeIds> result;
bool traverseIntoTypeFunctions = true;
explicit ReferenceCountInitializer(NotNull<TypeIds> result)
: result(result) : result(result)
{ {
} }
@ -78,7 +129,7 @@ bool isReferenceCountedType(const TypeId typ)
return get<FreeType>(typ) || get<BlockedType>(typ) || get<PendingExpansionType>(typ); return get<FreeType>(typ) || get<BlockedType>(typ) || get<PendingExpansionType>(typ);
} }
DenseHashSet<TypeId> Constraint::getMaybeMutatedFreeTypes() const DenseHashSet<TypeId> Constraint::getMaybeMutatedFreeTypes_DEPRECATED() const
{ {
// For the purpose of this function and reference counting in general, we are only considering // 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 // mutations that affect the _bounds_ of the free type, and not something that may bind the free
@ -86,7 +137,103 @@ DenseHashSet<TypeId> Constraint::getMaybeMutatedFreeTypes() const
// contribution to the output set here. // contribution to the output set here.
DenseHashSet<TypeId> types{{}}; DenseHashSet<TypeId> types{{}};
ReferenceCountInitializer rci{&types}; ReferenceCountInitializer_DEPRECATED rci{&types};
if (auto ec = get<EqualityConstraint>(*this))
{
rci.traverse(ec->resultType);
// `EqualityConstraints` should not mutate `assignmentType`.
}
else if (auto sc = get<SubtypeConstraint>(*this))
{
rci.traverse(sc->subType);
rci.traverse(sc->superType);
}
else if (auto psc = get<PackSubtypeConstraint>(*this))
{
rci.traverse(psc->subPack);
rci.traverse(psc->superPack);
}
else if (auto itc = get<IterableConstraint>(*this))
{
for (TypeId ty : itc->variables)
rci.traverse(ty);
// `IterableConstraints` should not mutate `iterator`.
}
else if (auto nc = get<NameConstraint>(*this))
{
rci.traverse(nc->namedType);
}
else if (auto taec = get<TypeAliasExpansionConstraint>(*this))
{
rci.traverse(taec->target);
}
else if (auto fchc = get<FunctionCheckConstraint>(*this))
{
rci.traverse(fchc->argsPack);
}
else if (auto fcc = get<FunctionCallConstraint>(*this); fcc && FFlag::LuauEagerGeneralization4)
{
rci.traverseIntoTypeFunctions = false;
rci.traverse(fcc->fn);
rci.traverse(fcc->argsPack);
rci.traverseIntoTypeFunctions = true;
}
else if (auto ptc = get<PrimitiveTypeConstraint>(*this))
{
rci.traverse(ptc->freeType);
}
else if (auto hpc = get<HasPropConstraint>(*this))
{
rci.traverse(hpc->resultType);
if (FFlag::LuauEagerGeneralization4)
rci.traverse(hpc->subjectType);
}
else if (auto hic = get<HasIndexerConstraint>(*this))
{
if (FFlag::LuauEagerGeneralization4)
rci.traverse(hic->subjectType);
rci.traverse(hic->resultType);
// `HasIndexerConstraint` should not mutate `indexType`.
}
else if (auto apc = get<AssignPropConstraint>(*this))
{
rci.traverse(apc->lhsType);
rci.traverse(apc->rhsType);
}
else if (auto aic = get<AssignIndexConstraint>(*this))
{
rci.traverse(aic->lhsType);
rci.traverse(aic->indexType);
rci.traverse(aic->rhsType);
}
else if (auto uc = get<UnpackConstraint>(*this))
{
for (TypeId ty : uc->resultPack)
rci.traverse(ty);
// `UnpackConstraint` should not mutate `sourcePack`.
}
else if (auto rpc = get<ReducePackConstraint>(*this))
{
rci.traverse(rpc->tp);
}
else if (auto tcc = get<TableCheckConstraint>(*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<EqualityConstraint>(*this)) if (auto ec = get<EqualityConstraint>(*this))
{ {

View file

@ -33,14 +33,15 @@ LUAU_FASTFLAGVARIABLE(DebugLuauLogBindings)
LUAU_FASTINTVARIABLE(LuauSolverRecursionLimit, 500) LUAU_FASTINTVARIABLE(LuauSolverRecursionLimit, 500)
LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification) LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch)
LUAU_FASTFLAGVARIABLE(LuauAddCallConstraintForIterableFunctions) LUAU_FASTFLAGVARIABLE(LuauAddCallConstraintForIterableFunctions)
LUAU_FASTFLAGVARIABLE(LuauGuardAgainstMalformedTypeAliasExpansion2) LUAU_FASTFLAGVARIABLE(LuauGuardAgainstMalformedTypeAliasExpansion2)
LUAU_FASTFLAGVARIABLE(LuauInsertErrorTypesIntoIndexerResult) LUAU_FASTFLAGVARIABLE(LuauInsertErrorTypesIntoIndexerResult)
LUAU_FASTFLAGVARIABLE(LuauClipVariadicAnysFromArgsToGenericFuncs2)
LUAU_FASTFLAGVARIABLE(LuauAvoidGenericsLeakingDuringFunctionCallCheck) LUAU_FASTFLAGVARIABLE(LuauAvoidGenericsLeakingDuringFunctionCallCheck)
LUAU_FASTFLAGVARIABLE(LuauMissingFollowInAssignIndexConstraint) LUAU_FASTFLAGVARIABLE(LuauMissingFollowInAssignIndexConstraint)
LUAU_FASTFLAGVARIABLE(LuauRemoveTypeCallsForReadWriteProps) LUAU_FASTFLAGVARIABLE(LuauRemoveTypeCallsForReadWriteProps)
LUAU_FASTFLAGVARIABLE(LuauTableLiteralSubtypeCheckFunctionCalls) LUAU_FASTFLAGVARIABLE(LuauTableLiteralSubtypeCheckFunctionCalls)
LUAU_FASTFLAGVARIABLE(LuauUseOrderedTypeSetsInConstraints)
namespace Luau namespace Luau
{ {
@ -418,10 +419,21 @@ void ConstraintSolver::run()
// Free types that have no constraints at all can be generalized right away. // Free types that have no constraints at all can be generalized right away.
if (FFlag::LuauEagerGeneralization4) if (FFlag::LuauEagerGeneralization4)
{ {
for (TypeId ty : constraintSet.freeTypes) if (FFlag::LuauUseOrderedTypeSetsInConstraints)
{ {
if (auto it = mutatedFreeTypeToConstraint.find(ty); it == mutatedFreeTypeToConstraint.end() || it->second.empty()) for (TypeId ty : constraintSet.freeTypes)
generalizeOneType(ty); {
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); unblock(c);
unsolvedConstraints.erase(unsolvedConstraints.begin() + ptrdiff_t(i)); unsolvedConstraints.erase(unsolvedConstraints.begin() + ptrdiff_t(i));
const auto maybeMutated = maybeMutatedFreeTypes.find(c); if (FFlag::LuauUseOrderedTypeSetsInConstraints)
if (maybeMutated != maybeMutatedFreeTypes.end())
{ {
DenseHashSet<TypeId> seen{nullptr}; if (const auto maybeMutated = maybeMutatedFreeTypes.find(c); maybeMutated != maybeMutatedFreeTypes.end())
for (auto ty : maybeMutated->second)
{ {
// There is a high chance that this type has been rebound DenseHashSet<TypeId> seen{nullptr};
// across blocked types, rebound free types, pending for (auto ty : maybeMutated->second)
// expansion types, etc, so we need to follow it.
ty = follow(ty);
if (FFlag::LuauEagerGeneralization4)
{ {
if (seen.contains(ty)) // There is a high chance that this type has been rebound
continue; // across blocked types, rebound free types, pending
seen.insert(ty); // 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<TypeId> 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) if (logger)
{ {
@ -664,27 +716,57 @@ struct TypeSearcher : TypeVisitor
void ConstraintSolver::initFreeTypeTracking() void ConstraintSolver::initFreeTypeTracking()
{ {
for (NotNull<Constraint> c : this->constraints) if (FFlag::LuauUseOrderedTypeSetsInConstraints)
{ {
unsolvedConstraints.emplace_back(c); for (auto c : this->constraints)
auto maybeMutatedTypesPerConstraint = c->getMaybeMutatedFreeTypes();
for (auto ty : maybeMutatedTypesPerConstraint)
{ {
auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0); unsolvedConstraints.emplace_back(c);
refCount += 1;
if (FFlag::LuauEagerGeneralization4) auto maybeMutatedTypesPerConstraint = c->getMaybeMutatedFreeTypes();
for (auto ty : maybeMutatedTypesPerConstraint)
{ {
auto [it, fresh] = mutatedFreeTypeToConstraint.try_emplace(ty, nullptr); auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0);
it->second.insert(c.get()); refCount += 1;
if (FFlag::LuauEagerGeneralization4)
{
auto [it, fresh] = mutatedFreeTypeToConstraint.try_emplace(ty);
it->second.insert(c.get());
}
}
maybeMutatedFreeTypes.emplace(c, maybeMutatedTypesPerConstraint);
for (NotNull<const Constraint> dep : c->dependencies)
{
block(dep, c);
} }
} }
maybeMutatedFreeTypes.emplace(c, maybeMutatedTypesPerConstraint); }
else
{
for (NotNull<const Constraint> dep : c->dependencies) for (NotNull<Constraint> 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<const Constraint> dep : c->dependencies)
{
block(dep, c);
}
} }
} }
} }
@ -1538,51 +1620,6 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
if (c.result != result) if (c.result != result)
emplaceTypePack<BoundTypePack>(asMutable(c.result), result); emplaceTypePack<BoundTypePack>(asMutable(c.result), result);
if (FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2)
{
FunctionType* inferredFuncTy = getMutable<FunctionType>(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<FunctionType>(follow(t));
if (!f || !f->argTypes)
{
clippedArgs.head.push_back(t);
continue;
}
const TypePack* argTp = get<TypePack>(follow(f->argTypes));
if (!argTp || !argTp->tail)
{
clippedArgs.head.push_back(t);
continue;
}
if (const VariadicTypePack* argTpTail = get<VariadicTypePack>(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<FunctionType>(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) for (const auto& [expanded, additions] : u2.expandedFreeTypes)
@ -1922,7 +1959,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<con
// Generic types are skipped over entirely, for now. // Generic types are skipped over entirely, for now.
if (containsGenerics.hasGeneric(expectedArgTy)) if (containsGenerics.hasGeneric(expectedArgTy))
{ {
if (!FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2 || !lambdaTy || !lambdaTy->argTypes) if (!lambdaTy || !lambdaTy->argTypes)
continue; continue;
const TypePack* argTp = get<TypePack>(follow(lambdaTy->argTypes)); const TypePack* argTp = get<TypePack>(follow(lambdaTy->argTypes));
@ -3630,7 +3667,11 @@ bool ConstraintSolver::isBlocked(TypeId ty) const
ty = follow(ty); ty = follow(ty);
if (auto tfit = get<TypeFunctionInstanceType>(ty)) if (auto tfit = get<TypeFunctionInstanceType>(ty))
{
if (FFlag::LuauStuckTypeFunctionsStillDispatch && tfit->state != TypeFunctionInstanceState::Unsolved)
return false;
return uninhabitedTypeFunctions.contains(ty) == false; return uninhabitedTypeFunctions.contains(ty) == false;
}
return nullptr != get<BlockedType>(ty) || nullptr != get<PendingExpansionType>(ty); return nullptr != get<BlockedType>(ty) || nullptr != get<PendingExpansionType>(ty);
} }
@ -3739,21 +3780,42 @@ void ConstraintSolver::shiftReferences(TypeId source, TypeId target)
if (FFlag::LuauEagerGeneralization4) if (FFlag::LuauEagerGeneralization4)
{ {
auto it = mutatedFreeTypeToConstraint.find(source); if (FFlag::LuauUseOrderedTypeSetsInConstraints)
if (it != mutatedFreeTypeToConstraint.end())
{ {
const DenseHashSet<const Constraint*>& constraintsAffectedBySource = it->second;
auto [it2, fresh2] = mutatedFreeTypeToConstraint.try_emplace(target, DenseHashSet<const Constraint*>{nullptr}); if (auto it = mutatedFreeTypeToConstraint.find(source); it != mutatedFreeTypeToConstraint.end())
DenseHashSet<const Constraint*>& constraintsAffectedByTarget = it2->second;
// auto [it2, fresh] = mutatedFreeTypeToConstraint.try_emplace(target, DenseHashSet<const Constraint*>{nullptr});
for (const Constraint* constraint : constraintsAffectedBySource)
{ {
constraintsAffectedByTarget.insert(constraint); const OrderedSet<const Constraint*>& constraintsAffectedBySource = it->second;
auto [it2, fresh2] = mutatedFreeTypeToConstraint.try_emplace(target);
auto [it3, fresh3] = maybeMutatedFreeTypes.try_emplace(NotNull{constraint}, DenseHashSet<TypeId>{nullptr}); OrderedSet<const Constraint*>& constraintsAffectedByTarget = it2->second;
it3->second.insert(target);
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<const Constraint*>& constraintsAffectedBySource = it->second;
auto [it2, fresh2] = mutatedFreeTypeToConstraint_DEPRECATED.try_emplace(target, DenseHashSet<const Constraint*>{nullptr});
DenseHashSet<const Constraint*>& constraintsAffectedByTarget = it2->second;
// auto [it2, fresh] = mutatedFreeTypeToConstraint.try_emplace(target, DenseHashSet<const Constraint*>{nullptr});
for (const Constraint* constraint : constraintsAffectedBySource)
{
constraintsAffectedByTarget.insert(constraint);
auto [it3, fresh3] = maybeMutatedFreeTypes_DEPRECATED.try_emplace(NotNull{constraint}, DenseHashSet<TypeId>{nullptr});
it3->second.insert(target);
}
} }
} }
} }

View file

@ -22,6 +22,7 @@ LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps) LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
LUAU_FASTFLAGVARIABLE(LuauBetterCannotCallFunctionPrimitive) LUAU_FASTFLAGVARIABLE(LuauBetterCannotCallFunctionPrimitive)
LUAU_FASTFLAG(LuauSolverAgnosticStringification)
static std::string wrongNumberOfArgsString( static std::string wrongNumberOfArgsString(
size_t expectedCount, size_t expectedCount,
@ -420,10 +421,17 @@ struct ErrorConverter
auto it = mtt->props.find("__call"); auto it = mtt->props.find("__call");
if (it != mtt->props.end()) if (it != mtt->props.end())
{ {
if (FFlag::LuauSolverV2 && FFlag::LuauRemoveTypeCallsForReadWriteProps) if (FFlag::LuauSolverAgnosticStringification)
{
return it->second.readTy; return it->second.readTy;
}
else else
return it->second.type_DEPRECATED(); {
if (FFlag::LuauSolverV2 && FFlag::LuauRemoveTypeCallsForReadWriteProps)
return it->second.readTy;
else
return it->second.type_DEPRECATED();
}
} }
else else
return std::nullopt; 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 " + return "Different number of generic type pack parameters: subtype had " + std::to_string(e.subTyGenericPackCount) + ", supertype had " +
std::to_string(e.superTyGenericPackCount) + "."; 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 struct InvalidNameChecker
@ -1275,6 +1288,11 @@ bool GenericTypePackCountMismatch::operator==(const GenericTypePackCountMismatch
return subTyGenericPackCount == rhs.subTyGenericPackCount && superTyGenericPackCount == rhs.superTyGenericPackCount; return subTyGenericPackCount == rhs.subTyGenericPackCount && superTyGenericPackCount == rhs.superTyGenericPackCount;
} }
bool MultipleNonviableOverloads::operator==(const MultipleNonviableOverloads& rhs) const
{
return attemptedArgCount == rhs.attemptedArgCount;
}
std::string toString(const TypeError& error) std::string toString(const TypeError& error)
{ {
return toString(error, TypeErrorToStringOptions{}); return toString(error, TypeErrorToStringOptions{});
@ -1495,6 +1513,9 @@ void copyError(T& e, TypeArena& destArena, CloneState& cloneState)
else if constexpr (std::is_same_v<T, GenericTypePackCountMismatch>) else if constexpr (std::is_same_v<T, GenericTypePackCountMismatch>)
{ {
} }
else if constexpr (std::is_same_v<T, MultipleNonviableOverloads>)
{
}
else else
static_assert(always_false_v<T>, "Non-exhaustive type switch"); static_assert(always_false_v<T>, "Non-exhaustive type switch");
} }

View file

@ -11,6 +11,7 @@
#include "Luau/Parser.h" #include "Luau/Parser.h"
#include "Luau/ParseOptions.h" #include "Luau/ParseOptions.h"
#include "Luau/Module.h" #include "Luau/Module.h"
#include "Luau/RequireTracer.h"
#include "Luau/TimeTrace.h" #include "Luau/TimeTrace.h"
#include "Luau/UnifierSharedState.h" #include "Luau/UnifierSharedState.h"
#include "Luau/TypeFunction.h" #include "Luau/TypeFunction.h"
@ -37,12 +38,15 @@ LUAU_FASTFLAGVARIABLE(LuauGlobalVariableModuleIsolation)
LUAU_FASTFLAGVARIABLE(LuauFragmentAutocompleteIfRecommendations) LUAU_FASTFLAGVARIABLE(LuauFragmentAutocompleteIfRecommendations)
LUAU_FASTFLAG(LuauExpectedTypeVisitor) LUAU_FASTFLAG(LuauExpectedTypeVisitor)
LUAU_FASTFLAGVARIABLE(LuauPopulateRefinedTypesInFragmentFromOldSolver) LUAU_FASTFLAGVARIABLE(LuauPopulateRefinedTypesInFragmentFromOldSolver)
LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver)
LUAU_FASTFLAGVARIABLE(LuauFragmentRequiresCanBeResolvedToAModule)
namespace Luau namespace Luau
{ {
static std::pair<size_t, size_t> getDocumentOffsets(std::string_view src, const Position& startPos, const Position& endPos); static std::pair<size_t, size_t> getDocumentOffsets(std::string_view src, const Position& startPos, const Position& endPos);
// when typing a function partially, get the span of the first line // 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 // e.g. local function fn() : ... - typically we want to provide autocomplete results if you're
// editing type annotations in this range // editing type annotations in this range
@ -761,7 +765,7 @@ void cloneTypesFromFragment(
destScope->returnType = Luau::cloneIncremental(staleScope->returnType, *destArena, cloneState, destScope); destScope->returnType = Luau::cloneIncremental(staleScope->returnType, *destArena, cloneState, destScope);
} }
static FrontendModuleResolver& getModuleResolver(Frontend& frontend, std::optional<FrontendOptions> options) static FrontendModuleResolver& getModuleResolver_DEPRECATED(Frontend& frontend, std::optional<FrontendOptions> options)
{ {
if (FFlag::LuauSolverV2 || !options) if (FFlag::LuauSolverV2 || !options)
return frontend.moduleResolver; return frontend.moduleResolver;
@ -769,6 +773,14 @@ static FrontendModuleResolver& getModuleResolver(Frontend& frontend, std::option
return options->forAutocomplete ? frontend.moduleResolverForAutocomplete : frontend.moduleResolver; return options->forAutocomplete ? frontend.moduleResolverForAutocomplete : frontend.moduleResolver;
} }
static FrontendModuleResolver& getModuleResolver(Frontend& frontend, std::optional<FrontendOptions> 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) bool statIsBeforePos(const AstNode* stat, const Position& cursorPos)
{ {
return (stat->location.begin < cursorPos); return (stat->location.begin < cursorPos);
@ -1066,6 +1078,42 @@ static void reportFragmentString(IFragmentAutocompleteReporter* reporter, std::s
reporter->reportFragmentString(fragment); reporter->reportFragmentString(fragment);
} }
struct ScopedExit
{
public:
explicit ScopedExit(std::function<void()> 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<void()> func;
};
FragmentTypeCheckResult typecheckFragment_( FragmentTypeCheckResult typecheckFragment_(
Frontend& frontend, Frontend& frontend,
AstStatBlock* root, AstStatBlock* root,
@ -1078,7 +1126,6 @@ FragmentTypeCheckResult typecheckFragment_(
) )
{ {
LUAU_TIMETRACE_SCOPE("Luau::typecheckFragment_", "FragmentAutocomplete"); LUAU_TIMETRACE_SCOPE("Luau::typecheckFragment_", "FragmentAutocomplete");
freeze(stale->internalTypes); freeze(stale->internalTypes);
freeze(stale->interfaceTypes); freeze(stale->interfaceTypes);
ModulePtr incrementalModule = std::make_shared<Module>(); ModulePtr incrementalModule = std::make_shared<Module>();
@ -1107,7 +1154,7 @@ FragmentTypeCheckResult typecheckFragment_(
unifierState.counters.iterationLimit = limits.unifierIterationLimit.value_or(FInt::LuauTypeInferIterationLimit); unifierState.counters.iterationLimit = limits.unifierIterationLimit.value_or(FInt::LuauTypeInferIterationLimit);
/// Initialize the normalizer /// 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 /// User defined type functions runtime
TypeFunctionRuntime typeFunctionRuntime(iceHandler, NotNull{&limits}); TypeFunctionRuntime typeFunctionRuntime(iceHandler, NotNull{&limits});
@ -1120,6 +1167,17 @@ FragmentTypeCheckResult typecheckFragment_(
SimplifierPtr simplifier = newSimplifier(NotNull{&incrementalModule->internalTypes}, frontend.builtinTypes); 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); FrontendModuleResolver& resolver = getModuleResolver(frontend, opts);
std::shared_ptr<Scope> freshChildOfNearestScope = std::make_shared<Scope>(nullptr); std::shared_ptr<Scope> freshChildOfNearestScope = std::make_shared<Scope>(nullptr);
/// Contraint Generator /// Contraint Generator
@ -1218,7 +1276,164 @@ FragmentTypeCheckResult typecheckFragment_(
// In frontend we would forbid internal types // In frontend we would forbid internal types
// because this is just for autocomplete, we don't actually care // 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 // 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<Allocator> astAllocator,
const FrontendOptions& opts,
IFragmentAutocompleteReporter* reporter
)
{
LUAU_TIMETRACE_SCOPE("Luau::typecheckFragment_", "FragmentAutocomplete");
freeze(stale->internalTypes);
freeze(stale->interfaceTypes);
ModulePtr incrementalModule = std::make_shared<Module>();
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<InternalErrorReporter> 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<Scope> freshChildOfNearestScope = std::make_shared<Scope>(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<Scope>(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->internalTypes);
freeze(incrementalModule->interfaceTypes); freeze(incrementalModule->interfaceTypes);
freshChildOfNearestScope->parent = closestScope; freshChildOfNearestScope->parent = closestScope;
@ -1243,7 +1458,8 @@ std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
if (!frontend.allModuleDependenciesValid(moduleName, opts && opts->forAutocomplete)) if (!frontend.allModuleDependenciesValid(moduleName, opts && opts->forAutocomplete))
return {FragmentTypeCheckStatus::SkipAutocomplete, {}}; return {FragmentTypeCheckStatus::SkipAutocomplete, {}};
FrontendModuleResolver& resolver = getModuleResolver(frontend, opts); FrontendModuleResolver& resolver =
FFlag::LuauUseWorkspacePropToChooseSolver ? getModuleResolver(frontend, opts) : getModuleResolver_DEPRECATED(frontend, opts);
ModulePtr module = resolver.getModule(moduleName); ModulePtr module = resolver.getModule(moduleName);
if (!module) if (!module)
{ {
@ -1268,7 +1484,11 @@ std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
const ScopePtr& closestScope = FFlag::LuauBetterScopeSelection ? findClosestScope(module, parseResult.scopePos) const ScopePtr& closestScope = FFlag::LuauBetterScopeSelection ? findClosestScope(module, parseResult.scopePos)
: findClosestScope_DEPRECATED(module, parseResult.nearestStatement); : findClosestScope_DEPRECATED(module, parseResult.nearestStatement);
FragmentTypeCheckResult result = 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); result.ancestry = std::move(parseResult.ancestry);
reportFragmentString(reporter, tryParse->fragmentToParse); reportFragmentString(reporter, tryParse->fragmentToParse);
return {FragmentTypeCheckStatus::Success, result}; return {FragmentTypeCheckStatus::Success, result};

View file

@ -388,30 +388,31 @@ double getTimestamp()
} // namespace } // namespace
Frontend::Frontend(FileResolver* fileResolver, ConfigResolver* configResolver, const FrontendOptions& options) Frontend::Frontend(FileResolver* fileResolver, ConfigResolver* configResolver, const FrontendOptions& options)
: builtinTypes(NotNull{&builtinTypes_}) : useNewLuauSolver(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old)
, builtinTypes(NotNull{&builtinTypes_})
, fileResolver(fileResolver) , fileResolver(fileResolver)
, moduleResolver(this) , moduleResolver(this)
, moduleResolverForAutocomplete(this) , moduleResolverForAutocomplete(this)
, globals(builtinTypes) , globals(builtinTypes, getLuauSolverMode())
, globalsForAutocomplete(builtinTypes) , globalsForAutocomplete(builtinTypes, getLuauSolverMode())
, configResolver(configResolver) , configResolver(configResolver)
, options(options) , 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(); if (FFlag::LuauUseWorkspacePropToChooseSolver)
} return useNewLuauSolver.load();
else if (FFlag::LuauSolverV2)
bool Frontend::getLuauSolverSelectionFlagged() const return SolverMode::New;
{ else
return FFlag::LuauUseWorkspacePropToChooseSolver ? getLuauSolverSelection() : FFlag::LuauSolverV2; return SolverMode::Old;
} }
void Frontend::parse(const ModuleName& name) void Frontend::parse(const ModuleName& name)
@ -464,7 +465,7 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
LUAU_TIMETRACE_ARGUMENT("name", name.c_str()); LUAU_TIMETRACE_ARGUMENT("name", name.c_str());
FrontendOptions frontendOptions = optionOverride.value_or(options); FrontendOptions frontendOptions = optionOverride.value_or(options);
if (FFlag::LuauSolverV2) if (getLuauSolverMode() == SolverMode::New)
frontendOptions.forAutocomplete = false; frontendOptions.forAutocomplete = false;
if (std::optional<CheckResult> result = getCheckResult(name, true, frontendOptions.forAutocomplete)) if (std::optional<CheckResult> result = getCheckResult(name, true, frontendOptions.forAutocomplete))
@ -524,7 +525,7 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
) )
{ {
FrontendOptions frontendOptions = optionOverride.value_or(options); FrontendOptions frontendOptions = optionOverride.value_or(options);
if (FFlag::LuauSolverV2) if (getLuauSolverMode() == SolverMode::New)
frontendOptions.forAutocomplete = false; 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 // 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<ModuleName> Frontend::checkQueuedModules(
std::optional<CheckResult> Frontend::getCheckResult(const ModuleName& name, bool accumulateNested, bool forAutocomplete) std::optional<CheckResult> Frontend::getCheckResult(const ModuleName& name, bool accumulateNested, bool forAutocomplete)
{ {
if (FFlag::LuauSolverV2) if (getLuauSolverMode() == SolverMode::New)
forAutocomplete = false; forAutocomplete = false;
auto it = sourceNodes.find(name); auto it = sourceNodes.find(name);
@ -1039,7 +1040,7 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item)
if (item.options.customModuleCheck) if (item.options.customModuleCheck)
item.options.customModuleCheck(sourceModule, *module); item.options.customModuleCheck(sourceModule, *module);
if (FFlag::LuauSolverV2 && mode == Mode::NoCheck) if ((getLuauSolverMode() == SolverMode::New) && mode == Mode::NoCheck)
module->errors.clear(); module->errors.clear();
if (item.options.runLintChecks) if (item.options.runLintChecks)
@ -1461,7 +1462,7 @@ ModulePtr check(
unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit; unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit;
unifierState.counters.iterationLimit = limits.unifierIterationLimit.value_or(FInt::LuauTypeInferIterationLimit); 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); SimplifierPtr simplifier = newSimplifier(NotNull{&result->internalTypes}, builtinTypes);
TypeFunctionRuntime typeFunctionRuntime{iceHandler, NotNull{&limits}}; TypeFunctionRuntime typeFunctionRuntime{iceHandler, NotNull{&limits}};
@ -1728,7 +1729,7 @@ ModulePtr Frontend::check(
TypeCheckLimits typeCheckLimits TypeCheckLimits typeCheckLimits
) )
{ {
if (FFlag::LuauSolverV2) if (getLuauSolverMode() == SolverMode::New)
{ {
auto prepareModuleScopeWrap = [this, forAutocomplete](const ModuleName& name, const ScopePtr& scope) auto prepareModuleScopeWrap = [this, forAutocomplete](const ModuleName& name, const ScopePtr& scope)
{ {
@ -2057,4 +2058,10 @@ void Frontend::clear()
requireTrace.clear(); requireTrace.clear();
} }
void Frontend::clearBuiltinEnvironments()
{
environments.clear();
builtinDefinitions.clear();
}
} // namespace Luau } // namespace Luau

View file

@ -5,6 +5,7 @@
#include "Luau/Common.h" #include "Luau/Common.h"
#include "Luau/DenseHash.h" #include "Luau/DenseHash.h"
#include "Luau/InsertionOrderedMap.h" #include "Luau/InsertionOrderedMap.h"
#include "Luau/OrderedSet.h"
#include "Luau/Polarity.h" #include "Luau/Polarity.h"
#include "Luau/Scope.h" #include "Luau/Scope.h"
#include "Luau/ToString.h" #include "Luau/ToString.h"
@ -23,68 +24,6 @@ LUAU_FASTFLAGVARIABLE(LuauGeneralizationCannotMutateAcrossModules)
namespace Luau namespace Luau
{ {
namespace
{
template<typename T>
struct OrderedSet
{
using iterator = typename std::vector<T>::iterator;
using const_iterator = typename std::vector<T>::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<T> takeVector()
{
elementSet.clear();
return std::move(elements);
}
private:
std::vector<T> elements;
DenseHashSet<T> elementSet{nullptr};
};
} // namespace
struct MutatingGeneralizer : TypeOnceVisitor struct MutatingGeneralizer : TypeOnceVisitor
{ {
NotNull<TypeArena> arena; NotNull<TypeArena> arena;

View file

@ -5,8 +5,9 @@
namespace Luau namespace Luau
{ {
GlobalTypes::GlobalTypes(NotNull<BuiltinTypes> builtinTypes) GlobalTypes::GlobalTypes(NotNull<BuiltinTypes> builtinTypes, SolverMode mode)
: builtinTypes(builtinTypes) : builtinTypes(builtinTypes)
, mode(mode)
{ {
globalScope = std::make_shared<Scope>(globalTypes.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}})); globalScope = std::make_shared<Scope>(globalTypes.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}}));
globalTypeFunctionScope = std::make_shared<Scope>(globalTypes.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}})); globalTypeFunctionScope = std::make_shared<Scope>(globalTypes.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}}));
@ -22,7 +23,7 @@ GlobalTypes::GlobalTypes(NotNull<BuiltinTypes> builtinTypes)
globalScope->addBuiltinTypeBinding("never", TypeFun{{}, builtinTypes->neverType}); globalScope->addBuiltinTypeBinding("never", TypeFun{{}, builtinTypes->neverType});
unfreeze(*builtinTypes->arena); unfreeze(*builtinTypes->arena);
TypeId stringMetatableTy = makeStringMetatable(builtinTypes); TypeId stringMetatableTy = makeStringMetatable(builtinTypes, mode);
asMutable(builtinTypes->stringType)->ty.emplace<PrimitiveType>(PrimitiveType::String, stringMetatableTy); asMutable(builtinTypes->stringType)->ty.emplace<PrimitiveType>(PrimitiveType::String, stringMetatableTy);
persist(stringMetatableTy); persist(stringMetatableTy);
freeze(*builtinTypes->arena); freeze(*builtinTypes->arena);

View file

@ -265,6 +265,8 @@ static void errorToString(std::ostream& stream, const T& err)
stream << "GenericTypePackCountMismatch { subTyGenericPackCount = " << err.subTyGenericPackCount stream << "GenericTypePackCountMismatch { subTyGenericPackCount = " << err.subTyGenericPackCount
<< ", superTyGenericPackCount = " << err.superTyGenericPackCount << " }"; << ", superTyGenericPackCount = " << err.superTyGenericPackCount << " }";
} }
else if constexpr (std::is_same_v<T, MultipleNonviableOverloads>)
stream << "MultipleNonviableOverloads { attemptedArgCount = " << err.attemptedArgCount << " }";
else else
static_assert(always_false_v<T>, "Non-exhaustive type switch"); static_assert(always_false_v<T>, "Non-exhaustive type switch");
} }

View file

@ -192,7 +192,7 @@ struct NonStrictTypeChecker
, ice(ice) , ice(ice)
, arena(arena) , arena(arena)
, module(module) , 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} , subtyping{builtinTypes, arena, simplifier, NotNull(&normalizer), typeFunctionRuntime, ice}
, dfg(dfg) , dfg(dfg)
, limits(limits) , limits(limits)

View file

@ -24,6 +24,7 @@ LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauNormalizationIntersectTablesPreservesExternTypes) LUAU_FASTFLAGVARIABLE(LuauNormalizationIntersectTablesPreservesExternTypes)
LUAU_FASTFLAGVARIABLE(LuauNormalizationReorderFreeTypeIntersect) LUAU_FASTFLAGVARIABLE(LuauNormalizationReorderFreeTypeIntersect)
LUAU_FASTFLAG(LuauRefineTablesWithReadType) LUAU_FASTFLAG(LuauRefineTablesWithReadType)
LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver)
namespace Luau namespace Luau
{ {
@ -392,7 +393,7 @@ NormalizationResult Normalizer::isInhabited(TypeId ty, Set<TypeId>& seen)
{ {
for (const auto& [_, prop] : ttv->props) 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, // 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. // 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 #endif
} }
Normalizer::Normalizer(TypeArena* arena, NotNull<BuiltinTypes> builtinTypes, NotNull<UnifierSharedState> sharedState, bool cacheInhabitance) Normalizer::Normalizer(
TypeArena* arena,
NotNull<BuiltinTypes> builtinTypes,
NotNull<UnifierSharedState> sharedState,
SolverMode solverMode,
bool cacheInhabitance
)
: arena(arena) : arena(arena)
, builtinTypes(builtinTypes) , builtinTypes(builtinTypes)
, sharedState(sharedState) , sharedState(sharedState)
, cacheInhabitance(cacheInhabitance) , cacheInhabitance(cacheInhabitance)
, solverMode(solverMode)
{ {
} }
@ -1583,6 +1591,11 @@ bool Normalizer::withinResourceLimits()
return true; return true;
} }
bool Normalizer::useNewLuauSolver() const
{
return FFlag::LuauUseWorkspacePropToChooseSolver ? (solverMode == SolverMode::New) : FFlag::LuauSolverV2;
}
NormalizationResult Normalizer::intersectNormalWithNegationTy(TypeId toNegate, NormalizedType& intersect) NormalizationResult Normalizer::intersectNormalWithNegationTy(TypeId toNegate, NormalizedType& intersect)
{ {
@ -2457,7 +2470,7 @@ std::optional<TypeId> Normalizer::intersectionOfTables(TypeId here, TypeId there
{ {
const auto& [_name, tprop] = *tfound; const auto& [_name, tprop] = *tfound;
// TODO: variance issues here, which can't be fixed until we have read/write property types // 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()) if (hprop.readTy.has_value())
{ {
@ -3041,7 +3054,7 @@ NormalizationResult Normalizer::intersectNormalWithTy(
} }
else if (get<TableType>(there) || get<MetatableType>(there)) else if (get<TableType>(there) || get<MetatableType>(there))
{ {
if (FFlag::LuauSolverV2 && FFlag::LuauNormalizationIntersectTablesPreservesExternTypes) if (useNewLuauSolver() && FFlag::LuauNormalizationIntersectTablesPreservesExternTypes)
{ {
NormalizedExternType externTypes = std::move(here.externTypes); NormalizedExternType externTypes = std::move(here.externTypes);
TypeIds tables = std::move(here.tables); TypeIds tables = std::move(here.tables);
@ -3323,7 +3336,7 @@ TypeId Normalizer::typeFromNormal(const NormalizedType& norm)
if (!get<NeverType>(norm.buffers)) if (!get<NeverType>(norm.buffers))
result.push_back(builtinTypes->bufferType); result.push_back(builtinTypes->bufferType);
if (FFlag::LuauSolverV2) if (useNewLuauSolver())
{ {
result.reserve(result.size() + norm.tables.size()); result.reserve(result.size() + norm.tables.size());
for (auto table : norm.tables) for (auto table : norm.tables)
@ -3361,30 +3374,50 @@ bool isSubtype(
NotNull<Scope> scope, NotNull<Scope> scope,
NotNull<BuiltinTypes> builtinTypes, NotNull<BuiltinTypes> builtinTypes,
NotNull<Simplifier> simplifier, NotNull<Simplifier> simplifier,
InternalErrorReporter& ice InternalErrorReporter& ice,
SolverMode solverMode
) )
{ {
UnifierSharedState sharedState{&ice}; UnifierSharedState sharedState{&ice};
TypeArena arena; TypeArena arena;
Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}};
TypeCheckLimits limits; TypeCheckLimits limits;
TypeFunctionRuntime typeFunctionRuntime{ TypeFunctionRuntime typeFunctionRuntime{
NotNull{&ice}, NotNull{&limits} NotNull{&ice}, NotNull{&limits}
}; // TODO: maybe subtyping checks should not invoke user-defined type function runtime }; // TODO: maybe subtyping checks should not invoke user-defined type function runtime
// Subtyping under DCR is not implemented using unification! if (FFlag::LuauUseWorkspacePropToChooseSolver)
if (FFlag::LuauSolverV2)
{ {
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 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 subtyping.isSubtype(subTy, superTy, scope).isSubtype;
return !u.failure; }
else
{
Unifier u{NotNull{&normalizer}, scope, Location{}, Covariant};
u.tryUnify(subTy, superTy);
return !u.failure;
}
} }
} }
@ -3394,30 +3427,50 @@ bool isSubtype(
NotNull<Scope> scope, NotNull<Scope> scope,
NotNull<BuiltinTypes> builtinTypes, NotNull<BuiltinTypes> builtinTypes,
NotNull<Simplifier> simplifier, NotNull<Simplifier> simplifier,
InternalErrorReporter& ice InternalErrorReporter& ice,
SolverMode solverMode
) )
{ {
UnifierSharedState sharedState{&ice}; UnifierSharedState sharedState{&ice};
TypeArena arena; TypeArena arena;
Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}};
TypeCheckLimits limits; TypeCheckLimits limits;
TypeFunctionRuntime typeFunctionRuntime{ TypeFunctionRuntime typeFunctionRuntime{
NotNull{&ice}, NotNull{&limits} NotNull{&ice}, NotNull{&limits}
}; // TODO: maybe subtyping checks should not invoke user-defined type function runtime }; // TODO: maybe subtyping checks should not invoke user-defined type function runtime
// Subtyping under DCR is not implemented using unification! if (FFlag::LuauUseWorkspacePropToChooseSolver)
if (FFlag::LuauSolverV2)
{ {
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 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 subtyping.isSubtype(subPack, superPack, scope).isSubtype;
return !u.failure; }
else
{
Unifier u{NotNull{&normalizer}, scope, Location{}, Covariant};
u.tryUnify(subPack, superPack);
return !u.failure;
}
} }
} }

View file

@ -11,7 +11,7 @@
#include "Luau/Unifier2.h" #include "Luau/Unifier2.h"
LUAU_FASTFLAGVARIABLE(LuauArityMismatchOnUndersaturatedUnknownArguments) LUAU_FASTFLAGVARIABLE(LuauArityMismatchOnUndersaturatedUnknownArguments)
LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping)
namespace Luau namespace Luau
{ {
@ -201,6 +201,43 @@ bool OverloadResolver::isLiteral(AstExpr* expr)
expr->is<AstExprConstantString>() || expr->is<AstExprFunction>() || expr->is<AstExprTable>(); expr->is<AstExprConstantString>() || expr->is<AstExprFunction>() || expr->is<AstExprTable>();
} }
void OverloadResolver::maybeEmplaceError(
ErrorVec* errors,
Location argLocation,
const SubtypingReasoning* reason,
const std::optional<TypeId> failedSubTy,
const std::optional<TypeId> 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::Analysis, ErrorVec> OverloadResolver::checkOverload_( std::pair<OverloadResolver::Analysis, ErrorVec> OverloadResolver::checkOverload_(
TypeId fnTy, TypeId fnTy,
const FunctionType* fn, const FunctionType* fn,
@ -292,14 +329,44 @@ std::pair<OverloadResolver::Analysis, ErrorVec> OverloadResolver::checkOverload_
return {Analysis::Ok, {}}; 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<GenericTypePack>(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 // We have an arity mismatch if the argument tail is a generic type pack
if (auto fnArgs = get<TypePack>(fn->argTypes)) if (auto fnArgs = get<TypePack>(fn->argTypes))
{ {
// TODO: Determine whether arguments have incorrect type, incorrect count, or both (CLI-152070)
if (get<GenericTypePack>(fnArgs->tail)) if (get<GenericTypePack>(fnArgs->tail))
{ {
auto [minParams, optMaxParams] = getParameterExtents(TxnLog::empty(), fn->argTypes); auto [minParams, optMaxParams] = getParameterExtents(TxnLog::empty(), fn->argTypes);
@ -337,12 +404,18 @@ std::pair<OverloadResolver::Analysis, ErrorVec> OverloadResolver::checkOverload_
: argExprs->size() != 0 ? argExprs->back()->location : argExprs->size() != 0 ? argExprs->back()->location
: fnExpr->location; : fnExpr->location;
std::optional<TypeId> failedSubTy = traverseForType(fnTy, reason.subPath, builtinTypes); std::optional<TypeId> failedSubTy = FFlag::LuauReturnMappedGenericPacksFromSubtyping
std::optional<TypeId> failedSuperTy = traverseForType(prospectiveFunction, reason.superPath, builtinTypes); ? traverseForType(fnTy, reason.subPath, builtinTypes, NotNull{&sr.mappedGenericPacks}, arena)
: traverseForType_DEPRECATED(fnTy, reason.subPath, builtinTypes);
std::optional<TypeId> 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))) switch (shouldSuppressErrors(normalizer, *failedSubTy).orElse(shouldSuppressErrors(normalizer, *failedSuperTy)))
{ {
case ErrorSuppression::Suppress: case ErrorSuppression::Suppress:
@ -369,9 +442,36 @@ std::pair<OverloadResolver::Analysis, ErrorVec> 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<TypeId> failedSubTy = traverseForType(fnTy, reason.subPath, builtinTypes, NotNull{&sr.mappedGenericPacks}, arena);
std::optional<TypeId> failedSuperTy =
traverseForType(prospectiveFunction, reason.superPath, builtinTypes, NotNull{&sr.mappedGenericPacks}, arena);
maybeEmplaceError(&errors, argLocation, &reason, failedSubTy, failedSuperTy);
}
}
std::optional<TypePackId> failedSubPack = traverseForPack(fnTy, reason.subPath, builtinTypes); std::optional<TypePackId> failedSubPack = FFlag::LuauReturnMappedGenericPacksFromSubtyping
std::optional<TypePackId> failedSuperPack = traverseForPack(prospectiveFunction, reason.superPath, builtinTypes); ? traverseForPack(fnTy, reason.subPath, builtinTypes, NotNull{&sr.mappedGenericPacks}, arena)
: traverseForPack_DEPRECATED(fnTy, reason.subPath, builtinTypes);
std::optional<TypePackId> failedSuperPack =
FFlag::LuauReturnMappedGenericPacksFromSubtyping
? traverseForPack(prospectiveFunction, reason.superPath, builtinTypes, NotNull{&sr.mappedGenericPacks}, arena)
: traverseForPack_DEPRECATED(prospectiveFunction, reason.superPath, builtinTypes);
if (failedSubPack && failedSuperPack) if (failedSubPack && failedSuperPack)
{ {

View file

@ -22,6 +22,7 @@ LUAU_FASTFLAGVARIABLE(LuauSimplificationTableExternType)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps) LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
LUAU_FASTFLAGVARIABLE(LuauRelateTablesAreNeverDisjoint) LUAU_FASTFLAGVARIABLE(LuauRelateTablesAreNeverDisjoint)
LUAU_FASTFLAG(LuauRefineTablesWithReadType) LUAU_FASTFLAG(LuauRefineTablesWithReadType)
LUAU_FASTFLAGVARIABLE(LuauMissingSeenSetRelate)
namespace Luau 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()) if (auto propInExternType = re->props.find(name); propInExternType != re->props.end())
{ {
Relation propRel; 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); LUAU_ASSERT(prop.readTy && propInExternType->second.readTy);
propRel = relate(*prop.readTy, *propInExternType->second.readTy); propRel = relate(*prop.readTy, *propInExternType->second.readTy);

View file

@ -18,10 +18,10 @@
LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity) LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity)
LUAU_FASTINTVARIABLE(LuauSubtypingReasoningLimit, 100) LUAU_FASTINTVARIABLE(LuauSubtypingReasoningLimit, 100)
LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2)
LUAU_FASTFLAGVARIABLE(LuauSubtypingCheckFunctionGenericCounts) LUAU_FASTFLAGVARIABLE(LuauSubtypingCheckFunctionGenericCounts)
LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps) LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
LUAU_FASTFLAGVARIABLE(LuauReturnMappedGenericPacksFromSubtyping)
namespace Luau namespace Luau
{ {
@ -63,20 +63,52 @@ size_t SubtypingReasoningHash::operator()(const SubtypingReasoning& r) const
} }
template<typename TID> template<typename TID>
static void assertReasoningValid(TID subTy, TID superTy, const SubtypingResult& result, NotNull<BuiltinTypes> builtinTypes) static void assertReasoningValid_DEPRECATED(TID subTy, TID superTy, const SubtypingResult& result, NotNull<BuiltinTypes> builtinTypes)
{ {
if (!FFlag::DebugLuauSubtypingCheckPathValidity) if (!FFlag::DebugLuauSubtypingCheckPathValidity)
return; return;
for (const SubtypingReasoning& reasoning : result.reasoning) for (const SubtypingReasoning& reasoning : result.reasoning)
{ {
LUAU_ASSERT(traverse(subTy, reasoning.subPath, builtinTypes)); LUAU_ASSERT(traverse_DEPRECATED(subTy, reasoning.subPath, builtinTypes));
LUAU_ASSERT(traverse(superTy, reasoning.superPath, builtinTypes)); LUAU_ASSERT(traverse_DEPRECATED(superTy, reasoning.superPath, builtinTypes));
}
}
template<typename TID>
static void assertReasoningValid(TID subTy, TID superTy, const SubtypingResult& result, NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> 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<> template<>
void assertReasoningValid<TableIndexer>(TableIndexer subIdx, TableIndexer superIdx, const SubtypingResult& result, NotNull<BuiltinTypes> builtinTypes) void assertReasoningValid_DEPRECATED<TableIndexer>(
TableIndexer subIdx,
TableIndexer superIdx,
const SubtypingResult& result,
NotNull<BuiltinTypes> builtinTypes
)
{
// Empty method to satisfy the compiler.
}
template<>
void assertReasoningValid<TableIndexer>(
TableIndexer subIdx,
TableIndexer superIdx,
const SubtypingResult& result,
NotNull<BuiltinTypes> builtinTypes,
NotNull<TypeArena> arena
)
{ {
// Empty method to satisfy the compiler. // Empty method to satisfy the compiler.
} }
@ -481,6 +513,11 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope
* cacheable. * cacheable.
*/ */
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping)
{
result.mappedGenericPacks = std::move(env.mappedGenericPacks);
}
if (result.isCacheable) if (result.isCacheable)
resultCache[{subTy, superTy}] = result; resultCache[{subTy, superTy}] = result;
@ -490,12 +527,25 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope
SubtypingResult Subtyping::isSubtype(TypePackId subTp, TypePackId superTp, NotNull<Scope> scope) SubtypingResult Subtyping::isSubtype(TypePackId subTp, TypePackId superTp, NotNull<Scope> scope)
{ {
SubtypingEnvironment env; 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) SubtypingResult Subtyping::cache(SubtypingEnvironment& env, SubtypingResult result, TypeId subTy, TypeId superTy)
{ {
const std::pair<TypeId, TypeId> p{subTy, superTy}; const std::pair<TypeId, TypeId> p{subTy, superTy};
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping && !env.mappedGenericPacks.empty())
result.mappedGenericPacks = env.mappedGenericPacks;
if (result.isCacheable) if (result.isCacheable)
resultCache[p] = result; resultCache[p] = result;
else else
@ -547,11 +597,27 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
const SubtypingResult* cachedResult = resultCache.find({subTy, superTy}); const SubtypingResult* cachedResult = resultCache.find({subTy, superTy});
if (cachedResult) if (cachedResult)
{
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping)
{
for (const auto& [genericTp, boundTp] : cachedResult->mappedGenericPacks)
env.mappedGenericPacks.try_insert(genericTp, boundTp);
}
return *cachedResult; return *cachedResult;
}
cachedResult = env.tryFindSubtypingResult({subTy, superTy}); cachedResult = env.tryFindSubtypingResult({subTy, superTy});
if (cachedResult) if (cachedResult)
{
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping)
{
for (const auto& [genericTp, boundTp] : cachedResult->mappedGenericPacks)
env.mappedGenericPacks.try_insert(genericTp, boundTp);
}
return *cachedResult; return *cachedResult;
}
// TODO: Do we care about returning a proof that this is error-suppressing? // 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, // 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<SingletonType, TableType>(subTy, superTy)) else if (auto p = get2<SingletonType, TableType>(subTy, superTy))
result = isCovariantWith(env, p, scope); 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); return cache(env, std::move(result), subTy, superTy);
} }
@ -840,14 +909,31 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
// <X>(X) -> () <: (T) -> () // <X>(X) -> () <: (T) -> ()
// Possible optimization: If headSize == 0 then we can just use subTp as-is. // Possible optimization: If headSize == 0 then we can just use subTp as-is.
std::vector<TypeId> headSlice = FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2 std::vector<TypeId> headSlice = FFlag::LuauReturnMappedGenericPacksFromSubtyping
? std::vector<TypeId>(begin(superHead) + headSize, end(superHead)) ? std::vector<TypeId>(begin(superHead) + headSize, end(superHead))
: std::vector<TypeId>(begin(superHead), begin(superHead) + headSize); : std::vector<TypeId>(begin(superHead), begin(superHead) + headSize);
TypePackId superTailPack = arena->addTypePack(std::move(headSlice), superTail); TypePackId superTailPack = arena->addTypePack(std::move(headSlice), superTail);
if (TypePackId* other = env.getMappedPackBounds(*subTail)) 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<TypePack>(*other);
if (const VariadicTypePack* vtp = tp ? get<VariadicTypePack>(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 else
env.mappedGenericPacks.try_insert(*subTail, superTailPack); env.mappedGenericPacks.try_insert(*subTail, superTailPack);
@ -897,17 +983,31 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
// <X...>(X...) -> () <: (T) -> () // <X...>(X...) -> () <: (T) -> ()
// Possible optimization: If headSize == 0 then we can just use subTp as-is. // Possible optimization: If headSize == 0 then we can just use subTp as-is.
std::vector<TypeId> headSlice = FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2 std::vector<TypeId> headSlice = FFlag::LuauReturnMappedGenericPacksFromSubtyping
? std::vector<TypeId>(begin(subHead) + headSize, end(subHead)) ? std::vector<TypeId>(begin(subHead) + headSize, end(subHead))
: std::vector<TypeId>(begin(subHead), begin(subHead) + headSize); : std::vector<TypeId>(begin(subHead), begin(subHead) + headSize);
TypePackId subTailPack = arena->addTypePack(std::move(headSlice), subTail); TypePackId subTailPack = arena->addTypePack(std::move(headSlice), subTail);
if (TypePackId* other = env.getMappedPackBounds(*superTail)) if (TypePackId* other = env.getMappedPackBounds(*superTail))
// TODO: TypePath can't express "slice of a pack + its tail". {
if (FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2) if (FFlag::LuauReturnMappedGenericPacksFromSubtyping)
results.push_back(isCovariantWith(env, subTailPack, *other, scope).withSuperComponent(TypePath::PackField::Tail)); {
const TypePack* tp = get<TypePack>(*other);
if (const VariadicTypePack* vtp = tp ? get<VariadicTypePack>(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 else
results.push_back(isContravariantWith(env, subTailPack, *other, scope).withSuperComponent(TypePath::PackField::Tail)); results.push_back(isContravariantWith(env, subTailPack, *other, scope).withSuperComponent(TypePath::PackField::Tail));
}
else else
env.mappedGenericPacks.try_insert(*superTail, subTailPack); env.mappedGenericPacks.try_insert(*superTail, subTailPack);
@ -1041,7 +1141,11 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
} }
SubtypingResult result = SubtypingResult::all(results); 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; 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; return result;
} }
@ -1091,7 +1198,11 @@ SubtypingResult Subtyping::isInvariantWith(SubtypingEnvironment& env, SubTy&& su
reasoning.variance = SubtypingVariance::Invariant; 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; return result;
} }

View file

@ -23,6 +23,7 @@ LUAU_FASTFLAGVARIABLE(LuauEnableDenseTableAlias)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps) LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
LUAU_FASTFLAGVARIABLE(LuauSolverAgnosticStringification)
/* /*
* Enables increasing levels of verbosity for Luau type names when stringifying. * Enables increasing levels of verbosity for Luau type names when stringifying.
@ -86,8 +87,14 @@ struct FindCyclicTypes final : TypeVisitor
{ {
if (!visited.insert(ty)) if (!visited.insert(ty))
return false; return false;
if (FFlag::LuauSolverAgnosticStringification)
if (FFlag::LuauSolverV2) {
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 // TODO: Replace these if statements with assert()s when we
// delete FFlag::LuauSolverV2. // delete FFlag::LuauSolverV2.
@ -440,7 +447,7 @@ struct TypeStringifier
void stringify(const std::string& name, const Property& prop) void stringify(const std::string& name, const Property& prop)
{ {
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticStringification)
return _newStringify(name, prop); return _newStringify(name, prop);
emitKey(name); emitKey(name);
@ -503,9 +510,44 @@ struct TypeStringifier
{ {
state.result.invalid = true; state.result.invalid = true;
// TODO: ftv.lowerBound and ftv.upperBound should always be non-nil when // Free types are guaranteed to have upper and lower bounds now.
// the new solver is used. This can be replaced with an assert. if (FFlag::LuauSolverAgnosticStringification)
if (FFlag::LuauSolverV2 && ftv.lowerBound && ftv.upperBound) {
LUAU_ASSERT(ftv.lowerBound);
LUAU_ASSERT(ftv.upperBound);
const TypeId lowerBound = follow(ftv.lowerBound);
const TypeId upperBound = follow(ftv.upperBound);
if (get<NeverType>(lowerBound) && get<UnknownType>(upperBound))
{
state.emit("'");
state.emit(state.getName(ty));
if (FInt::DebugLuauVerboseTypeNames >= 1)
state.emit(ftv.polarity);
}
else
{
state.emit("(");
if (!get<NeverType>(lowerBound))
{
stringify(lowerBound);
state.emit(" <: ");
}
state.emit("'");
state.emit(state.getName(ty));
if (FInt::DebugLuauVerboseTypeNames >= 1)
state.emit(ftv.polarity);
if (!get<UnknownType>(upperBound))
{
state.emit(" <: ");
stringify(upperBound);
}
state.emit(")");
}
return;
}
else if (FFlag::LuauSolverV2 && ftv.lowerBound && ftv.upperBound)
{ {
const TypeId lowerBound = follow(ftv.lowerBound); const TypeId lowerBound = follow(ftv.lowerBound);
const TypeId upperBound = follow(ftv.upperBound); const TypeId upperBound = follow(ftv.upperBound);
@ -545,13 +587,16 @@ struct TypeStringifier
state.emit(state.getName(ty)); state.emit(state.getName(ty));
if (FFlag::LuauSolverV2 && FInt::DebugLuauVerboseTypeNames >= 1) if (FFlag::LuauSolverAgnosticStringification && FInt::DebugLuauVerboseTypeNames >= 1)
state.emit(ftv.polarity); state.emit(ftv.polarity);
else if (FFlag::LuauSolverV2 && FInt::DebugLuauVerboseTypeNames >= 1)
state.emit(ftv.polarity);
if (FInt::DebugLuauVerboseTypeNames >= 2) if (FInt::DebugLuauVerboseTypeNames >= 2)
{ {
state.emit("-"); state.emit("-");
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticStringification)
state.emitLevel(ftv.scope); state.emitLevel(ftv.scope);
else else
state.emit(ftv.level); state.emit(ftv.level);
@ -583,7 +628,7 @@ struct TypeStringifier
if (FInt::DebugLuauVerboseTypeNames >= 2) if (FInt::DebugLuauVerboseTypeNames >= 2)
{ {
state.emit("-"); state.emit("-");
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticStringification)
state.emitLevel(gtv.scope); state.emitLevel(gtv.scope);
else else
state.emit(gtv.level); state.emit(gtv.level);
@ -686,7 +731,7 @@ struct TypeStringifier
state.emit(">"); state.emit(">");
} }
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticStringification)
{ {
if (ftv.isCheckedFunction) if (ftv.isCheckedFunction)
state.emit("@checked "); state.emit("@checked ");
@ -783,10 +828,10 @@ struct TypeStringifier
std::string openbrace = "@@@"; std::string openbrace = "@@@";
std::string closedbrace = "@@@?!"; 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: case TableState::Sealed:
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticStringification)
{ {
openbrace = "{"; openbrace = "{";
closedbrace = "}"; closedbrace = "}";
@ -799,7 +844,7 @@ struct TypeStringifier
} }
break; break;
case TableState::Unsealed: case TableState::Unsealed:
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticStringification)
{ {
state.result.invalid = true; state.result.invalid = true;
openbrace = "{|"; openbrace = "{|";
@ -1314,7 +1359,7 @@ struct TypePackStringifier
if (FInt::DebugLuauVerboseTypeNames >= 2) if (FInt::DebugLuauVerboseTypeNames >= 2)
{ {
state.emit("-"); state.emit("-");
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticStringification)
state.emitLevel(pack.scope); state.emitLevel(pack.scope);
else else
state.emit(pack.level); state.emit(pack.level);
@ -1336,7 +1381,7 @@ struct TypePackStringifier
if (FInt::DebugLuauVerboseTypeNames >= 2) if (FInt::DebugLuauVerboseTypeNames >= 2)
{ {
state.emit("-"); state.emit("-");
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticStringification)
state.emitLevel(pack.scope); state.emitLevel(pack.scope);
else else
state.emit(pack.level); state.emit(pack.level);

View file

@ -10,8 +10,6 @@
#include <limits> #include <limits>
#include <math.h> #include <math.h>
LUAU_FASTFLAG(LuauStoreLocalAnnotationColonPositions)
namespace namespace
{ {
bool isIdentifierStartChar(char c) bool isIdentifierStartChar(char c)
@ -322,8 +320,7 @@ struct Printer
writer.identifier(local.name.value); writer.identifier(local.name.value);
if (writeTypes && local.annotation) if (writeTypes && local.annotation)
{ {
if (FFlag::LuauStoreLocalAnnotationColonPositions) advance(colonPosition);
advance(colonPosition);
writer.symbol(":"); writer.symbol(":");
visualizeTypeAnnotation(*local.annotation); visualizeTypeAnnotation(*local.annotation);
} }
@ -928,7 +925,7 @@ struct Printer
for (size_t i = 0; i < a->vars.size; i++) for (size_t i = 0; i < a->vars.size; i++)
{ {
varComma(); varComma();
if (FFlag::LuauStoreLocalAnnotationColonPositions && cstNode) if (cstNode)
{ {
LUAU_ASSERT(cstNode->varsAnnotationColonPositions.size > i); LUAU_ASSERT(cstNode->varsAnnotationColonPositions.size > i);
visualize(*a->vars.data[i], cstNode->varsAnnotationColonPositions.data[i]); visualize(*a->vars.data[i], cstNode->varsAnnotationColonPositions.data[i]);
@ -957,10 +954,7 @@ struct Printer
writer.keyword("for"); writer.keyword("for");
if (FFlag::LuauStoreLocalAnnotationColonPositions) visualize(*a->var, cstNode ? cstNode->annotationColonPosition : Position{0, 0});
visualize(*a->var, cstNode ? cstNode->annotationColonPosition : Position{0, 0});
else
visualize(*a->var, Position{0, 0});
if (cstNode) if (cstNode)
advance(cstNode->equalsPosition); advance(cstNode->equalsPosition);
@ -994,7 +988,7 @@ struct Printer
for (size_t i = 0; i < a->vars.size; i++) for (size_t i = 0; i < a->vars.size; i++)
{ {
varComma(); varComma();
if (FFlag::LuauStoreLocalAnnotationColonPositions && cstNode) if (cstNode)
{ {
LUAU_ASSERT(cstNode->varsAnnotationColonPositions.size > i); LUAU_ASSERT(cstNode->varsAnnotationColonPositions.size > i);
visualize(*a->vars.data[i], cstNode->varsAnnotationColonPositions.data[i]); visualize(*a->vars.data[i], cstNode->varsAnnotationColonPositions.data[i]);
@ -1317,7 +1311,7 @@ struct Printer
writer.identifier(local->name.value); writer.identifier(local->name.value);
if (writeTypes && local->annotation) if (writeTypes && local->annotation)
{ {
if (FFlag::LuauStoreLocalAnnotationColonPositions && cstNode) if (cstNode)
{ {
LUAU_ASSERT(cstNode->argsAnnotationColonPositions.size > i); LUAU_ASSERT(cstNode->argsAnnotationColonPositions.size > i);
advance(cstNode->argsAnnotationColonPositions.data[i]); advance(cstNode->argsAnnotationColonPositions.data[i]);
@ -1335,7 +1329,7 @@ struct Printer
if (func.varargAnnotation) if (func.varargAnnotation)
{ {
if (FFlag::LuauStoreLocalAnnotationColonPositions && cstNode) if (cstNode)
{ {
LUAU_ASSERT(cstNode->varargAnnotationColonPosition != Position({0, 0})); LUAU_ASSERT(cstNode->varargAnnotationColonPosition != Position({0, 0}));
advance(cstNode->varargAnnotationColonPosition); advance(cstNode->varargAnnotationColonPosition);

View file

@ -31,6 +31,7 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauSubtypingCheckFunctionGenericCounts) LUAU_FASTFLAG(LuauSubtypingCheckFunctionGenericCounts)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps) LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver)
namespace Luau namespace Luau
{ {
@ -711,7 +712,7 @@ Property Property::create(std::optional<TypeId> read, std::optional<TypeId> writ
TypeId Property::type_DEPRECATED() const TypeId Property::type_DEPRECATED() const
{ {
if (FFlag::LuauRemoveTypeCallsForReadWriteProps) if (FFlag::LuauRemoveTypeCallsForReadWriteProps && !FFlag::LuauUseWorkspacePropToChooseSolver)
LUAU_ASSERT(!FFlag::LuauSolverV2); LUAU_ASSERT(!FFlag::LuauSolverV2);
LUAU_ASSERT(readTy); LUAU_ASSERT(readTy);
@ -1001,7 +1002,7 @@ TypeId makeFunction(
std::initializer_list<TypeId> retTypes std::initializer_list<TypeId> retTypes
); );
TypeId makeStringMetatable(NotNull<BuiltinTypes> builtinTypes); // BuiltinDefinitions.cpp TypeId makeStringMetatable(NotNull<BuiltinTypes> builtinTypes, SolverMode mode); // BuiltinDefinitions.cpp
BuiltinTypes::BuiltinTypes() BuiltinTypes::BuiltinTypes()
: arena(new TypeArena) : arena(new TypeArena)

View file

@ -36,8 +36,11 @@ LUAU_FASTFLAG(LuauNewNonStrictFixGenericTypePacks)
LUAU_FASTFLAGVARIABLE(LuauReportSubtypingErrors) LUAU_FASTFLAGVARIABLE(LuauReportSubtypingErrors)
LUAU_FASTFLAGVARIABLE(LuauSkipMalformedTypeAliases) LUAU_FASTFLAGVARIABLE(LuauSkipMalformedTypeAliases)
LUAU_FASTFLAGVARIABLE(LuauTableLiteralSubtypeSpecificCheck2) LUAU_FASTFLAGVARIABLE(LuauTableLiteralSubtypeSpecificCheck2)
LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch)
LUAU_FASTFLAG(LuauSubtypingCheckFunctionGenericCounts) LUAU_FASTFLAG(LuauSubtypingCheckFunctionGenericCounts)
LUAU_FASTFLAG(LuauTableLiteralSubtypeCheckFunctionCalls) LUAU_FASTFLAG(LuauTableLiteralSubtypeCheckFunctionCalls)
LUAU_FASTFLAGVARIABLE(LuauSuppressErrorsForMultipleNonviableOverloads)
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping)
namespace Luau namespace Luau
{ {
@ -317,7 +320,7 @@ TypeChecker2::TypeChecker2(
, ice(unifierState->iceHandler) , ice(unifierState->iceHandler)
, sourceModule(sourceModule) , sourceModule(sourceModule)
, module(module) , 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{builtinTypes, NotNull{&module->internalTypes}, simplifier, NotNull{&normalizer}, typeFunctionRuntime, NotNull{unifierState->iceHandler}}
, subtyping(&_subtyping) , subtyping(&_subtyping)
{ {
@ -1710,14 +1713,24 @@ void TypeChecker2::visitCall(AstExprCall* call)
return; // Ok. Calling an uninhabited type is no-op. return; // Ok. Calling an uninhabited type is no-op.
else if (!resolver.nonviableOverloads.empty()) else if (!resolver.nonviableOverloads.empty())
{ {
if (resolver.nonviableOverloads.size() == 1 && !isErrorSuppressing(call->func->location, resolver.nonviableOverloads.front().first)) if (FFlag::LuauSuppressErrorsForMultipleNonviableOverloads)
reportErrors(resolver.nonviableOverloads.front().second); {
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 else
{ {
std::string s = "None of the overloads for function that accept "; if (resolver.nonviableOverloads.size() == 1 && !isErrorSuppressing(call->func->location, resolver.nonviableOverloads.front().first))
s += std::to_string(args.head.size()); reportErrors(resolver.nonviableOverloads.front().second);
s += " arguments are compatible."; else
reportError(GenericError{std::move(s)}, call->location); {
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()) 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 the function type has a function annotation, we need to see if we can suggest an annotation
if (normalizedFnTy) if (normalizedFnTy)
{ {
const FunctionType* inferredFtv = get<FunctionType>(normalizedFnTy->functions.parts.front()); if (FFlag::LuauStuckTypeFunctionsStillDispatch)
LUAU_ASSERT(inferredFtv); suggestAnnotations(fn, normalizedFnTy->functions.parts.front());
else
TypeFunctionReductionGuesser guesser{NotNull{&module->internalTypes}, builtinTypes, NotNull{&normalizer}};
for (TypeId retTy : inferredFtv->retTypes)
{ {
if (get<TypeFunctionInstanceType>(follow(retTy))) const FunctionType* inferredFtv = get<FunctionType>(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 (get<TypeFunctionInstanceType>(follow(retTy)))
if (result.shouldRecommendAnnotation && !get<UnknownType>(result.guessedReturnType)) {
reportError( TypeFunctionReductionGuessResult result = guesser.guessTypeFunctionReductionForFunctionExpr(*fn, inferredFtv, retTy);
ExplicitFunctionAnnotationRecommended{std::move(result.guessedFunctionAnnotations), result.guessedReturnType}, fn->location if (result.shouldRecommendAnnotation && !get<UnknownType>(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()) if (reasoning.subPath.empty() && reasoning.superPath.empty())
continue; continue;
std::optional<TypeOrPack> optSubLeaf = traverse(subTy, reasoning.subPath, builtinTypes); std::optional<TypeOrPack> optSubLeaf =
std::optional<TypeOrPack> optSuperLeaf = traverse(superTy, reasoning.superPath, builtinTypes); FFlag::LuauReturnMappedGenericPacksFromSubtyping
? traverse(subTy, reasoning.subPath, builtinTypes, NotNull{&r.mappedGenericPacks}, subtyping->arena)
: traverse_DEPRECATED(subTy, reasoning.subPath, builtinTypes);
std::optional<TypeOrPack> optSuperLeaf =
FFlag::LuauReturnMappedGenericPacksFromSubtyping
? traverse(superTy, reasoning.superPath, builtinTypes, NotNull{&r.mappedGenericPacks}, subtyping->arena)
: traverse_DEPRECATED(superTy, reasoning.superPath, builtinTypes);
if (!optSubLeaf || !optSuperLeaf) if (!optSubLeaf || !optSuperLeaf)
ice->ice("Subtyping test returned a reasoning with an invalid path", location); 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 indexType = follow(tt->indexer->indexType);
TypeId givenType = module->internalTypes.addType(SingletonType{StringSingleton{prop}}); 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}}; return {NormalizationResult::True, {tt->indexer->indexResultType}};
} }
@ -3550,7 +3574,47 @@ PropertyType TypeChecker2::hasIndexTypeFromType(
return {NormalizationResult::False, {}}; return {NormalizationResult::False, {}};
} }
void TypeChecker2::suggestAnnotations(AstExprFunction* expr, TypeId ty)
{
const FunctionType* inferredFtv = get<FunctionType>(ty);
LUAU_ASSERT(inferredFtv);
VecDeque<TypeId> workList;
DenseHashSet<TypeId> 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<UnionType>(t))
{
for (TypeId t : ut)
workList.push_back(t);
}
else if (auto it = get<IntersectionType>(t))
{
for (TypeId t : it)
workList.push_back(t);
}
else if (get<TypeFunctionInstanceType>(t))
{
TypeFunctionReductionGuessResult result = guesser.guessTypeFunctionReductionForFunctionExpr(*expr, inferredFtv, t);
if (result.shouldRecommendAnnotation && !get<UnknownType>(result.guessedReturnType))
reportError(
ExplicitFunctionAnnotationRecommended{std::move(result.guessedFunctionAnnotations), result.guessedReturnType}, expr->location
);
}
}
}
void TypeChecker2::diagnoseMissingTableKey(UnknownProperty* utk, TypeErrorData& data) const 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); return isErrorSuppressing(loc1, tp1) || isErrorSuppressing(loc2, tp2);
} }
bool TypeChecker2::reportNonviableOverloadErrors(
std::vector<std::pair<TypeId, ErrorVec>> 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<ErrorVec> 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 } // namespace Luau

View file

@ -57,6 +57,7 @@ LUAU_FASTFLAG(LuauUserTypeFunctionAliases)
LUAU_FASTFLAG(LuauUpdateGetMetatableTypeSignature) LUAU_FASTFLAG(LuauUpdateGetMetatableTypeSignature)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps) LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
LUAU_FASTFLAGVARIABLE(LuauOccursCheckForRefinement) LUAU_FASTFLAGVARIABLE(LuauOccursCheckForRefinement)
LUAU_FASTFLAGVARIABLE(LuauStuckTypeFunctionsStillDispatch)
LUAU_FASTFLAG(LuauRefineTablesWithReadType) LUAU_FASTFLAG(LuauRefineTablesWithReadType)
LUAU_FASTFLAGVARIABLE(LuauEmptyStringInKeyOf) LUAU_FASTFLAGVARIABLE(LuauEmptyStringInKeyOf)
@ -257,6 +258,10 @@ struct TypeFunctionReducer
/// reducible later. /// reducible later.
Irreducible, Irreducible,
/// A type function that cannot be reduced any further because it has no valid reduction.
/// eg add<number, string>
Stuck,
/// Some type functions can operate on generic parameters /// Some type functions can operate on generic parameters
Generic, Generic,
@ -313,8 +318,15 @@ struct TypeFunctionReducer
if (seen.contains(t)) if (seen.contains(t))
continue; continue;
if (is<TypeFunctionInstanceType>(t)) if (auto tfit = get<TypeFunctionInstanceType>(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) for (auto cyclicTy : cyclicTypeFunctions)
{ {
if (t == cyclicTy) if (t == cyclicTy)
@ -382,6 +394,35 @@ struct TypeFunctionReducer
result.reducedPacks.insert(subject); result.reducedPacks.insert(subject);
} }
TypeFunctionInstanceState getState(TypeId ty) const
{
auto tfit = get<TypeFunctionInstanceType>(ty);
LUAU_ASSERT(tfit);
return tfit->state;
}
void setState(TypeId ty, TypeFunctionInstanceState state) const
{
if (ty->owningArena != ctx.arena)
return;
TypeFunctionInstanceType* tfit = getMutable<TypeFunctionInstanceType>(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<typename T> template<typename T>
void handleTypeFunctionReduction(T subject, TypeFunctionReductionResult<T> reduction) void handleTypeFunctionReduction(T subject, TypeFunctionReductionResult<T> reduction)
{ {
@ -402,6 +443,24 @@ struct TypeFunctionReducer
if (FFlag::DebugLuauLogTypeFamilies) if (FFlag::DebugLuauLogTypeFamilies)
printf("%s is uninhabited\n", toString(subject, {true}).c_str()); 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<T, TypeId>) if constexpr (std::is_same_v<T, TypeId>)
result.errors.emplace_back(location, UninhabitedTypeFunction{subject}); result.errors.emplace_back(location, UninhabitedTypeFunction{subject});
else if constexpr (std::is_same_v<T, TypePackId>) else if constexpr (std::is_same_v<T, TypePackId>)
@ -409,6 +468,9 @@ struct TypeFunctionReducer
} }
else if (reduction.reductionStatus == Reduction::MaybeOk && !force) 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) if (FFlag::DebugLuauLogTypeFamilies)
printf( printf(
"%s is irreducible; blocked on %zu types, %zu packs\n", "%s is irreducible; blocked on %zu types, %zu packs\n",
@ -423,6 +485,8 @@ struct TypeFunctionReducer
for (TypePackId b : reduction.blockedPacks) for (TypePackId b : reduction.blockedPacks)
result.blockedPacks.insert(b); result.blockedPacks.insert(b);
} }
else
LUAU_ASSERT(!"Unreachable");
} }
} }
@ -438,12 +502,33 @@ struct TypeFunctionReducer
{ {
SkipTestResult skip = testForSkippability(p); 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 (skip == SkipTestResult::Irreducible || (skip == SkipTestResult::Generic && !tfit->function->canReduceGenerics))
{ {
if (FFlag::DebugLuauLogTypeFamilies) 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); irreducible.insert(subject);
if (skip == SkipTestResult::Generic)
setState(subject, TypeFunctionInstanceState::Solved);
return false; return false;
} }
else if (skip == SkipTestResult::Defer) else if (skip == SkipTestResult::Defer)
@ -556,6 +641,9 @@ struct TypeFunctionReducer
if (FFlag::DebugLuauLogTypeFamilies) if (FFlag::DebugLuauLogTypeFamilies)
printf("Irreducible due to irreducible/pending and a non-cyclic function\n"); printf("Irreducible due to irreducible/pending and a non-cyclic function\n");
if (tfit->state == TypeFunctionInstanceState::Stuck || tfit->state == TypeFunctionInstanceState::Solved)
tryGuessing(subject);
return; return;
} }
@ -732,7 +820,14 @@ FunctionGraphReductionResult reduceTypeFunctions(TypePackId entrypoint, Location
bool isPending(TypeId ty, ConstraintSolver* solver) bool isPending(TypeId ty, ConstraintSolver* solver)
{ {
return is<BlockedType, PendingExpansionType, TypeFunctionInstanceType>(ty) || (solver && solver->hasUnresolvedConstraints(ty)); if (FFlag::LuauStuckTypeFunctionsStillDispatch)
{
if (auto tfit = get<TypeFunctionInstanceType>(ty); tfit && tfit->state == TypeFunctionInstanceState::Unsolved)
return true;
return is<BlockedType, PendingExpansionType>(ty) || (solver && solver->hasUnresolvedConstraints(ty));
}
else
return is<BlockedType, PendingExpansionType, TypeFunctionInstanceType>(ty) || (solver && solver->hasUnresolvedConstraints(ty));
} }
template<typename F, typename... Args> template<typename F, typename... Args>
@ -3268,7 +3363,7 @@ bool searchPropsAndIndexer(
indexType = follow(tblIndexer->indexResultType); 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); TypeId idxResultTy = follow(tblIndexer->indexResultType);

View file

@ -209,7 +209,7 @@ TypeChecker::TypeChecker(const ScopePtr& globalScope, ModuleResolver* resolver,
, builtinTypes(builtinTypes) , builtinTypes(builtinTypes)
, iceHandler(iceHandler) , iceHandler(iceHandler)
, unifierState(iceHandler) , unifierState(iceHandler)
, normalizer(nullptr, builtinTypes, NotNull{&unifierState}) , normalizer(nullptr, builtinTypes, NotNull{&unifierState}, SolverMode::Old)
, reusableInstantiation(TxnLog::empty(), nullptr, builtinTypes, {}, nullptr) , reusableInstantiation(TxnLog::empty(), nullptr, builtinTypes, {}, nullptr)
, nilType(builtinTypes->nilType) , nilType(builtinTypes->nilType)
, numberType(builtinTypes->numberType) , numberType(builtinTypes->numberType)

View file

@ -453,6 +453,33 @@ std::pair<std::vector<TypeId>, std::optional<TypePackId>> flatten(TypePackId tp,
return {flattened, tail}; return {flattened, tail};
} }
std::pair<std::vector<TypeId>, std::optional<TypePackId>> flatten(TypePackId tp, const DenseHashMap<TypePackId, TypePackId>& mappedGenericPacks)
{
tp = mappedGenericPacks.contains(tp) ? *mappedGenericPacks.find(tp) : tp;
std::vector<TypeId> flattened;
std::optional<TypePackId> 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) bool isVariadic(TypePackId tp)
{ {
return isVariadic(tp, *TxnLog::empty()); return isVariadic(tp, *TxnLog::empty());

View file

@ -1,9 +1,12 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // 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/TypePath.h"
#include "Luau/Anyification.h"
#include "Luau/Common.h" #include "Luau/Common.h"
#include "Luau/DenseHash.h" #include "Luau/DenseHash.h"
#include "Luau/Type.h" #include "Luau/Type.h"
#include "Luau/TypeArena.h"
#include "Luau/TypeFwd.h" #include "Luau/TypeFwd.h"
#include "Luau/TypePack.h" #include "Luau/TypePack.h"
#include "Luau/TypeOrPack.h" #include "Luau/TypeOrPack.h"
@ -11,9 +14,9 @@
#include <functional> #include <functional>
#include <optional> #include <optional>
#include <sstream> #include <sstream>
#include <type_traits>
LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping)
// Maximum number of steps to follow when traversing a path. May not always // 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 // equate to the number of components in a path, depending on the traversal
@ -29,7 +32,6 @@ namespace TypePath
Property::Property(std::string name) Property::Property(std::string name)
: name(std::move(name)) : name(std::move(name))
{ {
LUAU_ASSERT(!FFlag::LuauSolverV2);
} }
Property Property::read(std::string name) Property Property::read(std::string name)
@ -52,6 +54,11 @@ bool Index::operator==(const Index& other) const
return index == other.index; return index == other.index;
} }
bool PackSlice::operator==(const PackSlice& other) const
{
return start_index == other.start_index;
}
bool Reduction::operator==(const Reduction& other) const bool Reduction::operator==(const Reduction& other) const
{ {
return resultType == other.resultType; return resultType == other.resultType;
@ -129,6 +136,11 @@ size_t PathHash::operator()(const PackField& field) const
return static_cast<size_t>(field); return static_cast<size_t>(field);
} }
size_t PathHash::operator()(const PackSlice& slice) const
{
return slice.start_index;
}
size_t PathHash::operator()(const Reduction& reduction) const size_t PathHash::operator()(const Reduction& reduction) const
{ {
return std::hash<TypeId>()(reduction.resultType); return std::hash<TypeId>()(reduction.resultType);
@ -168,7 +180,6 @@ PathBuilder& PathBuilder::writeProp(std::string name)
PathBuilder& PathBuilder::prop(std::string name) PathBuilder& PathBuilder::prop(std::string name)
{ {
LUAU_ASSERT(!FFlag::LuauSolverV2);
components.push_back(Property{std::move(name)}); components.push_back(Property{std::move(name)});
return *this; return *this;
} }
@ -239,6 +250,12 @@ PathBuilder& PathBuilder::tail()
return *this; return *this;
} }
PathBuilder& PathBuilder::packSlice(size_t start_index)
{
components.emplace_back(PackSlice{start_index});
return *this;
}
} // namespace TypePath } // namespace TypePath
namespace namespace
@ -246,19 +263,31 @@ namespace
struct TraversalState struct TraversalState
{ {
TraversalState(TypeId root, NotNull<BuiltinTypes> builtinTypes) TraversalState(TypeId root, NotNull<BuiltinTypes> builtinTypes, const DenseHashMap<TypePackId, TypePackId>* mappedGenericPacks, TypeArena* arena)
: current(root) : current(root)
, builtinTypes(builtinTypes) , builtinTypes(builtinTypes)
, mappedGenericPacks(mappedGenericPacks)
, arena(arena)
{ {
} }
TraversalState(TypePackId root, NotNull<BuiltinTypes> builtinTypes) TraversalState(
TypePackId root,
NotNull<BuiltinTypes> builtinTypes,
const DenseHashMap<TypePackId, TypePackId>* mappedGenericPacks,
TypeArena* arena
)
: current(root) : current(root)
, builtinTypes(builtinTypes) , builtinTypes(builtinTypes)
, mappedGenericPacks(mappedGenericPacks)
, arena(arena)
{ {
} }
TypeOrPack current; TypeOrPack current;
NotNull<BuiltinTypes> builtinTypes; NotNull<BuiltinTypes> builtinTypes;
// TODO: make these NotNull when LuauReturnMappedGenericPacksFromSubtyping is clipped
const DenseHashMap<TypePackId, TypePackId>* mappedGenericPacks;
TypeArena* arena;
int steps = 0; int steps = 0;
void updateCurrent(TypeId ty) void updateCurrent(TypeId ty)
@ -388,17 +417,43 @@ struct TraversalState
{ {
auto currentPack = get<TypePackId>(current); auto currentPack = get<TypePackId>(current);
LUAU_ASSERT(currentPack); LUAU_ASSERT(currentPack);
if (get<TypePack>(*currentPack)) if (FFlag::LuauReturnMappedGenericPacksFromSubtyping)
{ {
auto it = begin(*currentPack); if (const auto tp = get<TypePack>(*currentPack))
for (size_t i = 0; i < index.index && it != end(*currentPack); ++i)
++it;
if (it != end(*currentPack))
{ {
updateCurrent(*it); auto it = begin(*currentPack);
return true;
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<TypePack>(*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()) if (auto tail = it.tail())
{ {
updateCurrent(*tail); if (FFlag::LuauReturnMappedGenericPacksFromSubtyping && mappedGenericPacks && mappedGenericPacks->contains(*tail))
updateCurrent(*mappedGenericPacks->find(*tail));
else
updateCurrent(*tail);
return true; return true;
} }
} }
@ -531,6 +589,47 @@ struct TraversalState
return false; 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<TypePackId>(current);
if (!currentPack)
return false;
auto [flatHead, flatTail] = flatten(*currentPack, *mappedGenericPacks);
if (flatHead.size() <= slice.start_index)
return false;
std::vector<TypeId> 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 } // namespace
@ -614,6 +713,8 @@ std::string toString(const TypePath::Path& path, bool prefixDot)
} }
result << "()"; result << "()";
} }
else if constexpr (std::is_same_v<T, TypePath::PackSlice>)
result << "[" << std::to_string(c.start_index) << ":]";
else if constexpr (std::is_same_v<T, TypePath::Reduction>) else if constexpr (std::is_same_v<T, TypePath::Reduction>)
{ {
// We need to rework the TypePath system to make subtyping failures easier to understand // 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; state = State::Normal;
} }
} }
else if constexpr (std::is_same_v<T, TypePath::PackSlice>)
result << "the portion of the type pack starting at index " << c.start_index << " to the end";
else if constexpr (std::is_same_v<T, TypePath::Reduction>) else if constexpr (std::is_same_v<T, TypePath::Reduction>)
{ {
if (state == State::Initial) if (state == State::Initial)
@ -892,27 +995,57 @@ static bool traverse(TraversalState& state, const Path& path)
return true; return true;
} }
std::optional<TypeOrPack> traverse(TypeId root, const Path& path, NotNull<BuiltinTypes> builtinTypes) std::optional<TypeOrPack> traverse_DEPRECATED(TypeId root, const Path& path, NotNull<BuiltinTypes> builtinTypes)
{ {
TraversalState state(follow(root), builtinTypes); TraversalState state(follow(root), builtinTypes, nullptr, nullptr);
if (traverse(state, path)) if (traverse(state, path))
return state.current; return state.current;
else else
return std::nullopt; return std::nullopt;
} }
std::optional<TypeOrPack> traverse(TypePackId root, const Path& path, NotNull<BuiltinTypes> builtinTypes) std::optional<TypeOrPack> traverse_DEPRECATED(TypePackId root, const Path& path, NotNull<BuiltinTypes> builtinTypes)
{ {
TraversalState state(follow(root), builtinTypes); TraversalState state(follow(root), builtinTypes, nullptr, nullptr);
if (traverse(state, path)) if (traverse(state, path))
return state.current; return state.current;
else else
return std::nullopt; return std::nullopt;
} }
std::optional<TypeId> traverseForType(TypeId root, const Path& path, NotNull<BuiltinTypes> builtinTypes) std::optional<TypeOrPack> traverse(
TypeId root,
const Path& path,
NotNull<BuiltinTypes> builtinTypes,
NotNull<const DenseHashMap<TypePackId, TypePackId>> mappedGenericPacks,
NotNull<TypeArena> 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<TypeOrPack> traverse(
TypePackId root,
const Path& path,
NotNull<BuiltinTypes> builtinTypes,
NotNull<const DenseHashMap<TypePackId, TypePackId>> mappedGenericPacks,
NotNull<TypeArena> arena
)
{
TraversalState state(follow(root), builtinTypes, mappedGenericPacks, arena);
if (traverse(state, path))
return state.current;
else
return std::nullopt;
}
std::optional<TypeId> traverseForType_DEPRECATED(TypeId root, const Path& path, NotNull<BuiltinTypes> builtinTypes)
{
TraversalState state(follow(root), builtinTypes, nullptr, nullptr);
if (traverse(state, path)) if (traverse(state, path))
{ {
auto ty = get<TypeId>(state.current); auto ty = get<TypeId>(state.current);
@ -922,9 +1055,15 @@ std::optional<TypeId> traverseForType(TypeId root, const Path& path, NotNull<Bui
return std::nullopt; return std::nullopt;
} }
std::optional<TypeId> traverseForType(TypePackId root, const Path& path, NotNull<BuiltinTypes> builtinTypes) std::optional<TypeId> traverseForType(
TypeId root,
const Path& path,
NotNull<BuiltinTypes> builtinTypes,
NotNull<const DenseHashMap<TypePackId, TypePackId>> mappedGenericPacks,
NotNull<TypeArena> arena
)
{ {
TraversalState state(follow(root), builtinTypes); TraversalState state(follow(root), builtinTypes, mappedGenericPacks, arena);
if (traverse(state, path)) if (traverse(state, path))
{ {
auto ty = get<TypeId>(state.current); auto ty = get<TypeId>(state.current);
@ -934,9 +1073,39 @@ std::optional<TypeId> traverseForType(TypePackId root, const Path& path, NotNull
return std::nullopt; return std::nullopt;
} }
std::optional<TypePackId> traverseForPack(TypeId root, const Path& path, NotNull<BuiltinTypes> builtinTypes) std::optional<TypeId> traverseForType_DEPRECATED(TypePackId root, const Path& path, NotNull<BuiltinTypes> builtinTypes)
{ {
TraversalState state(follow(root), builtinTypes); TraversalState state(follow(root), builtinTypes, nullptr, nullptr);
if (traverse(state, path))
{
auto ty = get<TypeId>(state.current);
return ty ? std::make_optional(*ty) : std::nullopt;
}
else
return std::nullopt;
}
std::optional<TypeId> traverseForType(
TypePackId root,
const Path& path,
NotNull<BuiltinTypes> builtinTypes,
NotNull<const DenseHashMap<TypePackId, TypePackId>> mappedGenericPacks,
NotNull<TypeArena> arena
)
{
TraversalState state(follow(root), builtinTypes, mappedGenericPacks, arena);
if (traverse(state, path))
{
auto ty = get<TypeId>(state.current);
return ty ? std::make_optional(*ty) : std::nullopt;
}
else
return std::nullopt;
}
std::optional<TypePackId> traverseForPack_DEPRECATED(TypeId root, const Path& path, NotNull<BuiltinTypes> builtinTypes)
{
TraversalState state(follow(root), builtinTypes, nullptr, nullptr);
if (traverse(state, path)) if (traverse(state, path))
{ {
auto ty = get<TypePackId>(state.current); auto ty = get<TypePackId>(state.current);
@ -946,9 +1115,15 @@ std::optional<TypePackId> traverseForPack(TypeId root, const Path& path, NotNull
return std::nullopt; return std::nullopt;
} }
std::optional<TypePackId> traverseForPack(TypePackId root, const Path& path, NotNull<BuiltinTypes> builtinTypes) std::optional<TypePackId> traverseForPack(
TypeId root,
const Path& path,
NotNull<BuiltinTypes> builtinTypes,
NotNull<const DenseHashMap<TypePackId, TypePackId>> mappedGenericPacks,
NotNull<TypeArena> arena
)
{ {
TraversalState state(follow(root), builtinTypes); TraversalState state(follow(root), builtinTypes, mappedGenericPacks, arena);
if (traverse(state, path)) if (traverse(state, path))
{ {
auto ty = get<TypePackId>(state.current); auto ty = get<TypePackId>(state.current);
@ -958,4 +1133,61 @@ std::optional<TypePackId> traverseForPack(TypePackId root, const Path& path, Not
return std::nullopt; return std::nullopt;
} }
std::optional<TypePackId> traverseForPack_DEPRECATED(TypePackId root, const Path& path, NotNull<BuiltinTypes> builtinTypes)
{
TraversalState state(follow(root), builtinTypes, nullptr, nullptr);
if (traverse(state, path))
{
auto ty = get<TypePackId>(state.current);
return ty ? std::make_optional(*ty) : std::nullopt;
}
else
return std::nullopt;
}
std::optional<TypePackId> traverseForPack(
TypePackId root,
const Path& path,
NotNull<BuiltinTypes> builtinTypes,
NotNull<const DenseHashMap<TypePackId, TypePackId>> mappedGenericPacks,
NotNull<TypeArena> arena
)
{
TraversalState state(follow(root), builtinTypes, mappedGenericPacks, arena);
if (traverse(state, path))
{
auto ty = get<TypePackId>(state.current);
return ty ? std::make_optional(*ty) : std::nullopt;
}
else
return std::nullopt;
}
std::optional<size_t> 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<Luau::TypePath::PackSlice>(&*componentIter))
{
index += packSlice->start_index;
}
else
{
return std::nullopt;
}
++componentIter;
}
if (const auto indexComponent = get_if<TypePath::Index>(&*componentIter))
{
index += indexComponent->index;
return index;
}
return std::nullopt;
}
} // namespace Luau } // namespace Luau

View file

@ -381,9 +381,6 @@ Unifier::Unifier(NotNull<Normalizer> normalizer, NotNull<Scope> scope, const Loc
, sharedState(*normalizer->sharedState) , sharedState(*normalizer->sharedState)
{ {
LUAU_ASSERT(sharedState.iceHandler); 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) void Unifier::tryUnify(TypeId subTy, TypeId superTy, bool isFunctionCall, bool isIntersection, const LiteralProperties* literalProperties)

View file

@ -20,6 +20,7 @@
LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauEnableWriteOnlyProperties) LUAU_FASTFLAG(LuauEnableWriteOnlyProperties)
LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps) LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
LUAU_FASTFLAG(LuauRefineTablesWithReadType) 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`. // returns `true` if `ty` is irressolvable and should be added to `incompleteSubtypes`.
static bool isIrresolvable(TypeId ty) static bool isIrresolvable(TypeId ty)
{ {
if (FFlag::LuauStuckTypeFunctionsStillDispatch)
{
if (auto tfit = get<TypeFunctionInstanceType>(ty); tfit && tfit->state != TypeFunctionInstanceState::Unsolved)
return false;
}
return get<BlockedType>(ty) || get<TypeFunctionInstanceType>(ty); return get<BlockedType>(ty) || get<TypeFunctionInstanceType>(ty);
} }

View file

@ -20,7 +20,6 @@ LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
LUAU_FASTFLAGVARIABLE(LuauSolverV2) LUAU_FASTFLAGVARIABLE(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauDeclareExternType) LUAU_FASTFLAGVARIABLE(LuauDeclareExternType)
LUAU_FASTFLAGVARIABLE(LuauParseStringIndexer) LUAU_FASTFLAGVARIABLE(LuauParseStringIndexer)
LUAU_FASTFLAGVARIABLE(LuauStoreLocalAnnotationColonPositions)
LUAU_FASTFLAGVARIABLE(LuauCSTForReturnTypeFunctionTail) LUAU_FASTFLAGVARIABLE(LuauCSTForReturnTypeFunctionTail)
LUAU_FASTFLAGVARIABLE(LuauParseAttributeFixUninit) LUAU_FASTFLAGVARIABLE(LuauParseAttributeFixUninit)
LUAU_DYNAMIC_FASTFLAGVARIABLE(DebugLuauReportReturnTypeVariadicWithTypeSuffix, false) LUAU_DYNAMIC_FASTFLAGVARIABLE(DebugLuauReportReturnTypeVariadicWithTypeSuffix, false)
@ -691,11 +690,7 @@ AstStat* Parser::parseFor()
allocator.alloc<AstStatForIn>(Location(start, end), copy(vars), copy(values), body, hasIn, inLocation, hasDo, matchDo.location); allocator.alloc<AstStatForIn>(Location(start, end), copy(vars), copy(values), body, hasIn, inLocation, hasDo, matchDo.location);
if (options.storeCstData) if (options.storeCstData)
{ {
if (FFlag::LuauStoreLocalAnnotationColonPositions) cstNodeMap[node] = allocator.alloc<CstStatForIn>(extractAnnotationColonPositions(names), varsCommaPosition, copy(valuesCommaPositions));
cstNodeMap[node] =
allocator.alloc<CstStatForIn>(extractAnnotationColonPositions(names), varsCommaPosition, copy(valuesCommaPositions));
else
cstNodeMap[node] = allocator.alloc<CstStatForIn>(AstArray<Position>{}, varsCommaPosition, copy(valuesCommaPositions));
} }
return node; return node;
} }
@ -1019,11 +1014,7 @@ AstStat* Parser::parseLocal(const AstArray<AstAttr*>& attributes)
AstStatLocal* node = allocator.alloc<AstStatLocal>(Location(start, end), copy(vars), copy(values), equalsSignLocation); AstStatLocal* node = allocator.alloc<AstStatLocal>(Location(start, end), copy(vars), copy(values), equalsSignLocation);
if (options.storeCstData) if (options.storeCstData)
{ {
if (FFlag::LuauStoreLocalAnnotationColonPositions) cstNodeMap[node] = allocator.alloc<CstStatLocal>(extractAnnotationColonPositions(names), varsCommaPositions, copy(valuesCommaPositions));
cstNodeMap[node] =
allocator.alloc<CstStatLocal>(extractAnnotationColonPositions(names), varsCommaPositions, copy(valuesCommaPositions));
else
cstNodeMap[node] = allocator.alloc<CstStatLocal>(AstArray<Position>{}, varsCommaPositions, copy(valuesCommaPositions));
} }
return node; return node;
@ -1608,17 +1599,11 @@ std::pair<AstExprFunction*, AstLocal*> Parser::parseFunctionBody(
if (lexer.current().type != ')') if (lexer.current().type != ')')
{ {
if (FFlag::LuauStoreLocalAnnotationColonPositions) if (cstNode)
{
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
std::tie(vararg, varargLocation, varargAnnotation) = 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<Location> argLocation; std::optional<Location> argLocation;
@ -1676,8 +1661,7 @@ std::pair<AstExprFunction*, AstLocal*> Parser::parseFunctionBody(
if (options.storeCstData) if (options.storeCstData)
{ {
cstNode->functionKeywordPosition = matchFunction.location.begin; cstNode->functionKeywordPosition = matchFunction.location.begin;
if (FFlag::LuauStoreLocalAnnotationColonPositions) cstNode->argsAnnotationColonPositions = extractAnnotationColonPositions(args);
cstNode->argsAnnotationColonPositions = extractAnnotationColonPositions(args);
cstNodeMap[node] = cstNode; cstNodeMap[node] = cstNode;
} }
@ -1716,7 +1700,7 @@ Parser::Binding Parser::parseBinding()
Position colonPosition = lexer.current().location.begin; Position colonPosition = lexer.current().location.begin;
AstType* annotation = parseOptionalType(); AstType* annotation = parseOptionalType();
if (FFlag::LuauStoreLocalAnnotationColonPositions && options.storeCstData) if (options.storeCstData)
return Binding(*name, annotation, colonPosition); return Binding(*name, annotation, colonPosition);
else else
return Binding(*name, annotation); return Binding(*name, annotation);
@ -1724,7 +1708,6 @@ Parser::Binding Parser::parseBinding()
AstArray<Position> Parser::extractAnnotationColonPositions(const TempVector<Binding>& bindings) AstArray<Position> Parser::extractAnnotationColonPositions(const TempVector<Binding>& bindings)
{ {
LUAU_ASSERT(FFlag::LuauStoreLocalAnnotationColonPositions);
TempVector<Position> annotationColonPositions(scratchPosition); TempVector<Position> annotationColonPositions(scratchPosition);
for (size_t i = 0; i < bindings.size(); ++i) for (size_t i = 0; i < bindings.size(); ++i)
annotationColonPositions.push_back(bindings[i].colonPosition); annotationColonPositions.push_back(bindings[i].colonPosition);
@ -1755,7 +1738,7 @@ std::tuple<bool, Location, AstTypePack*> Parser::parseBindingList(
AstTypePack* tailAnnotation = nullptr; AstTypePack* tailAnnotation = nullptr;
if (lexer.current().type == ':') if (lexer.current().type == ':')
{ {
if (FFlag::LuauStoreLocalAnnotationColonPositions && varargAnnotationColonPosition) if (varargAnnotationColonPosition)
*varargAnnotationColonPosition = lexer.current().location.begin; *varargAnnotationColonPosition = lexer.current().location.begin;
nextLexeme(); nextLexeme();

View file

@ -20,7 +20,7 @@ struct ConstraintGeneratorFixture : Fixture
ModulePtr mainModule; ModulePtr mainModule;
DcrLogger logger; DcrLogger logger;
UnifierSharedState sharedState{&ice}; UnifierSharedState sharedState{&ice};
Normalizer normalizer{&arena, getBuiltins(), NotNull{&sharedState}}; Normalizer normalizer{&arena, getBuiltins(), NotNull{&sharedState}, SolverMode::New};
SimplifierPtr simplifier; SimplifierPtr simplifier;
TypeCheckLimits limits; TypeCheckLimits limits;
TypeFunctionRuntime typeFunctionRuntime{NotNull{&ice}, NotNull{&limits}}; TypeFunctionRuntime typeFunctionRuntime{NotNull{&ice}, NotNull{&limits}};

View file

@ -555,6 +555,18 @@ TypeId Fixture::requireExportedType(const ModuleName& moduleName, const std::str
return it->second.type; 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) std::string Fixture::decorateWithTypes(const std::string& code)
{ {
fileResolver.source[mainModuleName] = code; fileResolver.source[mainModuleName] = code;

View file

@ -2,6 +2,7 @@
#pragma once #pragma once
#include "Luau/Config.h" #include "Luau/Config.h"
#include "Luau/EqSatSimplification.h"
#include "Luau/Error.h" #include "Luau/Error.h"
#include "Luau/FileResolver.h" #include "Luau/FileResolver.h"
#include "Luau/Frontend.h" #include "Luau/Frontend.h"
@ -145,6 +146,8 @@ struct Fixture
TypeId requireTypeAlias(const std::string& name); TypeId requireTypeAlias(const std::string& name);
TypeId requireExportedType(const ModuleName& moduleName, 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 // 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. // Most often those are changes related to builtin type definitions.
// In that case, flag can be forced to 'true' using the example below: // In that case, flag can be forced to 'true' using the example below:
@ -187,6 +190,9 @@ protected:
bool forAutocomplete = false; bool forAutocomplete = false;
std::optional<Frontend> frontend; std::optional<Frontend> frontend;
BuiltinTypes* builtinTypes = nullptr; BuiltinTypes* builtinTypes = nullptr;
TypeArena simplifierArena;
SimplifierPtr simplifier{nullptr, nullptr};
}; };
struct BuiltinsFixture : Fixture struct BuiltinsFixture : Fixture

View file

@ -7,6 +7,7 @@
#include "Luau/Autocomplete.h" #include "Luau/Autocomplete.h"
#include "Luau/BuiltinDefinitions.h" #include "Luau/BuiltinDefinitions.h"
#include "Luau/Common.h" #include "Luau/Common.h"
#include "Luau/FileResolver.h"
#include "Luau/Frontend.h" #include "Luau/Frontend.h"
#include "Luau/AutocompleteTypes.h" #include "Luau/AutocompleteTypes.h"
#include "Luau/ToString.h" #include "Luau/ToString.h"
@ -32,6 +33,8 @@ LUAU_FASTFLAG(LuauFragmentAcMemoryLeak)
LUAU_FASTFLAG(LuauGlobalVariableModuleIsolation) LUAU_FASTFLAG(LuauGlobalVariableModuleIsolation)
LUAU_FASTFLAG(LuauFragmentAutocompleteIfRecommendations) LUAU_FASTFLAG(LuauFragmentAutocompleteIfRecommendations)
LUAU_FASTFLAG(LuauPopulateRefinedTypesInFragmentFromOldSolver) LUAU_FASTFLAG(LuauPopulateRefinedTypesInFragmentFromOldSolver)
LUAU_FASTFLAG(LuauSolverAgnosticStringification)
LUAU_FASTFLAG(LuauFragmentRequiresCanBeResolvedToAModule)
static std::optional<AutocompleteEntryMap> nullCallback(std::string tag, std::optional<const ExternType*> ptr, std::optional<std::string> contents) static std::optional<AutocompleteEntryMap> nullCallback(std::string tag, std::optional<const ExternType*> ptr, std::optional<std::string> contents)
{ {
@ -53,7 +56,7 @@ static FrontendOptions getOptions()
static ModuleResolver& getModuleResolver(Frontend& frontend) static ModuleResolver& getModuleResolver(Frontend& frontend)
{ {
return FFlag::LuauSolverV2 ?frontend.moduleResolver : frontend.moduleResolverForAutocomplete; return FFlag::LuauSolverV2 ? frontend.moduleResolver : frontend.moduleResolverForAutocomplete;
} }
template<class BaseType> template<class BaseType>
@ -157,6 +160,7 @@ struct FragmentAutocompleteFixtureImpl : BaseType
) )
{ {
ScopedFastFlag sff{FFlag::LuauSolverV2, true}; ScopedFastFlag sff{FFlag::LuauSolverV2, true};
this->getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::New);
this->check(document, getOptions()); this->check(document, getOptions());
FragmentAutocompleteStatusResult result = autocompleteFragment(updated, cursorPos, fragmentEndPosition); FragmentAutocompleteStatusResult result = autocompleteFragment(updated, cursorPos, fragmentEndPosition);
@ -173,6 +177,7 @@ struct FragmentAutocompleteFixtureImpl : BaseType
) )
{ {
ScopedFastFlag sff{FFlag::LuauSolverV2, false}; ScopedFastFlag sff{FFlag::LuauSolverV2, false};
this->getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::Old);
this->check(document, getOptions()); this->check(document, getOptions());
FragmentAutocompleteStatusResult result = autocompleteFragment(updated, cursorPos, fragmentEndPosition); FragmentAutocompleteStatusResult result = autocompleteFragment(updated, cursorPos, fragmentEndPosition);
@ -189,6 +194,7 @@ struct FragmentAutocompleteFixtureImpl : BaseType
) )
{ {
ScopedFastFlag sff{FFlag::LuauSolverV2, true}; ScopedFastFlag sff{FFlag::LuauSolverV2, true};
this->getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::New);
this->check(document, getOptions()); this->check(document, getOptions());
FragmentAutocompleteStatusResult result = autocompleteFragment(updated, cursorPos, fragmentEndPosition); FragmentAutocompleteStatusResult result = autocompleteFragment(updated, cursorPos, fragmentEndPosition);
@ -196,6 +202,7 @@ struct FragmentAutocompleteFixtureImpl : BaseType
assertions(result); assertions(result);
ScopedFastFlag _{FFlag::LuauSolverV2, false}; ScopedFastFlag _{FFlag::LuauSolverV2, false};
this->getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::Old);
this->check(document, getOptions()); this->check(document, getOptions());
result = autocompleteFragment(updated, cursorPos, fragmentEndPosition); result = autocompleteFragment(updated, cursorPos, fragmentEndPosition);
@ -1317,7 +1324,7 @@ abc("bar")
CHECK_EQ(Position{3, 1}, parent->location.end); 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(); DOES_NOT_PASS_NEW_SOLVER_GUARD();
@ -1329,7 +1336,7 @@ t
FrontendOptions opts; FrontendOptions opts;
opts.forAutocomplete = true; opts.forAutocomplete = true;
getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
getFrontend().check("game/A", opts); getFrontend().check("game/A", opts);
CHECK_NE(getFrontend().moduleResolverForAutocomplete.getModule("game/A"), nullptr); CHECK_NE(getFrontend().moduleResolverForAutocomplete.getModule("game/A"), nullptr);
CHECK_EQ(getFrontend().moduleResolver.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") TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "mixed_mode_basic_example_append")
{ {
ScopedFastFlag sff{FFlag::LuauSolverV2, false}; ScopedFastFlag sff{FFlag::LuauSolverV2, false};
getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
auto res = checkOldSolver( auto res = checkOldSolver(
R"( R"(
local x = 4 local x = 4
@ -1437,6 +1445,7 @@ local z = x + y
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "mixed_mode_basic_example_inlined") TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "mixed_mode_basic_example_inlined")
{ {
ScopedFastFlag sff{FFlag::LuauSolverV2, false}; ScopedFastFlag sff{FFlag::LuauSolverV2, false};
getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
auto res = checkOldSolver( auto res = checkOldSolver(
R"( R"(
local x = 4 local x = 4
@ -1461,6 +1470,7 @@ local y = 5
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "mixed_mode_can_autocomplete_simple_property_access") TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "mixed_mode_can_autocomplete_simple_property_access")
{ {
ScopedFastFlag sff{FFlag::LuauSolverV2, false}; ScopedFastFlag sff{FFlag::LuauSolverV2, false};
getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
auto res = checkOldSolver( auto res = checkOldSolver(
R"( R"(
local tbl = { abc = 1234} local tbl = { abc = 1234}
@ -1521,6 +1531,7 @@ TEST_SUITE_BEGIN("FragmentAutocompleteTests");
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "multiple_fragment_autocomplete") TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "multiple_fragment_autocomplete")
{ {
ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true};
ToStringOptions opt; ToStringOptions opt;
opt.exhaustive = true; opt.exhaustive = true;
opt.exhaustive = true; opt.exhaustive = true;
@ -1574,12 +1585,14 @@ return module)";
{ {
ScopedFastFlag sff{FFlag::LuauSolverV2, false}; ScopedFastFlag sff{FFlag::LuauSolverV2, false};
checkAndExamine(source, "module", "{ }"); getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::Old);
fragmentACAndCheck(updated1, Position{1, 17}, "module", "{ }", "{ a: (%error-id%: unknown) -> () }"); checkAndExamine(source, "module", "{| |}");
fragmentACAndCheck(updated2, Position{1, 18}, "module", "{ }", "{ ab: (%error-id%: unknown) -> () }"); 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}; ScopedFastFlag sff{FFlag::LuauSolverV2, true};
getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::New);
checkAndExamine(source, "module", "{ }"); checkAndExamine(source, "module", "{ }");
// [TODO] CLI-140762 Fragment autocomplete still doesn't return correct result when LuauSolverV2 is on // [TODO] CLI-140762 Fragment autocomplete still doesn't return correct result when LuauSolverV2 is on
return; return;
@ -2895,6 +2908,7 @@ end)
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "fragment_autocomplete_ensures_memory_isolation") TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "fragment_autocomplete_ensures_memory_isolation")
{ {
ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true};
ToStringOptions opt; ToStringOptions opt;
opt.exhaustive = true; opt.exhaustive = true;
opt.exhaustive = true; opt.exhaustive = true;
@ -2945,7 +2959,8 @@ return module)";
{ {
ScopedFastFlag sff{FFlag::LuauSolverV2, false}; 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 // [TODO] CLI-140762 we shouldn't mutate stale module in autocompleteFragment
// early return since the following checking will fail, which it shouldn't! // early return since the following checking will fail, which it shouldn't!
fragmentACAndCheck(updated1, Position{1, 17}, "module"); fragmentACAndCheck(updated1, Position{1, 17}, "module");
@ -2954,6 +2969,7 @@ return module)";
{ {
ScopedFastFlag sff{FFlag::LuauSolverV2, true}; ScopedFastFlag sff{FFlag::LuauSolverV2, true};
getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::New);
checkAndExamine(source, "module", "{ }"); checkAndExamine(source, "module", "{ }");
// [TODO] CLI-140762 we shouldn't mutate stale module in autocompleteFragment // [TODO] CLI-140762 we shouldn't mutate stale module in autocompleteFragment
// early return since the following checking will fail, which it shouldn't! // 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) // NOLINTEND(bugprone-unchecked-optional-access)
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -1359,7 +1359,7 @@ TEST_CASE_FIXTURE(FrontendFixture, "separate_caches_for_autocomplete")
FrontendOptions opts; FrontendOptions opts;
opts.forAutocomplete = true; opts.forAutocomplete = true;
getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
getFrontend().check("game/A", opts); getFrontend().check("game/A", opts);
CHECK(nullptr == getFrontend().moduleResolver.getModule("game/A")); 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") TEST_CASE_FIXTURE(FrontendFixture, "test_invalid_dependency_tracking_per_module_resolver")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, false}; 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/A"] = "return {hello=5, world=true}";
fileResolver.source["game/Gui/Modules/B"] = "return require(game:GetService('Gui').Modules.A)"; fileResolver.source["game/Gui/Modules/B"] = "return require(game:GetService('Gui').Modules.A)";

View file

@ -15,9 +15,8 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTINT(LuauNormalizeIntersectionLimit) LUAU_FASTINT(LuauNormalizeIntersectionLimit)
LUAU_FASTINT(LuauNormalizeUnionLimit) LUAU_FASTINT(LuauNormalizeUnionLimit)
LUAU_FASTFLAG(LuauSimplifyOutOfLine2) LUAU_FASTFLAG(LuauSimplifyOutOfLine2)
LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2)
LUAU_FASTFLAG(LuauNormalizationReorderFreeTypeIntersect) LUAU_FASTFLAG(LuauNormalizationReorderFreeTypeIntersect)
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping)
using namespace Luau; using namespace Luau;
namespace namespace
@ -34,7 +33,15 @@ struct IsSubtypeFixture : Fixture
SimplifierPtr simplifier = newSimplifier(NotNull{&module->internalTypes}, getBuiltins()); 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 } // namespace
@ -447,7 +454,7 @@ struct NormalizeFixture : Fixture
TypeArena arena; TypeArena arena;
InternalErrorReporter iceHandler; InternalErrorReporter iceHandler;
UnifierSharedState unifierState{&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}; Scope globalScope{getBuiltins()->anyTypePack};
NormalizeFixture() NormalizeFixture()
@ -1216,7 +1223,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_union_type_pack_cycle")
ScopedFastFlag sff[] = { ScopedFastFlag sff[] = {
{FFlag::LuauSolverV2, true}, {FFlag::LuauSolverV2, true},
{FFlag::LuauSimplifyOutOfLine2, true}, {FFlag::LuauSimplifyOutOfLine2, true},
{FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2, true}, {FFlag::LuauReturnMappedGenericPacksFromSubtyping, true},
}; };
ScopedFastInt sfi{FInt::LuauTypeInferRecursionLimit, 0}; ScopedFastInt sfi{FInt::LuauTypeInferRecursionLimit, 0};

View file

@ -17,6 +17,7 @@
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping)
using namespace Luau; using namespace Luau;
@ -67,7 +68,7 @@ struct SubtypeFixture : Fixture
InternalErrorReporter iceReporter; InternalErrorReporter iceReporter;
UnifierSharedState sharedState{&ice}; UnifierSharedState sharedState{&ice};
SimplifierPtr simplifier = newSimplifier(NotNull{&arena}, getBuiltins()); 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; TypeCheckLimits limits;
TypeFunctionRuntime typeFunctionRuntime{NotNull{&iceReporter}, NotNull{&limits}}; TypeFunctionRuntime typeFunctionRuntime{NotNull{&iceReporter}, NotNull{&limits}};
@ -1386,6 +1387,8 @@ TEST_CASE_FIXTURE(SubtypeFixture, "<T>({ x: T }) -> T <: ({ method: <T>({ x: T }
TEST_CASE_FIXTURE(SubtypeFixture, "subtyping_reasonings_to_follow_a_reduced_type_function_instance") TEST_CASE_FIXTURE(SubtypeFixture, "subtyping_reasonings_to_follow_a_reduced_type_function_instance")
{ {
ScopedFastFlag sff{FFlag::LuauReturnMappedGenericPacksFromSubtyping, true};
TypeId longTy = arena.addType(UnionType{ TypeId longTy = arena.addType(UnionType{
{getBuiltins()->booleanType, {getBuiltins()->booleanType,
getBuiltins()->bufferType, getBuiltins()->bufferType,
@ -1408,8 +1411,10 @@ TEST_CASE_FIXTURE(SubtypeFixture, "subtyping_reasonings_to_follow_a_reduced_type
if (reasoning.subPath.empty() && reasoning.superPath.empty()) if (reasoning.subPath.empty() && reasoning.superPath.empty())
continue; continue;
std::optional<TypeOrPack> optSubLeaf = traverse(subTy, reasoning.subPath, getBuiltins()); std::optional<TypeOrPack> optSubLeaf =
std::optional<TypeOrPack> optSuperLeaf = traverse(superTy, reasoning.superPath, getBuiltins()); traverse(subTy, reasoning.subPath, getBuiltins(), NotNull{&result.mappedGenericPacks}, NotNull{&arena});
std::optional<TypeOrPack> optSuperLeaf =
traverse(superTy, reasoning.superPath, getBuiltins(), NotNull{&result.mappedGenericPacks}, NotNull{&arena});
if (!optSubLeaf || !optSuperLeaf) if (!optSubLeaf || !optSuperLeaf)
CHECK(false); CHECK(false);

View file

@ -16,6 +16,7 @@ LUAU_FASTFLAG(LuauRecursiveTypeParameterRestriction)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauFixEmptyTypePackStringification) LUAU_FASTFLAG(LuauFixEmptyTypePackStringification)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2) LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2)
LUAU_FASTFLAG(LuauSolverAgnosticStringification)
TEST_SUITE_BEGIN("ToString"); TEST_SUITE_BEGIN("ToString");
@ -47,12 +48,22 @@ TEST_CASE_FIXTURE(Fixture, "bound_types")
TEST_CASE_FIXTURE(Fixture, "free_types") TEST_CASE_FIXTURE(Fixture, "free_types")
{ {
ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true};
DOES_NOT_PASS_NEW_SOLVER_GUARD(); DOES_NOT_PASS_NEW_SOLVER_GUARD();
CheckResult result = check("local a"); CheckResult result = check("local a");
LUAU_REQUIRE_NO_ERRORS(result); 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") TEST_CASE_FIXTURE(Fixture, "cyclic_table")

View file

@ -12,7 +12,6 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(LuauStoreLocalAnnotationColonPositions)
LUAU_FASTFLAG(LuauCSTForReturnTypeFunctionTail) LUAU_FASTFLAG(LuauCSTForReturnTypeFunctionTail)
TEST_SUITE_BEGIN("TranspilerTests"); TEST_SUITE_BEGIN("TranspilerTests");
@ -314,9 +313,6 @@ TEST_CASE("function_spaces_around_tokens")
TEST_CASE("function_with_types_spaces_around_tokens") TEST_CASE("function_with_types_spaces_around_tokens")
{ {
ScopedFastFlag sffs[] = {
{FFlag::LuauStoreLocalAnnotationColonPositions, true},
};
std::string code = R"( function p<X, Y, Z...>(o: string, m: number, ...: any): string end )"; std::string code = R"( function p<X, Y, Z...>(o: string, m: number, ...: any): string end )";
CHECK_EQ(code, transpile(code, {}, true).code); 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") TEST_CASE_FIXTURE(Fixture, "transpile_type_annotation_spaces_around_tokens")
{ {
ScopedFastFlag sffs[] = {
{FFlag::LuauStoreLocalAnnotationColonPositions, true},
};
std::string code = R"( local _: Type )"; std::string code = R"( local _: Type )";
CHECK_EQ(code, transpile(code, {}, true).code); 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") 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 )"; std::string code = R"( for i: number = 1, 10 do end )";
CHECK_EQ(code, transpile(code, {}, true).code); CHECK_EQ(code, transpile(code, {}, true).code);

View file

@ -18,6 +18,7 @@ LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauSimplifyOutOfLine2) LUAU_FASTFLAG(LuauSimplifyOutOfLine2)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2) LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2)
LUAU_FASTFLAG(LuauErrorSuppressionTypeFunctionArgs) LUAU_FASTFLAG(LuauErrorSuppressionTypeFunctionArgs)
LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch)
LUAU_FASTFLAG(LuauEmptyStringInKeyOf) LUAU_FASTFLAG(LuauEmptyStringInKeyOf)
struct TypeFunctionFixture : Fixture struct TypeFunctionFixture : Fixture
@ -743,6 +744,11 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_oss_crash_gh1161")
if (!FFlag::LuauSolverV2) if (!FFlag::LuauSolverV2)
return; return;
ScopedFastFlag sff[] = {
{FFlag::LuauEagerGeneralization4, true},
{FFlag::LuauStuckTypeFunctionsStillDispatch, true}
};
CheckResult result = check(R"( CheckResult result = check(R"(
local EnumVariants = { local EnumVariants = {
["a"] = 1, ["b"] = 2, ["c"] = 3 ["a"] = 1, ["b"] = 2, ["c"] = 3
@ -758,9 +764,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_oss_crash_gh1161")
fnB(result) fnB(result)
)"); )");
LUAU_REQUIRE_ERROR_COUNT(2, result); LUAU_CHECK_ERROR_COUNT(1, result);
CHECK(get<ConstraintSolvingIncompleteError>(result.errors[0])); LUAU_CHECK_ERROR(result, FunctionExitsWithoutReturning);
CHECK(get<FunctionExitsWithoutReturning>(result.errors[1]));
} }
TEST_CASE_FIXTURE(TypeFunctionFixture, "fuzzer_numeric_binop_doesnt_assert_on_generalizeFreeType") 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])); 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<add<nil, nil>, *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, B>(a: A, b: B): add<A, B>
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") TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_should_not_assert_on_empty_string_props")
{ {
if (!FFlag::LuauSolverV2) if (!FFlag::LuauSolverV2)
@ -1745,7 +1803,7 @@ struct TFFixture
InternalErrorReporter ice; InternalErrorReporter ice;
UnifierSharedState unifierState{&ice}; UnifierSharedState unifierState{&ice};
SimplifierPtr simplifier = EqSatSimplification::newSimplifier(arena, getBuiltins()); SimplifierPtr simplifier = EqSatSimplification::newSimplifier(arena, getBuiltins());
Normalizer normalizer{arena, getBuiltins(), NotNull{&unifierState}}; Normalizer normalizer{arena, getBuiltins(), NotNull{&unifierState}, SolverMode::New};
TypeCheckLimits limits; TypeCheckLimits limits;
TypeFunctionRuntime runtime{NotNull{&ice}, NotNull{&limits}}; TypeFunctionRuntime runtime{NotNull{&ice}, NotNull{&limits}};
@ -1794,4 +1852,54 @@ TEST_CASE_FIXTURE(TFFixture, "or<'a, 'b>")
CHECK(res.reducedTypes.size() == 1); 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<TypeFunctionInstanceType>(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<TypeFunctionInstanceType>(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<TypeFunctionInstanceType>(outerAddTy);
REQUIRE(tfit);
CHECK(tfit->state == TypeFunctionInstanceState::Stuck);
}
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -14,6 +14,7 @@ LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2)
LUAU_FASTFLAG(LuauUserTypeFunctionAliases) LUAU_FASTFLAG(LuauUserTypeFunctionAliases)
LUAU_FASTFLAG(LuauFollowTypeAlias) LUAU_FASTFLAG(LuauFollowTypeAlias)
LUAU_FASTFLAG(LuauFollowExistingTypeFunction) LUAU_FASTFLAG(LuauFollowExistingTypeFunction)
LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch)
LUAU_FASTFLAG(LuauTypeFunctionSerializeFollowMetatable) LUAU_FASTFLAG(LuauTypeFunctionSerializeFollowMetatable)
TEST_SUITE_BEGIN("UserDefinedTypeFunctionTests"); TEST_SUITE_BEGIN("UserDefinedTypeFunctionTests");
@ -2412,7 +2413,11 @@ TEST_CASE_FIXTURE(ExternTypeFixture, "type_alias_reduction_errors")
if (!FFlag::LuauSolverV2) if (!FFlag::LuauSolverV2)
return; return;
ScopedFastFlag luauUserTypeFunctionAliases{FFlag::LuauUserTypeFunctionAliases, true}; ScopedFastFlag sff[] = {
{FFlag::LuauUserTypeFunctionAliases, true},
{FFlag::LuauEagerGeneralization4, true},
{FFlag::LuauStuckTypeFunctionsStillDispatch, true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
type Test<T, U> = setmetatable<T, U> type Test<T, U> = setmetatable<T, U>
@ -2424,11 +2429,10 @@ end
local function ok(idx: get<>): number return idx 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(4, result);
LUAU_REQUIRE_ERROR_COUNT(5, result);
CHECK( CHECK(
toString(result.errors[1]) == toString(result.errors[1]) ==
R"('get' type function errored at runtime: [string "get"]:5: failed to reduce type function with: Type function instance setmetatable<number, string> is uninhabited)" R"(Type function instance get<> is uninhabited)"
); );
} }

View file

@ -19,6 +19,7 @@ LUAU_FASTFLAG(LuauWriteOnlyPropertyMangling)
LUAU_FASTFLAG(LuauEnableWriteOnlyProperties) LUAU_FASTFLAG(LuauEnableWriteOnlyProperties)
LUAU_FASTFLAG(LuauTableLiteralSubtypeCheckFunctionCalls) LUAU_FASTFLAG(LuauTableLiteralSubtypeCheckFunctionCalls)
LUAU_FASTFLAG(LuauTypeCheckerStricterIndexCheck) LUAU_FASTFLAG(LuauTypeCheckerStricterIndexCheck)
LUAU_FASTFLAG(LuauSuppressErrorsForMultipleNonviableOverloads)
TEST_SUITE_BEGIN("BuiltinTests"); 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") 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 // This will not result in a real refinement, as we refine `bnot`, a function, to be truthy

View file

@ -28,8 +28,11 @@ LUAU_FASTFLAG(LuauFormatUseLastPosition)
LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType) LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType)
LUAU_FASTFLAG(LuauSimplifyOutOfLine2) LUAU_FASTFLAG(LuauSimplifyOutOfLine2)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2) LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2)
LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch)
LUAU_FASTFLAG(LuauAvoidGenericsLeakingDuringFunctionCallCheck) LUAU_FASTFLAG(LuauAvoidGenericsLeakingDuringFunctionCallCheck)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps) LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
LUAU_FASTFLAG(LuauSolverAgnosticStringification)
LUAU_FASTFLAG(LuauSuppressErrorsForMultipleNonviableOverloads)
TEST_SUITE_BEGIN("TypeInferFunctions"); 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") TEST_CASE_FIXTURE(Fixture, "list_only_alternative_overloads_that_match_argument_count")
{ {
ScopedFastFlag _{FFlag::LuauSuppressErrorsForMultipleNonviableOverloads, true};
CheckResult result = check(R"( CheckResult result = check(R"(
local multiply: ((number)->number) & ((number)->string) & ((number, number)->number) local multiply: ((number)->number) & ((number)->string) & ((number, number)->number)
multiply("") multiply("")
@ -268,9 +273,9 @@ TEST_CASE_FIXTURE(Fixture, "list_only_alternative_overloads_that_match_argument_
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
{ {
GenericError* g = get<GenericError>(result.errors[0]); MultipleNonviableOverloads* mno = get<MultipleNonviableOverloads>(result.errors[0]);
REQUIRE(g); REQUIRE_MESSAGE(mno, "Expected MultipleNonviableOverloads but got " << result.errors[0]);
CHECK(g->message == "None of the overloads for function that accept 1 arguments are compatible."); CHECK_EQ(mno->attemptedArgCount, 1);
} }
else else
{ {
@ -1388,6 +1393,7 @@ f(function(x) return x * 2 end)
TEST_CASE_FIXTURE(BuiltinsFixture, "infer_generic_function_function_argument") 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 // FIXME: CLI-116133 bidirectional type inference needs to push expected types in for higher-order function calls
DOES_NOT_PASS_NEW_SOLVER_GUARD(); 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); 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") 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") TEST_CASE_FIXTURE(BuiltinsFixture, "function_decl_non_self_sealed_overwrite_2")
{ {
ScopedFastFlag sff{FFlag::LuauStuckTypeFunctionsStillDispatch, true};
CheckResult result = check(R"( CheckResult result = check(R"(
local t: { f: ((x: number) -> number)? } = {} local t: { f: ((x: number) -> number)? } = {}
@ -1706,9 +1714,7 @@ end
if (FFlag::LuauEagerGeneralization4 && FFlag::LuauSolverV2) if (FFlag::LuauEagerGeneralization4 && FFlag::LuauSolverV2)
{ {
// FIXME CLI-151985 LUAU_CHECK_ERROR_COUNT(2, result);
LUAU_CHECK_ERROR_COUNT(3, result);
LUAU_CHECK_ERROR(result, ConstraintSolvingIncompleteError);
LUAU_CHECK_ERROR(result, WhereClauseNeeded); // x2 LUAU_CHECK_ERROR(result, WhereClauseNeeded); // x2
} }
else if (FFlag::LuauSolverV2) 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") TEST_CASE_FIXTURE(BuiltinsFixture, "function_decl_non_self_unsealed_overwrite")
{ {
ScopedFastFlag sff{FFlag::LuauStuckTypeFunctionsStillDispatch, true};
CheckResult result = check(R"( CheckResult result = check(R"(
local t = { f = nil :: ((x: number) -> number)? } local t = { f = nil :: ((x: number) -> number)? }
@ -1791,9 +1799,7 @@ end
if (FFlag::LuauEagerGeneralization4 && FFlag::LuauSolverV2) if (FFlag::LuauEagerGeneralization4 && FFlag::LuauSolverV2)
{ {
// FIXME CLI-151985 LUAU_CHECK_ERROR_COUNT(1, result);
LUAU_CHECK_ERROR_COUNT(2, result);
LUAU_CHECK_ERROR(result, ConstraintSolvingIncompleteError);
LUAU_CHECK_ERROR(result, WhereClauseNeeded); LUAU_CHECK_ERROR(result, WhereClauseNeeded);
} }
else if (FFlag::LuauSolverV2) else if (FFlag::LuauSolverV2)
@ -2669,10 +2675,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tf_suggest_arg_type_2")
end end
)"); )");
LUAU_REQUIRE_ERRORS(result); LUAU_REQUIRE_ERROR(result, NotATable);
auto err = get<NotATable>(result.errors.back());
REQUIRE(err);
CHECK("a" == toString(err->ty));
} }
TEST_CASE_FIXTURE(Fixture, "local_function_fwd_decl_doesnt_crash") TEST_CASE_FIXTURE(Fixture, "local_function_fwd_decl_doesnt_crash")

View file

@ -1,9 +1,6 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // 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/TypeInfer.h"
#include "Luau/Type.h" #include "Luau/Type.h"
#include "Luau/Scope.h"
#include <algorithm>
#include "Fixture.h" #include "Fixture.h"
@ -13,12 +10,13 @@
LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions) LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions)
LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2) LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2)
LUAU_FASTFLAG(LuauIntersectNotNil) LUAU_FASTFLAG(LuauIntersectNotNil)
LUAU_FASTFLAG(LuauSubtypingCheckFunctionGenericCounts) LUAU_FASTFLAG(LuauSubtypingCheckFunctionGenericCounts)
LUAU_FASTFLAG(LuauReportSubtypingErrors) LUAU_FASTFLAG(LuauReportSubtypingErrors)
LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps) LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
using namespace Luau; using namespace Luau;
@ -1006,7 +1004,7 @@ local TheDispatcher: Dispatcher = {
TEST_CASE_FIXTURE(Fixture, "generic_argument_count_too_few") TEST_CASE_FIXTURE(Fixture, "generic_argument_count_too_few")
{ {
ScopedFastFlag sff{FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2, true}; ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping, true};
CheckResult result = check(R"( CheckResult result = check(R"(
function test(a: number) function test(a: number)
@ -1024,8 +1022,7 @@ wrapper(test)
{ {
const CountMismatch* cm = get<CountMismatch>(result.errors[0]); const CountMismatch* cm = get<CountMismatch>(result.errors[0]);
REQUIRE_MESSAGE(cm, "Expected CountMismatch but got " << result.errors[0]); REQUIRE_MESSAGE(cm, "Expected CountMismatch but got " << result.errors[0]);
// TODO: CLI-152070 fix to expect 2 CHECK_EQ(cm->expected, 2);
CHECK_EQ(cm->expected, 1);
CHECK_EQ(cm->actual, 1); CHECK_EQ(cm->actual, 1);
CHECK_EQ(cm->context, CountMismatch::Arg); CHECK_EQ(cm->context, CountMismatch::Arg);
} }
@ -1035,7 +1032,7 @@ wrapper(test)
TEST_CASE_FIXTURE(Fixture, "generic_argument_count_too_many") TEST_CASE_FIXTURE(Fixture, "generic_argument_count_too_many")
{ {
DOES_NOT_PASS_NEW_SOLVER_GUARD(); ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping, true};
CheckResult result = check(R"( CheckResult result = check(R"(
function test2(a: number, b: string) function test2(a: number, b: string)
@ -1049,7 +1046,16 @@ wrapper(test2, 1, "", 3)
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); 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<CountMismatch>(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") 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") TEST_CASE_FIXTURE(Fixture, "generic_argument_pack_type_inferred_from_return")
{ {
ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping, true};
CheckResult result = check(R"( CheckResult result = check(R"(
function test2(a: number) function test2(a: number)
return "hello" return "hello"
@ -1081,21 +1089,24 @@ end
wrapper(test2, 1) wrapper(test2, 1)
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
{ {
// TODO: CLI-152070 should expect a TypeMismatch, rather than not erroring const TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
LUAU_REQUIRE_NO_ERRORS(result); REQUIRE_MESSAGE(tm, "Expected TypeMismatch but got " << result.errors[0]);
CHECK_EQ(toString(tm->wantedType), "string");
CHECK_EQ(toString(tm->givenType), "number");
} }
else else
{ {
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(toString(result.errors[0]), R"(Type 'number' could not be converted into 'string')"); 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") 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"( CheckResult result = check(R"(
function test2(a: number) function test2(a: number)
@ -1111,6 +1122,64 @@ wrapper(test2, "hello")
LUAU_REQUIRE_NO_ERRORS(result); 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<B...>(f: (B...) -> number, ...: B...)
return f(...)
end
-- want A... to contain a generic type pack too
function wrapper<A...>(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<CountMismatch>(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<CountMismatch>(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<TypeMismatch>(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") TEST_CASE_FIXTURE(Fixture, "generic_function")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
@ -1446,6 +1515,9 @@ TEST_CASE_FIXTURE(Fixture, "infer_generic_function_function_argument_overloaded"
LUAU_REQUIRE_NO_ERRORS(result); 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") TEST_CASE_FIXTURE(BuiltinsFixture, "do_not_infer_generic_functions")
{ {
ScopedFastFlag _[] = { ScopedFastFlag _[] = {
@ -1461,15 +1533,20 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "do_not_infer_generic_functions")
local function sum<a>(x: a, y: a, f: (a, a) -> a) return f(x, y) end local function sum<a>(x: a, y: a, f: (a, a) -> a) return f(x, y) end
local function sumrec(f: typeof(sum)) local function sumrec(f: typeof(sum))
return sum(2, 3, function<T>(a: T, b: T): add<T> return a + b end) return sum(2, 3, function<X>(a: X, b: X): add<X, X> return a + b end)
end end
local b = sumrec(sum) -- ok local b = sumrec(sum) -- ok
local c = sumrec(function(x, y, f) return f(x, y) end) -- type binders are not inferred 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) -> a", toString(requireType("sum"))); if (FFlag::LuauStuckTypeFunctionsStillDispatch) // FIXME CLI-158432
CHECK_EQ("<a>(a, a, (a, a) -> a) -> a", toString(requireTypeAtPosition({7, 29}))); CHECK("add<X, X> | number" == toString(requireType("b")));
else
CHECK("number" == toString(requireType("b")));
CHECK("<a>(a, a, (a, a) -> a) -> a" == toString(requireType("sum")));
CHECK("<a>(a, a, (a, a) -> a) -> a" == toString(requireTypeAtPosition({7, 29})));
} }
else 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);
} }

View file

@ -12,6 +12,7 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2) LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2)
LUAU_FASTFLAG(LuauRefineTablesWithReadType) LUAU_FASTFLAG(LuauRefineTablesWithReadType)
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping)
TEST_SUITE_BEGIN("IntersectionTypes"); TEST_SUITE_BEGIN("IntersectionTypes");
@ -1149,6 +1150,8 @@ could not be converted into
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_4") TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_4")
{ {
ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping, true};
CheckResult result = check(R"( CheckResult result = check(R"(
function f<a...>() function f<a...>()
function g(x : ((a...) -> ()) & ((number,a...) -> number)) function g(x : ((a...) -> ()) & ((number,a...) -> number))
@ -1162,15 +1165,17 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_4")
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
{ {
const std::string expected = "Type\n\t" // TODO: CLI-159120 - this error message is bogus
"'((a...) -> ()) & ((number, a...) -> number)'" const std::string expected =
"\ncould not be converted into\n\t" "Type\n\t"
"'((a...) -> ()) & ((number, a...) -> number)'; \n" "'((a...) -> ()) & ((number, a...) -> number)'"
"this is because \n\t" "\ncould not be converted into\n\t"
" * in the 1st component of the intersection, the function returns is `()` in the former type and `number` in " "'((a...) -> ()) & ((number, a...) -> number)'; \n"
"the latter type, and `()` is not a subtype of `number`\n\t" "this is because \n\t"
" * in the 2nd component of the intersection, the function takes a tail of `a...` and in the 1st component of " " * in the 1st component of the intersection, the function returns is `()` in the former type and `number` in "
"the intersection, the function takes a tail of `a...`, and `a...` is not a supertype of `a...`"; "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])); CHECK(expected == toString(result.errors[0]));
} }
else else

View file

@ -14,7 +14,7 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions) LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions)
LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping)
using namespace Luau; using namespace Luau;
@ -824,7 +824,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cycles_dont_make_everything_any")
TEST_CASE_FIXTURE(BuiltinsFixture, "cross_module_function_mutation") 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"( fileResolver.source["game/A"] = R"(
function test2(a: number, b: string) function test2(a: number, b: string)

View file

@ -19,6 +19,7 @@ LUAU_FASTINT(LuauTypeInferIterationLimit)
LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTINT(LuauTypeInferTypePackLoopLimit) LUAU_FASTINT(LuauTypeInferTypePackLoopLimit)
LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops) LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops)
LUAU_FASTFLAG(LuauSolverAgnosticStringification)
TEST_SUITE_BEGIN("ProvisionalTests"); 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") TEST_CASE_FIXTURE(Fixture, "free_options_cannot_be_unified_together")
{ {
ScopedFastFlag sff_stringification{FFlag::LuauSolverAgnosticStringification, true};
ScopedFastFlag sff{FFlag::LuauSolverV2, false}; ScopedFastFlag sff{FFlag::LuauSolverV2, false};
TypeArena arena; TypeArena arena;
@ -549,7 +551,7 @@ TEST_CASE_FIXTURE(Fixture, "free_options_cannot_be_unified_together")
InternalErrorReporter iceHandler; InternalErrorReporter iceHandler;
UnifierSharedState sharedState{&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}; Unifier u{NotNull{&normalizer}, NotNull{scope.get()}, Location{}, Variance::Covariant};
u.tryUnify(option1, option2); u.tryUnify(option1, option2);
@ -559,10 +561,10 @@ TEST_CASE_FIXTURE(Fixture, "free_options_cannot_be_unified_together")
u.log.commit(); u.log.commit();
ToStringOptions opts; ToStringOptions opts;
CHECK("a?" == toString(option1, opts)); CHECK("'a?" == toString(option1, opts));
// CHECK("a?" == toString(option2, opts)); // This should hold, but does not. // 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") TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop_with_zero_iterators")
@ -677,7 +679,7 @@ struct IsSubtypeFixture : Fixture
if (!module->hasModuleScope()) if (!module->hasModuleScope())
FAIL("isSubtype: module scope data is not available"); 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 } // 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") TEST_CASE_FIXTURE(Fixture, "free_options_can_be_unified_together")
{ {
ScopedFastFlag sff_stringification{FFlag::LuauSolverAgnosticStringification, true};
ScopedFastFlag sff{FFlag::LuauSolverV2, false}; ScopedFastFlag sff{FFlag::LuauSolverV2, false};
TypeArena arena; TypeArena arena;
@ -977,7 +980,7 @@ TEST_CASE_FIXTURE(Fixture, "free_options_can_be_unified_together")
InternalErrorReporter iceHandler; InternalErrorReporter iceHandler;
UnifierSharedState sharedState{&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}; Unifier u{NotNull{&normalizer}, NotNull{scope.get()}, Location{}, Variance::Covariant};
u.tryUnify(option1, option2); u.tryUnify(option1, option2);
@ -987,8 +990,8 @@ TEST_CASE_FIXTURE(Fixture, "free_options_can_be_unified_together")
u.log.commit(); u.log.commit();
ToStringOptions opts; ToStringOptions opts;
CHECK("a?" == toString(option1, opts)); CHECK("'a?" == toString(option1, opts));
CHECK("b?" == toString(option2, opts)); // should be `a?`. CHECK("'b?" == toString(option2, opts)); // should be `a?`.
} }
TEST_CASE_FIXTURE(Fixture, "unify_more_complex_unions_that_include_nil") 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; TypeArena arena;
Scope globalScope(getBuiltins()->anyTypePack); Scope globalScope(getBuiltins()->anyTypePack);
UnifierSharedState sharedState{&ice}; UnifierSharedState sharedState{&ice};
Normalizer normalizer{&arena, getBuiltins(), NotNull{&sharedState}}; Normalizer normalizer{&arena, getBuiltins(), NotNull{&sharedState}, SolverMode::New};
TypeId tableTy = arena.addType(TableType{}); TypeId tableTy = arena.addType(TableType{});
TableType* table = getMutable<TableType>(tableTy); TableType* table = getMutable<TableType>(tableTy);

View file

@ -15,8 +15,10 @@ LUAU_FASTFLAG(LuauFunctionCallsAreNotNilable)
LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions) LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions)
LUAU_FASTFLAG(LuauSimplificationTableExternType) LUAU_FASTFLAG(LuauSimplificationTableExternType)
LUAU_FASTFLAG(LuauBetterCannotCallFunctionPrimitive) LUAU_FASTFLAG(LuauBetterCannotCallFunctionPrimitive)
LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch)
LUAU_FASTFLAG(LuauTypeCheckerStricterIndexCheck) LUAU_FASTFLAG(LuauTypeCheckerStricterIndexCheck)
LUAU_FASTFLAG(LuauNormalizationIntersectTablesPreservesExternTypes) LUAU_FASTFLAG(LuauNormalizationIntersectTablesPreservesExternTypes)
LUAU_FASTFLAG(LuauNormalizationReorderFreeTypeIntersect)
LUAU_FASTFLAG(LuauAvoidDoubleNegation) LUAU_FASTFLAG(LuauAvoidDoubleNegation)
LUAU_FASTFLAG(LuauRefineTablesWithReadType) LUAU_FASTFLAG(LuauRefineTablesWithReadType)
@ -140,6 +142,7 @@ struct RefinementExternTypeFixture : BuiltinsFixture
for (const auto& [name, ty] : getFrontend().globals.globalScope->exportedTypeBindings) for (const auto& [name, ty] : getFrontend().globals.globalScope->exportedTypeBindings)
persist(ty.type); persist(ty.type);
getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
freeze(getFrontend().globals.globalTypes); 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") 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"( CheckResult result = check(R"(
local function f(a, b: string?) local function f(a, b: string?)
if a == b then if a == b then
@ -664,11 +673,20 @@ TEST_CASE_FIXTURE(Fixture, "free_type_is_equal_to_an_lvalue")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "unknown"); // a == b {
else CHECK(toString(requireTypeAtPosition({3, 33})) == "unknown"); // a == b
CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "a"); // 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") 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 // CLI-115286 - Refining via type(x) == 'vector' does not work in the new solver
DOES_NOT_PASS_NEW_SOLVER_GUARD(); DOES_NOT_PASS_NEW_SOLVER_GUARD();
getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
CheckResult result = check(R"( CheckResult result = check(R"(
local function f(vec) local function f(vec)
local X, Y, Z = vec.X, vec.Y, vec.Z 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"( CheckResult result = check(R"(
--!strict --!strict

View file

@ -33,9 +33,11 @@ LUAU_FASTINT(LuauPrimitiveInferenceInTableLimit)
LUAU_FASTFLAG(LuauAutocompleteMissingFollows) LUAU_FASTFLAG(LuauAutocompleteMissingFollows)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps) LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
LUAU_FASTFLAG(LuauRelateTablesAreNeverDisjoint) LUAU_FASTFLAG(LuauRelateTablesAreNeverDisjoint)
LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch)
LUAU_FASTFLAG(LuauTableLiteralSubtypeCheckFunctionCalls) LUAU_FASTFLAG(LuauTableLiteralSubtypeCheckFunctionCalls)
LUAU_FASTFLAG(LuauAvoidGenericsLeakingDuringFunctionCallCheck) LUAU_FASTFLAG(LuauAvoidGenericsLeakingDuringFunctionCallCheck)
LUAU_FASTFLAG(LuauRefineTablesWithReadType) LUAU_FASTFLAG(LuauRefineTablesWithReadType)
LUAU_FASTFLAG(LuauSolverAgnosticStringification)
TEST_SUITE_BEGIN("TableTests"); TEST_SUITE_BEGIN("TableTests");
@ -1721,6 +1723,7 @@ TEST_CASE_FIXTURE(Fixture, "right_table_missing_key")
// Could be flaky if the fix has regressed. // Could be flaky if the fix has regressed.
TEST_CASE_FIXTURE(Fixture, "right_table_missing_key2") TEST_CASE_FIXTURE(Fixture, "right_table_missing_key2")
{ {
ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true};
// CLI-114792 We don't report MissingProperties // CLI-114792 We don't report MissingProperties
DOES_NOT_PASS_NEW_SOLVER_GUARD(); DOES_NOT_PASS_NEW_SOLVER_GUARD();
@ -1738,8 +1741,8 @@ TEST_CASE_FIXTURE(Fixture, "right_table_missing_key2")
REQUIRE_EQ(1, mp->properties.size()); REQUIRE_EQ(1, mp->properties.size());
CHECK_EQ(mp->properties[0], "a"); CHECK_EQ(mp->properties[0], "a");
CHECK_EQ("{| [string]: string, a: string |}", toString(mp->superType)); CHECK_EQ("{ [string]: string, a: string }", toString(mp->superType));
CHECK_EQ("{| |}", toString(mp->subType)); CHECK_EQ("{ }", toString(mp->subType));
} }
TEST_CASE_FIXTURE(Fixture, "casting_unsealed_tables_with_props_into_table_with_indexer") 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. // since mutating properties means table properties should be invariant.
TEST_CASE_FIXTURE(Fixture, "invariant_table_properties_means_instantiating_tables_in_assignment_is_unsound") TEST_CASE_FIXTURE(Fixture, "invariant_table_properties_means_instantiating_tables_in_assignment_is_unsound")
{ {
ScopedFastFlag sff{FFlag::LuauStuckTypeFunctionsStillDispatch, true};
CheckResult result = check(R"( CheckResult result = check(R"(
--!strict --!strict
local t = {} local t = {}
@ -2434,10 +2439,8 @@ TEST_CASE_FIXTURE(Fixture, "invariant_table_properties_means_instantiating_table
if (FFlag::LuauEagerGeneralization4 && FFlag::LuauSolverV2) if (FFlag::LuauEagerGeneralization4 && FFlag::LuauSolverV2)
{ {
// FIXME CLI-151985 LUAU_CHECK_ERROR_COUNT(1, result);
LUAU_CHECK_ERROR_COUNT(2, result);
LUAU_CHECK_ERROR(result, ExplicitFunctionAnnotationRecommended); LUAU_CHECK_ERROR(result, ExplicitFunctionAnnotationRecommended);
LUAU_CHECK_ERROR(result, ConstraintSolvingIncompleteError);
} }
else if (FFlag::LuauSolverV2) 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") TEST_CASE_FIXTURE(BuiltinsFixture, "recursive_metatable_type_call")
{ {
ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true};
// CLI-114782 // CLI-114782
DOES_NOT_PASS_NEW_SOLVER_GUARD(); DOES_NOT_PASS_NEW_SOLVER_GUARD();
@ -2779,7 +2783,7 @@ b()
LUAU_REQUIRE_ERROR_COUNT(1, result); 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") TEST_CASE_FIXTURE(Fixture, "table_subtyping_shouldn't_add_optional_properties_to_sealed_tables")

View file

@ -614,7 +614,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tc_after_error_recovery_no_replacement_name_
{ {
{ {
DOES_NOT_PASS_NEW_SOLVER_GUARD(); DOES_NOT_PASS_NEW_SOLVER_GUARD();
getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
CheckResult result = check(R"( CheckResult result = check(R"(
--!strict --!strict
local t = { x = 10, y = 20 } 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"( CheckResult result = check(R"(
--!strict --!strict
export type = number export type = number
@ -636,7 +637,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tc_after_error_recovery_no_replacement_name_
{ {
DOES_NOT_PASS_NEW_SOLVER_GUARD(); DOES_NOT_PASS_NEW_SOLVER_GUARD();
getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
CheckResult result = check(R"( CheckResult result = check(R"(
--!strict --!strict
function string.() end 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"( CheckResult result = check(R"(
--!strict --!strict
local function () end 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"( CheckResult result = check(R"(
--!strict --!strict
local dm = {} local dm = {}
@ -1153,7 +1156,7 @@ TEST_CASE_FIXTURE(Fixture, "cli_50041_committing_txnlog_in_apollo_client_error")
"\ncaused by:\n" "\ncaused by:\n"
" Property 'getStoreFieldName' is not compatible.\n" " Property 'getStoreFieldName' is not compatible.\n"
"Type\n\t" "Type\n\t"
"'(Policies, FieldSpecifier & {| from: number? |}) -> (a, b...)'" "'(Policies, FieldSpecifier & { from: number? }) -> ('a, b...)'"
"\ncould not be converted into\n\t" "\ncould not be converted into\n\t"
"'(Policies, FieldSpecifier) -> string'" "'(Policies, FieldSpecifier) -> string'"
"\ncaused by:\n" "\ncaused by:\n"
@ -1161,10 +1164,10 @@ TEST_CASE_FIXTURE(Fixture, "cli_50041_committing_txnlog_in_apollo_client_error")
"Type\n\t" "Type\n\t"
"'FieldSpecifier'" "'FieldSpecifier'"
"\ncould not be converted into\n\t" "\ncould not be converted into\n\t"
"'FieldSpecifier & {| from: number? |}'" "'FieldSpecifier & { from: number? }'"
"\ncaused by:\n" "\ncaused by:\n"
" Not all intersection parts are compatible.\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])); CHECK_EQ(expected, toString(result.errors[0]));
} }
else else
@ -2381,6 +2384,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "is_safe_integer_example")
TEST_CASE_FIXTURE(BuiltinsFixture, "type_remover_heap_use_after_free") TEST_CASE_FIXTURE(BuiltinsFixture, "type_remover_heap_use_after_free")
{ {
ScopedFastFlag sff{FFlag::LuauEagerGeneralization4, true};
LUAU_REQUIRE_ERRORS(check(R"( LUAU_REQUIRE_ERRORS(check(R"(
_ = if l0.n0.n0 then {n4(...,setmetatable(setmetatable(_),_)),_ == _,} elseif _.ceil._ then _ elseif _ then not _ _ = if l0.n0.n0 then {n4(...,setmetatable(setmetatable(_),_)),_ == _,} elseif _.ceil._ then _ elseif _ then not _
)")); )"));

View file

@ -23,7 +23,7 @@ struct TryUnifyFixture : Fixture
ScopePtr globalScope{new Scope{arena.addTypePack({TypeId{}})}}; ScopePtr globalScope{new Scope{arena.addTypePack({TypeId{}})}};
InternalErrorReporter iceHandler; InternalErrorReporter iceHandler;
UnifierSharedState unifierState{&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}; Unifier state{NotNull{&normalizer}, NotNull{globalScope.get()}, Location{}, Variance::Covariant};
}; };

View file

@ -7,6 +7,8 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(LuauEagerGeneralization4);
LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch);
TEST_SUITE_BEGIN("TypeInferUnknownNever"); 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") TEST_CASE_FIXTURE(Fixture, "math_operators_and_never")
{ {
ScopedFastFlag sff[] = {
{FFlag::LuauEagerGeneralization4, true},
{FFlag::LuauStuckTypeFunctionsStillDispatch, true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
local function mul(x: nil, y) local function mul(x: nil, y)
return x ~= nil and x * y -- infers boolean | never, which is normalized into boolean 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-114134 Egraph-based simplification.
// CLI-116549 x ~= nil : false when x : nil // CLI-116549 x ~= nil : false when x : nil
CHECK("<a>(nil, a) -> and<boolean, mul<nil & ~nil, a>>" == toString(requireType("mul"))); CHECK("<a>(nil, a) -> false | mul<nil & ~nil, a>" == toString(requireType("mul")));
} }
else else
{ {

View file

@ -21,11 +21,15 @@ LUAU_DYNAMIC_FASTINT(LuauTypePathMaximumTraverseSteps);
struct TypePathFixture : Fixture struct TypePathFixture : Fixture
{ {
ScopedFastFlag sff1{FFlag::LuauSolverV2, true}; ScopedFastFlag sff1{FFlag::LuauSolverV2, true};
TypeArena arena;
const DenseHashMap<TypePackId, TypePackId> emptyMap{nullptr};
}; };
struct TypePathBuiltinsFixture : BuiltinsFixture struct TypePathBuiltinsFixture : BuiltinsFixture
{ {
ScopedFastFlag sff1{FFlag::LuauSolverV2, true}; ScopedFastFlag sff1{FFlag::LuauSolverV2, true};
TypeArena arena;
const DenseHashMap<TypePackId, TypePackId> emptyMap{nullptr};
}; };
TEST_SUITE_BEGIN("TypePathManipulation"); TEST_SUITE_BEGIN("TypePathManipulation");
@ -108,7 +112,7 @@ TEST_SUITE_BEGIN("TypePathTraversal");
TEST_CASE_FIXTURE(TypePathFixture, "empty_traversal") 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") TEST_CASE_FIXTURE(TypePathFixture, "table_property")
@ -117,14 +121,22 @@ TEST_CASE_FIXTURE(TypePathFixture, "table_property")
local x = { y = 123 } 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") TEST_CASE_FIXTURE(ExternTypeFixture, "class_property")
{ {
// Force this here because vector2InstanceType won't get initialized until the frontend has been forced // Force this here because vector2InstanceType won't get initialized until the frontend has been forced
getFrontend(); getFrontend();
CHECK(traverseForType(vector2InstanceType, Path(TypePath::Property{"X", true}), getBuiltins()) == getBuiltins()->numberType); const DenseHashMap<TypePackId, TypePackId> 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") 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") TEST_CASE_FIXTURE(TypePathFixture, "index")
@ -164,12 +179,17 @@ TEST_CASE_FIXTURE(TypePathFixture, "index")
SUBCASE("in_bounds") 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") 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") 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); CHECK(result);
if (result) if (result)
@ -191,7 +211,9 @@ TEST_CASE_FIXTURE(TypePathFixture, "index")
SUBCASE("out_of_bounds") 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") SUBCASE("in_bounds")
{ {
Path path = Path({TypePath::PackField::Arguments, TypePath::Index{1}}); 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); CHECK(result == getBuiltins()->stringType);
} }
SUBCASE("out_of_bounds") SUBCASE("out_of_bounds")
{ {
Path path = Path({TypePath::PackField::Arguments, TypePath::Index{72}}); 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); CHECK(result == std::nullopt);
} }
} }
@ -221,9 +243,12 @@ TEST_CASE_FIXTURE(TypePathFixture, "index")
TEST_CASE_FIXTURE(ExternTypeFixture, "metatables") TEST_CASE_FIXTURE(ExternTypeFixture, "metatables")
{ {
getFrontend(); getFrontend();
const DenseHashMap<TypePackId, TypePackId> emptyMap{nullptr};
TypeArena arena;
SUBCASE("string") 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())); CHECK(result == getMetatable(getBuiltins()->stringType, getBuiltins()));
} }
@ -233,7 +258,7 @@ TEST_CASE_FIXTURE(ExternTypeFixture, "metatables")
type T = "foo" 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())); 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 // 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"); auto expected = lookupType("Table");
REQUIRE(expected); REQUIRE(expected);
CHECK(result == follow(*expected)); CHECK(result == follow(*expected));
@ -261,13 +286,13 @@ TEST_CASE_FIXTURE(ExternTypeFixture, "metatables")
local tbl = setmetatable({}, mt) 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")); CHECK(result == requireType("mt"));
} }
SUBCASE("class") 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. // ExternTypeFixture's Vector2 metatable is just an empty table, but it's there.
CHECK(result); CHECK(result);
} }
@ -286,22 +311,28 @@ TEST_CASE_FIXTURE(TypePathFixture, "bounds")
SUBCASE("upper") SUBCASE("upper")
{ {
ft->upperBound = getBuiltins()->numberType; 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); CHECK(result == getBuiltins()->numberType);
} }
SUBCASE("lower") SUBCASE("lower")
{ {
ft->lowerBound = getBuiltins()->booleanType; 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); CHECK(result == getBuiltins()->booleanType);
} }
} }
SUBCASE("unbounded_type") SUBCASE("unbounded_type")
{ {
CHECK(traverseForType(getBuiltins()->numberType, Path(TypeField::UpperBound), getBuiltins()) == std::nullopt); CHECK(
CHECK(traverseForType(getBuiltins()->numberType, Path(TypeField::LowerBound), getBuiltins()) == std::nullopt); 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 } type T = { [string]: boolean }
)"); )");
auto lookupResult = traverseForType(requireTypeAlias("T"), Path(TypeField::IndexLookup), getBuiltins()); auto lookupResult =
auto resultResult = traverseForType(requireTypeAlias("T"), Path(TypeField::IndexResult), getBuiltins()); 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(lookupResult == getBuiltins()->stringType);
CHECK(resultResult == getBuiltins()->booleanType); CHECK(resultResult == getBuiltins()->booleanType);
@ -328,8 +361,10 @@ TEST_CASE_FIXTURE(TypePathFixture, "indexers")
type T = { y: number } type T = { y: number }
)"); )");
auto lookupResult = traverseForType(requireTypeAlias("T"), Path(TypeField::IndexLookup), getBuiltins()); auto lookupResult =
auto resultResult = traverseForType(requireTypeAlias("T"), Path(TypeField::IndexResult), getBuiltins()); 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(lookupResult == std::nullopt);
CHECK(resultResult == std::nullopt); CHECK(resultResult == std::nullopt);
@ -347,13 +382,13 @@ TEST_CASE_FIXTURE(TypePathFixture, "negated")
unfreeze(arena); unfreeze(arena);
TypeId ty = arena.addType(NegationType{getBuiltins()->numberType}); 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); CHECK(result == getBuiltins()->numberType);
} }
SUBCASE("not_negation") 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); CHECK(result == std::nullopt);
} }
} }
@ -366,13 +401,13 @@ TEST_CASE_FIXTURE(TypePathFixture, "variadic")
unfreeze(arena); unfreeze(arena);
TypePackId tp = arena.addTypePack(VariadicTypePack{getBuiltins()->numberType}); 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); CHECK(result == getBuiltins()->numberType);
} }
SUBCASE("not_variadic") 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); CHECK(result == std::nullopt);
} }
} }
@ -386,7 +421,7 @@ TEST_CASE_FIXTURE(TypePathFixture, "arguments")
end 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); CHECK(result);
if (result) if (result)
CHECK(toString(*result) == "number, string"); CHECK(toString(*result) == "number, string");
@ -394,7 +429,7 @@ TEST_CASE_FIXTURE(TypePathFixture, "arguments")
SUBCASE("not_function") 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); CHECK(result == std::nullopt);
} }
} }
@ -409,7 +444,7 @@ TEST_CASE_FIXTURE(TypePathFixture, "returns")
end 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); CHECK(result);
if (result) if (result)
CHECK(toString(*result) == "number, string"); CHECK(toString(*result) == "number, string");
@ -417,7 +452,7 @@ TEST_CASE_FIXTURE(TypePathFixture, "returns")
SUBCASE("not_function") 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); CHECK(result == std::nullopt);
} }
} }
@ -430,7 +465,8 @@ TEST_CASE_FIXTURE(TypePathFixture, "tail")
type T = (number, string, ...boolean) -> () 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); CHECK(result);
if (result) if (result)
CHECK(toString(*result) == "...boolean"); CHECK(toString(*result) == "...boolean");
@ -442,17 +478,62 @@ TEST_CASE_FIXTURE(TypePathFixture, "tail")
type T = (number, string) -> () 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); CHECK(result == std::nullopt);
} }
SUBCASE("type") 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); 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)) 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 // 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}); TypeId b = arena.addType(BoundType{a});
asMutable(a)->ty.emplace<BoundType>(b); asMutable(a)->ty.emplace<BoundType>(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") SUBCASE("table_contains_itself")
@ -477,7 +558,7 @@ TEST_CASE_FIXTURE(TypePathFixture, "cycles" * doctest::timeout(0.5))
TypeId tbl = arena.addType(TableType{}); TypeId tbl = arena.addType(TableType{});
getMutable<TableType>(tbl)->props["a"] = Luau::Property(tbl); getMutable<TableType>(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); CHECK(result == tbl);
} }
} }
@ -498,7 +579,7 @@ TEST_CASE_FIXTURE(TypePathFixture, "step_limit")
TypeId root = requireTypeAlias("T"); TypeId root = requireTypeAlias("T");
Path path = PathBuilder().readProp("x").readProp("y").readProp("z").build(); 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); CHECK(!result);
} }
@ -516,7 +597,7 @@ TEST_CASE_FIXTURE(TypePathBuiltinsFixture, "complex_chains")
TypeId root = requireTypeAlias("Tab"); TypeId root = requireTypeAlias("Tab");
Path path = PathBuilder().mt().readProp("__add").rets().index(0).build(); 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); CHECK(result == getBuiltins()->numberType);
} }
@ -530,7 +611,7 @@ TEST_CASE_FIXTURE(TypePathBuiltinsFixture, "complex_chains")
TypeId root = requireTypeAlias("Obj"); TypeId root = requireTypeAlias("Obj");
Path path = PathBuilder().readProp("method").index(0).args().index(1).build(); 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); 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 "); 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_END(); // TypePathToString
TEST_SUITE_BEGIN("TypePathBuilder"); 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}}) 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 TEST_SUITE_END(); // TypePathBuilder