mirror of
https://github.com/luau-lang/luau.git
synced 2025-08-26 11:27:08 +01:00
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:
parent
8fe64db609
commit
e190754565
64 changed files with 2342 additions and 536 deletions
|
@ -6,6 +6,7 @@
|
|||
#include "Luau/NotNull.h"
|
||||
#include "Luau/Variant.h"
|
||||
#include "Luau/TypeFwd.h"
|
||||
#include "Luau/TypeIds.h"
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
@ -316,7 +317,11 @@ struct Constraint
|
|||
|
||||
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>;
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include "Luau/Location.h"
|
||||
#include "Luau/Module.h"
|
||||
#include "Luau/Normalize.h"
|
||||
#include "Luau/OrderedSet.h"
|
||||
#include "Luau/Substitution.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/Type.h"
|
||||
|
@ -121,8 +122,12 @@ struct ConstraintSolver
|
|||
// A mapping from free types to the number of unresolved constraints that mention them.
|
||||
DenseHashMap<TypeId, size_t> unresolvedConstraints{{}};
|
||||
|
||||
std::unordered_map<NotNull<const Constraint>, DenseHashSet<TypeId>> maybeMutatedFreeTypes;
|
||||
std::unordered_map<TypeId, DenseHashSet<const Constraint*>> mutatedFreeTypeToConstraint;
|
||||
// Clip with LuauUseOrderedTypeSetsInConstraints
|
||||
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.
|
||||
DenseHashSet<const void*> uninhabitedTypeFunctions{{}};
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
#include "Luau/TypeFwd.h"
|
||||
#include "Luau/NotNull.h"
|
||||
#include "Luau/DenseHash.h"
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
|
|
@ -496,6 +496,14 @@ struct GenericTypePackCountMismatch
|
|||
bool operator==(const GenericTypePackCountMismatch& rhs) const;
|
||||
};
|
||||
|
||||
// Error during subtyping when the number of generic type packs between compared types does not match
|
||||
struct MultipleNonviableOverloads
|
||||
{
|
||||
size_t attemptedArgCount;
|
||||
|
||||
bool operator==(const MultipleNonviableOverloads& rhs) const;
|
||||
};
|
||||
|
||||
using TypeErrorData = Variant<
|
||||
TypeMismatch,
|
||||
UnknownSymbol,
|
||||
|
@ -550,7 +558,8 @@ using TypeErrorData = Variant<
|
|||
UnexpectedArrayLikeTableItem,
|
||||
CannotCheckDynamicStringFormatCalls,
|
||||
GenericTypeCountMismatch,
|
||||
GenericTypePackCountMismatch>;
|
||||
GenericTypePackCountMismatch,
|
||||
MultipleNonviableOverloads>;
|
||||
|
||||
struct TypeErrorSummary
|
||||
{
|
||||
|
|
|
@ -173,11 +173,10 @@ struct Frontend
|
|||
|
||||
Frontend(FileResolver* fileResolver, ConfigResolver* configResolver, const FrontendOptions& options = {});
|
||||
|
||||
void setLuauSolverSelectionFromWorkspace(bool newSolverEnabled);
|
||||
bool getLuauSolverSelection() const;
|
||||
bool getLuauSolverSelectionFlagged() const;
|
||||
void setLuauSolverSelectionFromWorkspace(SolverMode mode);
|
||||
SolverMode getLuauSolverMode() const;
|
||||
// The default value assuming there is no workspace setup yet
|
||||
std::atomic<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
|
||||
void parse(const ModuleName& name);
|
||||
void parseModules(const std::vector<ModuleName>& name);
|
||||
|
@ -205,6 +204,7 @@ struct Frontend
|
|||
|
||||
void clearStats();
|
||||
void clear();
|
||||
void clearBuiltinEnvironments();
|
||||
|
||||
ScopePtr addEnvironment(const std::string& environmentName);
|
||||
ScopePtr getEnvironmentScope(const std::string& environmentName) const;
|
||||
|
@ -272,7 +272,6 @@ private:
|
|||
static LintResult classifyLints(const std::vector<LintWarning>& warnings, const Config& config);
|
||||
|
||||
ScopePtr getModuleEnvironment(const SourceModule& module, const Config& config, bool forAutocomplete) const;
|
||||
|
||||
std::unordered_map<std::string, ScopePtr> environments;
|
||||
std::unordered_map<std::string, std::function<void(Frontend&, GlobalTypes&, ScopePtr)>> builtinDefinitions;
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ namespace Luau
|
|||
|
||||
struct GlobalTypes
|
||||
{
|
||||
explicit GlobalTypes(NotNull<BuiltinTypes> builtinTypes);
|
||||
explicit GlobalTypes(NotNull<BuiltinTypes> builtinTypes, SolverMode mode);
|
||||
|
||||
NotNull<BuiltinTypes> builtinTypes; // Global types are based on builtin types
|
||||
|
||||
|
@ -22,6 +22,8 @@ struct GlobalTypes
|
|||
|
||||
ScopePtr globalScope; // shared by all modules
|
||||
ScopePtr globalTypeFunctionScope; // shared by all modules
|
||||
|
||||
SolverMode mode = SolverMode::Old;
|
||||
};
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -28,7 +28,8 @@ bool isSubtype(
|
|||
NotNull<Scope> scope,
|
||||
NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<Simplifier> simplifier,
|
||||
InternalErrorReporter& ice
|
||||
InternalErrorReporter& ice,
|
||||
SolverMode solverMode
|
||||
);
|
||||
bool isSubtype(
|
||||
TypePackId subPack,
|
||||
|
@ -36,7 +37,8 @@ bool isSubtype(
|
|||
NotNull<Scope> scope,
|
||||
NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<Simplifier> simplifier,
|
||||
InternalErrorReporter& ice
|
||||
InternalErrorReporter& ice,
|
||||
SolverMode solverMode
|
||||
);
|
||||
|
||||
} // namespace Luau
|
||||
|
@ -311,14 +313,21 @@ class Normalizer
|
|||
DenseHashMap<std::pair<TypeId, TypeId>, bool, TypeIdPairHash> cachedIsInhabitedIntersection{{nullptr, nullptr}};
|
||||
|
||||
bool withinResourceLimits();
|
||||
bool useNewLuauSolver() const;
|
||||
|
||||
public:
|
||||
TypeArena* arena;
|
||||
NotNull<BuiltinTypes> builtinTypes;
|
||||
NotNull<UnifierSharedState> sharedState;
|
||||
bool cacheInhabitance = false;
|
||||
|
||||
Normalizer(TypeArena* arena, NotNull<BuiltinTypes> builtinTypes, NotNull<UnifierSharedState> sharedState, bool cacheInhabitance = false);
|
||||
SolverMode solverMode;
|
||||
Normalizer(
|
||||
TypeArena* arena,
|
||||
NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<UnifierSharedState> sharedState,
|
||||
SolverMode solver,
|
||||
bool cacheInhabitance = false
|
||||
);
|
||||
Normalizer(const Normalizer&) = delete;
|
||||
Normalizer(Normalizer&&) = delete;
|
||||
Normalizer() = delete;
|
||||
|
|
68
Analysis/include/Luau/OrderedSet.h
Normal file
68
Analysis/include/Luau/OrderedSet.h
Normal 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};
|
||||
};
|
||||
|
||||
}
|
|
@ -87,6 +87,13 @@ private:
|
|||
);
|
||||
size_t indexof(Analysis analysis);
|
||||
void add(Analysis analysis, TypeId ty, ErrorVec&& errors);
|
||||
void maybeEmplaceError(
|
||||
ErrorVec* errors,
|
||||
Location argLocation,
|
||||
const SubtypingReasoning* reason,
|
||||
std::optional<TypeId> failedSubTy,
|
||||
std::optional<TypeId> failedSuperTy
|
||||
) const;
|
||||
};
|
||||
|
||||
struct SolveResult
|
||||
|
|
|
@ -71,6 +71,7 @@ struct SubtypingResult
|
|||
/// The reason for isSubtype to be false. May not be present even if
|
||||
/// isSubtype is false, depending on the input types.
|
||||
SubtypingReasonings reasoning{kEmptyReasoning};
|
||||
DenseHashMap<TypePackId, TypePackId> mappedGenericPacks{nullptr};
|
||||
|
||||
// If this subtype result required testing free types, we might be making
|
||||
// assumptions about what the free type eventually resolves to. If so,
|
||||
|
|
|
@ -39,6 +39,12 @@ struct Constraint;
|
|||
struct Subtyping;
|
||||
struct TypeChecker2;
|
||||
|
||||
enum struct SolverMode
|
||||
{
|
||||
Old,
|
||||
New
|
||||
};
|
||||
|
||||
/**
|
||||
* There are three kinds of type variables:
|
||||
* - `Free` variables are metavariables, which stand for unconstrained types.
|
||||
|
@ -612,6 +618,21 @@ struct UserDefinedFunctionData
|
|||
DenseHashMap<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
|
||||
* 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
|
||||
UserDefinedFunctionData userFuncData;
|
||||
|
||||
TypeFunctionInstanceState state = TypeFunctionInstanceState::Unsolved;
|
||||
|
||||
TypeFunctionInstanceType(
|
||||
NotNull<const TypeFunction> function,
|
||||
std::vector<TypeId> typeArguments,
|
||||
|
@ -970,7 +993,7 @@ struct BuiltinTypes
|
|||
TypeId errorRecoveryType(TypeId guess) const;
|
||||
TypePackId errorRecoveryTypePack(TypePackId guess) const;
|
||||
|
||||
friend TypeId makeStringMetatable(NotNull<BuiltinTypes> builtinTypes);
|
||||
friend TypeId makeStringMetatable(NotNull<BuiltinTypes> builtinTypes, SolverMode mode);
|
||||
friend struct GlobalTypes;
|
||||
|
||||
private:
|
||||
|
|
|
@ -225,11 +225,21 @@ private:
|
|||
// Avoid duplicate warnings being emitted for the same global variable.
|
||||
DenseHashSet<std::string> warnedGlobals{""};
|
||||
|
||||
void suggestAnnotations(AstExprFunction* expr, TypeId ty);
|
||||
|
||||
void diagnoseMissingTableKey(UnknownProperty* utk, TypeErrorData& data) const;
|
||||
bool isErrorSuppressing(Location loc, TypeId ty);
|
||||
bool isErrorSuppressing(Location loc1, TypeId ty1, Location loc2, TypeId ty2);
|
||||
bool isErrorSuppressing(Location loc, TypePackId tp);
|
||||
bool isErrorSuppressing(Location loc1, TypePackId tp1, Location loc2, TypePackId tp2);
|
||||
|
||||
// Returns whether we reported any errors
|
||||
bool reportNonviableOverloadErrors(
|
||||
std::vector<std::pair<TypeId, ErrorVec>> nonviableOverloads,
|
||||
Location callFuncLocation,
|
||||
size_t argHeadSize,
|
||||
Location callLocation
|
||||
);
|
||||
};
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/NotNull.h"
|
||||
#include "Luau/Polarity.h"
|
||||
#include "Luau/TypeFwd.h"
|
||||
|
@ -230,6 +231,7 @@ bool isEmpty(TypePackId tp);
|
|||
/// Flattens out a type pack. Also returns a valid TypePackId tail if the type pack's full size is not known
|
||||
std::pair<std::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 DenseHashMap<TypePackId, TypePackId>& mappedGenericPacks);
|
||||
|
||||
/// Returs true if the type pack arose from a function that is declared to be variadic.
|
||||
/// Returns *false* for function argument packs that are inferred to be safe to oversaturate!
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/NotNull.h"
|
||||
#include "Luau/TypeArena.h"
|
||||
#include "Luau/TypeFwd.h"
|
||||
#include "Luau/Variant.h"
|
||||
#include "Luau/NotNull.h"
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
@ -91,6 +93,15 @@ enum class PackField
|
|||
Tail,
|
||||
};
|
||||
|
||||
/// Represents a one-sided slice of a type pack with a head and a tail. The slice starts at the type at starting index and includes the tail.
|
||||
struct PackSlice
|
||||
{
|
||||
/// The 0-based index to start the slice at.
|
||||
size_t start_index;
|
||||
|
||||
bool operator==(const PackSlice& other) const;
|
||||
};
|
||||
|
||||
/// Component that represents the result of a reduction
|
||||
/// `resultType` is `never` if the reduction could not proceed
|
||||
struct Reduction
|
||||
|
@ -102,7 +113,7 @@ struct Reduction
|
|||
|
||||
/// A single component of a path, representing one inner type or type pack to
|
||||
/// traverse into.
|
||||
using Component = Luau::Variant<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
|
||||
/// contained within.
|
||||
|
@ -177,6 +188,7 @@ struct PathHash
|
|||
size_t operator()(const Index& idx) const;
|
||||
size_t operator()(const TypeField& field) const;
|
||||
size_t operator()(const PackField& field) const;
|
||||
size_t operator()(const PackSlice& slice) const;
|
||||
size_t operator()(const Reduction& reduction) const;
|
||||
size_t operator()(const Component& component) const;
|
||||
size_t operator()(const Path& path) const;
|
||||
|
@ -205,6 +217,7 @@ struct PathBuilder
|
|||
PathBuilder& args();
|
||||
PathBuilder& rets();
|
||||
PathBuilder& tail();
|
||||
PathBuilder& packSlice(size_t start_index);
|
||||
};
|
||||
|
||||
} // namespace TypePath
|
||||
|
@ -218,35 +231,116 @@ std::string toString(const TypePath::Path& path, bool prefixDot = false);
|
|||
/// Converts a Path to a human readable string for error reporting.
|
||||
std::string toStringHuman(const TypePath::Path& path);
|
||||
|
||||
std::optional<TypeOrPack> traverse(TypeId root, const Path& path, NotNull<BuiltinTypes> builtinTypes);
|
||||
std::optional<TypeOrPack> traverse(TypePackId root, const Path& path, NotNull<BuiltinTypes> builtinTypes);
|
||||
// TODO: clip traverse_DEPRECATED along with `LuauReturnMappedGenericPacksFromSubtyping`
|
||||
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.
|
||||
/// @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(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.
|
||||
/// @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(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.
|
||||
/// @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(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.
|
||||
/// @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(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
|
||||
|
|
|
@ -24,7 +24,7 @@ AutocompleteResult autocomplete(Frontend& frontend, const ModuleName& moduleName
|
|||
return {};
|
||||
|
||||
ModulePtr module;
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (frontend.getLuauSolverMode() == SolverMode::New)
|
||||
module = frontend.moduleResolver.getModule(moduleName);
|
||||
else
|
||||
module = frontend.moduleResolverForAutocomplete.getModule(moduleName);
|
||||
|
@ -34,7 +34,7 @@ AutocompleteResult autocomplete(Frontend& frontend, const ModuleName& moduleName
|
|||
|
||||
NotNull<BuiltinTypes> builtinTypes = frontend.builtinTypes;
|
||||
Scope* globalScope;
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (frontend.getLuauSolverMode() == SolverMode::New)
|
||||
globalScope = frontend.globals.globalScope.get();
|
||||
else
|
||||
globalScope = frontend.globalsForAutocomplete.globalScope.get();
|
||||
|
|
|
@ -162,7 +162,7 @@ static bool checkTypeMatch(
|
|||
InternalErrorReporter iceReporter;
|
||||
UnifierSharedState unifierState(&iceReporter);
|
||||
SimplifierPtr simplifier = newSimplifier(NotNull{typeArena}, builtinTypes);
|
||||
Normalizer normalizer{typeArena, builtinTypes, NotNull{&unifierState}};
|
||||
Normalizer normalizer{typeArena, builtinTypes, NotNull{&unifierState}, module.checkedInNewSolver ? SolverMode::New : SolverMode::Old};
|
||||
if (module.checkedInNewSolver)
|
||||
{
|
||||
TypeCheckLimits limits;
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "Luau/Error.h"
|
||||
#include "Luau/Frontend.h"
|
||||
#include "Luau/InferPolarity.h"
|
||||
#include "Luau/Module.h"
|
||||
#include "Luau/NotNull.h"
|
||||
#include "Luau/Subtyping.h"
|
||||
#include "Luau/Symbol.h"
|
||||
|
@ -37,6 +38,7 @@ LUAU_FASTFLAGVARIABLE(LuauStringFormatImprovements)
|
|||
LUAU_FASTFLAGVARIABLE(LuauMagicFreezeCheckBlocked2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUpdateSetMetatableTypeSignature)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUpdateGetMetatableTypeSignature)
|
||||
LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -317,7 +319,7 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
|
|||
if (FFlag::LuauEagerGeneralization4)
|
||||
globalScope = globals.globalScope.get();
|
||||
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (frontend.getLuauSolverMode() == SolverMode::New)
|
||||
builtinTypeFunctions().addToScope(NotNull{&arena}, NotNull{globals.globalScope.get()});
|
||||
|
||||
LoadDefinitionFileResult loadResult = frontend.loadDefinitionFile(
|
||||
|
@ -390,7 +392,7 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
|
|||
|
||||
TypeId genericT = arena.addType(GenericType{globalScope, "T"});
|
||||
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauUpdateGetMetatableTypeSignature)
|
||||
if ((frontend.getLuauSolverMode() == SolverMode::New) && FFlag::LuauUpdateGetMetatableTypeSignature)
|
||||
{
|
||||
// getmetatable : <T>(T) -> getmetatable<T>
|
||||
TypeId getmtReturn = arena.addType(TypeFunctionInstanceType{builtinTypeFunctions().getmetatableFunc, {genericT}});
|
||||
|
@ -402,7 +404,7 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
|
|||
addGlobalBinding(globals, "getmetatable", makeFunction(arena, std::nullopt, {genericMT}, {}, {tableMetaMT}, {genericMT}), "@luau");
|
||||
}
|
||||
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (frontend.getLuauSolverMode() == SolverMode::New)
|
||||
{
|
||||
TypeId tMetaMT = arena.addType(MetatableType{genericT, genericMT});
|
||||
|
||||
|
@ -452,7 +454,7 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
|
|||
|
||||
attachMagicFunction(getGlobalBinding(globals, "assert"), std::make_shared<MagicAssert>());
|
||||
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (frontend.getLuauSolverMode() == SolverMode::New)
|
||||
{
|
||||
// declare function assert<T>(value: T, errorMessage: string?): intersect<T, ~(false?)>
|
||||
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 (FFlag::LuauSolverV2)
|
||||
if (frontend.getLuauSolverMode() == SolverMode::New)
|
||||
{
|
||||
// CLI-114044 - The new solver does not yet support generic tables,
|
||||
// which act, in an odd way, like generics that are constrained to
|
||||
|
@ -1227,7 +1229,7 @@ bool MagicFind::infer(const MagicFunctionCallContext& context)
|
|||
return true;
|
||||
}
|
||||
|
||||
TypeId makeStringMetatable(NotNull<BuiltinTypes> builtinTypes)
|
||||
TypeId makeStringMetatable(NotNull<BuiltinTypes> builtinTypes, SolverMode mode)
|
||||
{
|
||||
NotNull<TypeArena> arena{builtinTypes->arena.get()};
|
||||
|
||||
|
@ -1243,7 +1245,9 @@ TypeId makeStringMetatable(NotNull<BuiltinTypes> builtinTypes)
|
|||
const TypePackId oneStringPack = arena->addTypePack({stringType});
|
||||
const TypePackId anyTypePack = builtinTypes->anyTypePack;
|
||||
|
||||
const TypePackId variadicTailPack = FFlag::LuauSolverV2 ? builtinTypes->unknownTypePack : anyTypePack;
|
||||
const TypePackId variadicTailPack = (FFlag::LuauUseWorkspacePropToChooseSolver && mode == SolverMode::New) ? builtinTypes->unknownTypePack
|
||||
: FFlag::LuauSolverV2 ? builtinTypes->unknownTypePack
|
||||
: anyTypePack;
|
||||
const TypePackId emptyPack = arena->addTypePack({});
|
||||
const TypePackId stringVariadicList = arena->addTypePack(TypePackVar{VariadicTypePack{stringType}});
|
||||
const TypePackId numberVariadicList = arena->addTypePack(TypePackVar{VariadicTypePack{numberType}});
|
||||
|
|
|
@ -15,12 +15,63 @@ Constraint::Constraint(NotNull<Scope> scope, const Location& location, Constrain
|
|||
{
|
||||
}
|
||||
|
||||
struct ReferenceCountInitializer : TypeOnceVisitor
|
||||
struct ReferenceCountInitializer_DEPRECATED : TypeOnceVisitor
|
||||
{
|
||||
DenseHashSet<TypeId>* result;
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
@ -78,7 +129,7 @@ bool isReferenceCountedType(const TypeId 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
|
||||
// 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.
|
||||
|
||||
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))
|
||||
{
|
||||
|
|
|
@ -33,14 +33,15 @@ LUAU_FASTFLAGVARIABLE(DebugLuauLogBindings)
|
|||
LUAU_FASTINTVARIABLE(LuauSolverRecursionLimit, 500)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization4)
|
||||
LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAddCallConstraintForIterableFunctions)
|
||||
LUAU_FASTFLAGVARIABLE(LuauGuardAgainstMalformedTypeAliasExpansion2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauInsertErrorTypesIntoIndexerResult)
|
||||
LUAU_FASTFLAGVARIABLE(LuauClipVariadicAnysFromArgsToGenericFuncs2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAvoidGenericsLeakingDuringFunctionCallCheck)
|
||||
LUAU_FASTFLAGVARIABLE(LuauMissingFollowInAssignIndexConstraint)
|
||||
LUAU_FASTFLAGVARIABLE(LuauRemoveTypeCallsForReadWriteProps)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTableLiteralSubtypeCheckFunctionCalls)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUseOrderedTypeSetsInConstraints)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -418,10 +419,21 @@ void ConstraintSolver::run()
|
|||
// Free types that have no constraints at all can be generalized right away.
|
||||
if (FFlag::LuauEagerGeneralization4)
|
||||
{
|
||||
for (TypeId ty : constraintSet.freeTypes)
|
||||
if (FFlag::LuauUseOrderedTypeSetsInConstraints)
|
||||
{
|
||||
if (auto it = mutatedFreeTypeToConstraint.find(ty); it == mutatedFreeTypeToConstraint.end() || it->second.empty())
|
||||
generalizeOneType(ty);
|
||||
for (TypeId ty : constraintSet.freeTypes)
|
||||
{
|
||||
if (auto it = mutatedFreeTypeToConstraint.find(ty); it == mutatedFreeTypeToConstraint.end() || it->second.empty())
|
||||
generalizeOneType(ty);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (TypeId ty : constraintSet.freeTypes)
|
||||
{
|
||||
if (auto it = mutatedFreeTypeToConstraint_DEPRECATED.find(ty); it == mutatedFreeTypeToConstraint_DEPRECATED.end() || it->second.empty())
|
||||
generalizeOneType(ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -466,40 +478,80 @@ void ConstraintSolver::run()
|
|||
unblock(c);
|
||||
unsolvedConstraints.erase(unsolvedConstraints.begin() + ptrdiff_t(i));
|
||||
|
||||
const auto maybeMutated = maybeMutatedFreeTypes.find(c);
|
||||
if (maybeMutated != maybeMutatedFreeTypes.end())
|
||||
if (FFlag::LuauUseOrderedTypeSetsInConstraints)
|
||||
{
|
||||
DenseHashSet<TypeId> seen{nullptr};
|
||||
for (auto ty : maybeMutated->second)
|
||||
if (const auto maybeMutated = maybeMutatedFreeTypes.find(c); maybeMutated != maybeMutatedFreeTypes.end())
|
||||
{
|
||||
// There is a high chance that this type has been rebound
|
||||
// across blocked types, rebound free types, pending
|
||||
// expansion types, etc, so we need to follow it.
|
||||
ty = follow(ty);
|
||||
|
||||
if (FFlag::LuauEagerGeneralization4)
|
||||
DenseHashSet<TypeId> seen{nullptr};
|
||||
for (auto ty : maybeMutated->second)
|
||||
{
|
||||
if (seen.contains(ty))
|
||||
continue;
|
||||
seen.insert(ty);
|
||||
// There is a high chance that this type has been rebound
|
||||
// across blocked types, rebound free types, pending
|
||||
// expansion types, etc, so we need to follow it.
|
||||
ty = follow(ty);
|
||||
|
||||
if (FFlag::LuauEagerGeneralization4)
|
||||
{
|
||||
if (seen.contains(ty))
|
||||
continue;
|
||||
seen.insert(ty);
|
||||
}
|
||||
|
||||
size_t& refCount = unresolvedConstraints[ty];
|
||||
if (refCount > 0)
|
||||
refCount -= 1;
|
||||
|
||||
// We have two constraints that are designed to wait for the
|
||||
// refCount on a free type to be equal to 1: the
|
||||
// PrimitiveTypeConstraint and ReduceConstraint. We
|
||||
// therefore wake any constraint waiting for a free type's
|
||||
// refcount to be 1 or 0.
|
||||
if (refCount <= 1)
|
||||
unblock(ty, Location{});
|
||||
|
||||
if (FFlag::LuauEagerGeneralization4 && refCount == 0)
|
||||
generalizeOneType(ty);
|
||||
}
|
||||
|
||||
size_t& refCount = unresolvedConstraints[ty];
|
||||
if (refCount > 0)
|
||||
refCount -= 1;
|
||||
|
||||
// We have two constraints that are designed to wait for the
|
||||
// refCount on a free type to be equal to 1: the
|
||||
// PrimitiveTypeConstraint and ReduceConstraint. We
|
||||
// therefore wake any constraint waiting for a free type's
|
||||
// refcount to be 1 or 0.
|
||||
if (refCount <= 1)
|
||||
unblock(ty, Location{});
|
||||
|
||||
if (FFlag::LuauEagerGeneralization4 && refCount == 0)
|
||||
generalizeOneType(ty);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto maybeMutated = maybeMutatedFreeTypes_DEPRECATED.find(c);
|
||||
if (maybeMutated != maybeMutatedFreeTypes_DEPRECATED.end())
|
||||
{
|
||||
DenseHashSet<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)
|
||||
{
|
||||
|
@ -664,27 +716,57 @@ struct TypeSearcher : TypeVisitor
|
|||
|
||||
void ConstraintSolver::initFreeTypeTracking()
|
||||
{
|
||||
for (NotNull<Constraint> c : this->constraints)
|
||||
if (FFlag::LuauUseOrderedTypeSetsInConstraints)
|
||||
{
|
||||
unsolvedConstraints.emplace_back(c);
|
||||
|
||||
auto maybeMutatedTypesPerConstraint = c->getMaybeMutatedFreeTypes();
|
||||
for (auto ty : maybeMutatedTypesPerConstraint)
|
||||
for (auto c : this->constraints)
|
||||
{
|
||||
auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0);
|
||||
refCount += 1;
|
||||
unsolvedConstraints.emplace_back(c);
|
||||
|
||||
if (FFlag::LuauEagerGeneralization4)
|
||||
auto maybeMutatedTypesPerConstraint = c->getMaybeMutatedFreeTypes();
|
||||
for (auto ty : maybeMutatedTypesPerConstraint)
|
||||
{
|
||||
auto [it, fresh] = mutatedFreeTypeToConstraint.try_emplace(ty, nullptr);
|
||||
it->second.insert(c.get());
|
||||
auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0);
|
||||
refCount += 1;
|
||||
|
||||
if (FFlag::LuauEagerGeneralization4)
|
||||
{
|
||||
auto [it, fresh] = mutatedFreeTypeToConstraint.try_emplace(ty);
|
||||
it->second.insert(c.get());
|
||||
}
|
||||
}
|
||||
maybeMutatedFreeTypes.emplace(c, maybeMutatedTypesPerConstraint);
|
||||
|
||||
for (NotNull<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)
|
||||
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)
|
||||
|
@ -1922,7 +1959,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<con
|
|||
// Generic types are skipped over entirely, for now.
|
||||
if (containsGenerics.hasGeneric(expectedArgTy))
|
||||
{
|
||||
if (!FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2 || !lambdaTy || !lambdaTy->argTypes)
|
||||
if (!lambdaTy || !lambdaTy->argTypes)
|
||||
continue;
|
||||
|
||||
const TypePack* argTp = get<TypePack>(follow(lambdaTy->argTypes));
|
||||
|
@ -3630,7 +3667,11 @@ bool ConstraintSolver::isBlocked(TypeId ty) const
|
|||
ty = follow(ty);
|
||||
|
||||
if (auto tfit = get<TypeFunctionInstanceType>(ty))
|
||||
{
|
||||
if (FFlag::LuauStuckTypeFunctionsStillDispatch && tfit->state != TypeFunctionInstanceState::Unsolved)
|
||||
return false;
|
||||
return uninhabitedTypeFunctions.contains(ty) == false;
|
||||
}
|
||||
|
||||
return nullptr != get<BlockedType>(ty) || nullptr != get<PendingExpansionType>(ty);
|
||||
}
|
||||
|
@ -3739,21 +3780,42 @@ void ConstraintSolver::shiftReferences(TypeId source, TypeId target)
|
|||
|
||||
if (FFlag::LuauEagerGeneralization4)
|
||||
{
|
||||
auto it = mutatedFreeTypeToConstraint.find(source);
|
||||
if (it != mutatedFreeTypeToConstraint.end())
|
||||
if (FFlag::LuauUseOrderedTypeSetsInConstraints)
|
||||
{
|
||||
const DenseHashSet<const Constraint*>& constraintsAffectedBySource = it->second;
|
||||
|
||||
auto [it2, fresh2] = mutatedFreeTypeToConstraint.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)
|
||||
if (auto it = mutatedFreeTypeToConstraint.find(source); it != mutatedFreeTypeToConstraint.end())
|
||||
{
|
||||
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});
|
||||
it3->second.insert(target);
|
||||
OrderedSet<const Constraint*>& constraintsAffectedByTarget = it2->second;
|
||||
|
||||
for (const Constraint* constraint : constraintsAffectedBySource)
|
||||
{
|
||||
constraintsAffectedByTarget.insert(constraint);
|
||||
auto [it3, fresh3] = maybeMutatedFreeTypes.try_emplace(NotNull{constraint}, TypeIds{});
|
||||
it3->second.insert(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto it = mutatedFreeTypeToConstraint_DEPRECATED.find(source);
|
||||
if (it != mutatedFreeTypeToConstraint_DEPRECATED.end())
|
||||
{
|
||||
const DenseHashSet<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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ LUAU_FASTFLAG(LuauEagerGeneralization4)
|
|||
|
||||
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
|
||||
LUAU_FASTFLAGVARIABLE(LuauBetterCannotCallFunctionPrimitive)
|
||||
LUAU_FASTFLAG(LuauSolverAgnosticStringification)
|
||||
|
||||
static std::string wrongNumberOfArgsString(
|
||||
size_t expectedCount,
|
||||
|
@ -420,10 +421,17 @@ struct ErrorConverter
|
|||
auto it = mtt->props.find("__call");
|
||||
if (it != mtt->props.end())
|
||||
{
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauRemoveTypeCallsForReadWriteProps)
|
||||
if (FFlag::LuauSolverAgnosticStringification)
|
||||
{
|
||||
return it->second.readTy;
|
||||
}
|
||||
else
|
||||
return it->second.type_DEPRECATED();
|
||||
{
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauRemoveTypeCallsForReadWriteProps)
|
||||
return it->second.readTy;
|
||||
else
|
||||
return it->second.type_DEPRECATED();
|
||||
}
|
||||
}
|
||||
else
|
||||
return std::nullopt;
|
||||
|
@ -877,6 +885,11 @@ struct ErrorConverter
|
|||
return "Different number of generic type pack parameters: subtype had " + std::to_string(e.subTyGenericPackCount) + ", supertype had " +
|
||||
std::to_string(e.superTyGenericPackCount) + ".";
|
||||
}
|
||||
|
||||
std::string operator()(const MultipleNonviableOverloads& e) const
|
||||
{
|
||||
return "None of the overloads for function that accept " + std::to_string(e.attemptedArgCount) + " arguments are compatible.";
|
||||
}
|
||||
};
|
||||
|
||||
struct InvalidNameChecker
|
||||
|
@ -1275,6 +1288,11 @@ bool GenericTypePackCountMismatch::operator==(const GenericTypePackCountMismatch
|
|||
return subTyGenericPackCount == rhs.subTyGenericPackCount && superTyGenericPackCount == rhs.superTyGenericPackCount;
|
||||
}
|
||||
|
||||
bool MultipleNonviableOverloads::operator==(const MultipleNonviableOverloads& rhs) const
|
||||
{
|
||||
return attemptedArgCount == rhs.attemptedArgCount;
|
||||
}
|
||||
|
||||
std::string toString(const TypeError& error)
|
||||
{
|
||||
return toString(error, TypeErrorToStringOptions{});
|
||||
|
@ -1495,6 +1513,9 @@ void copyError(T& e, TypeArena& destArena, CloneState& cloneState)
|
|||
else if constexpr (std::is_same_v<T, GenericTypePackCountMismatch>)
|
||||
{
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, MultipleNonviableOverloads>)
|
||||
{
|
||||
}
|
||||
else
|
||||
static_assert(always_false_v<T>, "Non-exhaustive type switch");
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include "Luau/Parser.h"
|
||||
#include "Luau/ParseOptions.h"
|
||||
#include "Luau/Module.h"
|
||||
#include "Luau/RequireTracer.h"
|
||||
#include "Luau/TimeTrace.h"
|
||||
#include "Luau/UnifierSharedState.h"
|
||||
#include "Luau/TypeFunction.h"
|
||||
|
@ -37,12 +38,15 @@ LUAU_FASTFLAGVARIABLE(LuauGlobalVariableModuleIsolation)
|
|||
LUAU_FASTFLAGVARIABLE(LuauFragmentAutocompleteIfRecommendations)
|
||||
LUAU_FASTFLAG(LuauExpectedTypeVisitor)
|
||||
LUAU_FASTFLAGVARIABLE(LuauPopulateRefinedTypesInFragmentFromOldSolver)
|
||||
LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFragmentRequiresCanBeResolvedToAModule)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
static std::pair<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
|
||||
// e.g. local function fn() : ... - typically we want to provide autocomplete results if you're
|
||||
// editing type annotations in this range
|
||||
|
@ -761,7 +765,7 @@ void cloneTypesFromFragment(
|
|||
destScope->returnType = Luau::cloneIncremental(staleScope->returnType, *destArena, cloneState, destScope);
|
||||
}
|
||||
|
||||
static FrontendModuleResolver& getModuleResolver(Frontend& frontend, std::optional<FrontendOptions> options)
|
||||
static FrontendModuleResolver& getModuleResolver_DEPRECATED(Frontend& frontend, std::optional<FrontendOptions> options)
|
||||
{
|
||||
if (FFlag::LuauSolverV2 || !options)
|
||||
return frontend.moduleResolver;
|
||||
|
@ -769,6 +773,14 @@ static FrontendModuleResolver& getModuleResolver(Frontend& frontend, std::option
|
|||
return options->forAutocomplete ? frontend.moduleResolverForAutocomplete : frontend.moduleResolver;
|
||||
}
|
||||
|
||||
static FrontendModuleResolver& getModuleResolver(Frontend& frontend, std::optional<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)
|
||||
{
|
||||
return (stat->location.begin < cursorPos);
|
||||
|
@ -1066,6 +1078,42 @@ static void reportFragmentString(IFragmentAutocompleteReporter* reporter, std::s
|
|||
reporter->reportFragmentString(fragment);
|
||||
}
|
||||
|
||||
struct ScopedExit
|
||||
{
|
||||
public:
|
||||
explicit ScopedExit(std::function<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_(
|
||||
Frontend& frontend,
|
||||
AstStatBlock* root,
|
||||
|
@ -1078,7 +1126,6 @@ FragmentTypeCheckResult typecheckFragment_(
|
|||
)
|
||||
{
|
||||
LUAU_TIMETRACE_SCOPE("Luau::typecheckFragment_", "FragmentAutocomplete");
|
||||
|
||||
freeze(stale->internalTypes);
|
||||
freeze(stale->interfaceTypes);
|
||||
ModulePtr incrementalModule = std::make_shared<Module>();
|
||||
|
@ -1107,7 +1154,7 @@ FragmentTypeCheckResult typecheckFragment_(
|
|||
unifierState.counters.iterationLimit = limits.unifierIterationLimit.value_or(FInt::LuauTypeInferIterationLimit);
|
||||
|
||||
/// Initialize the normalizer
|
||||
Normalizer normalizer{&incrementalModule->internalTypes, frontend.builtinTypes, NotNull{&unifierState}};
|
||||
Normalizer normalizer{&incrementalModule->internalTypes, frontend.builtinTypes, NotNull{&unifierState}, SolverMode::New};
|
||||
|
||||
/// User defined type functions runtime
|
||||
TypeFunctionRuntime typeFunctionRuntime(iceHandler, NotNull{&limits});
|
||||
|
@ -1120,6 +1167,17 @@ FragmentTypeCheckResult typecheckFragment_(
|
|||
|
||||
SimplifierPtr simplifier = newSimplifier(NotNull{&incrementalModule->internalTypes}, frontend.builtinTypes);
|
||||
|
||||
// IncrementalModule gets moved at the end of the function, so capturing it here will cause SIGSEGV.
|
||||
// We'll capture just the name instead, since that's all we need to clean up the requireTrace at the end
|
||||
ScopedExit scopedExit{[&, name = incrementalModule->name]()
|
||||
{
|
||||
frontend.requireTrace.erase(name);
|
||||
}};
|
||||
|
||||
if (FFlag::LuauFragmentRequiresCanBeResolvedToAModule)
|
||||
frontend.requireTrace[incrementalModule->name] = traceRequires(frontend.fileResolver, root, incrementalModule->name);
|
||||
|
||||
|
||||
FrontendModuleResolver& resolver = getModuleResolver(frontend, opts);
|
||||
std::shared_ptr<Scope> freshChildOfNearestScope = std::make_shared<Scope>(nullptr);
|
||||
/// Contraint Generator
|
||||
|
@ -1218,7 +1276,164 @@ FragmentTypeCheckResult typecheckFragment_(
|
|||
// In frontend we would forbid internal types
|
||||
// because this is just for autocomplete, we don't actually care
|
||||
// We also don't even need to typecheck - just synthesize types as best as we can
|
||||
freeze(incrementalModule->internalTypes);
|
||||
freeze(incrementalModule->interfaceTypes);
|
||||
freshChildOfNearestScope->parent = closestScope;
|
||||
return {std::move(incrementalModule), std::move(freshChildOfNearestScope)};
|
||||
}
|
||||
|
||||
FragmentTypeCheckResult typecheckFragment__DEPRECATED(
|
||||
Frontend& frontend,
|
||||
AstStatBlock* root,
|
||||
const ModulePtr& stale,
|
||||
const ScopePtr& closestScope,
|
||||
const Position& cursorPos,
|
||||
std::unique_ptr<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->interfaceTypes);
|
||||
freshChildOfNearestScope->parent = closestScope;
|
||||
|
@ -1243,7 +1458,8 @@ std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
|
|||
if (!frontend.allModuleDependenciesValid(moduleName, opts && opts->forAutocomplete))
|
||||
return {FragmentTypeCheckStatus::SkipAutocomplete, {}};
|
||||
|
||||
FrontendModuleResolver& resolver = getModuleResolver(frontend, opts);
|
||||
FrontendModuleResolver& resolver =
|
||||
FFlag::LuauUseWorkspacePropToChooseSolver ? getModuleResolver(frontend, opts) : getModuleResolver_DEPRECATED(frontend, opts);
|
||||
ModulePtr module = resolver.getModule(moduleName);
|
||||
if (!module)
|
||||
{
|
||||
|
@ -1268,7 +1484,11 @@ std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
|
|||
const ScopePtr& closestScope = FFlag::LuauBetterScopeSelection ? findClosestScope(module, parseResult.scopePos)
|
||||
: findClosestScope_DEPRECATED(module, parseResult.nearestStatement);
|
||||
FragmentTypeCheckResult result =
|
||||
typecheckFragment_(frontend, parseResult.root, module, closestScope, cursorPos, std::move(parseResult.alloc), frontendOptions, reporter);
|
||||
FFlag::LuauFragmentRequiresCanBeResolvedToAModule
|
||||
? typecheckFragment_(frontend, parseResult.root, module, closestScope, cursorPos, std::move(parseResult.alloc), frontendOptions, reporter)
|
||||
: typecheckFragment__DEPRECATED(
|
||||
frontend, parseResult.root, module, closestScope, cursorPos, std::move(parseResult.alloc), frontendOptions, reporter
|
||||
);
|
||||
result.ancestry = std::move(parseResult.ancestry);
|
||||
reportFragmentString(reporter, tryParse->fragmentToParse);
|
||||
return {FragmentTypeCheckStatus::Success, result};
|
||||
|
|
|
@ -388,30 +388,31 @@ double getTimestamp()
|
|||
} // namespace
|
||||
|
||||
Frontend::Frontend(FileResolver* fileResolver, ConfigResolver* configResolver, const FrontendOptions& options)
|
||||
: builtinTypes(NotNull{&builtinTypes_})
|
||||
: useNewLuauSolver(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old)
|
||||
, builtinTypes(NotNull{&builtinTypes_})
|
||||
, fileResolver(fileResolver)
|
||||
, moduleResolver(this)
|
||||
, moduleResolverForAutocomplete(this)
|
||||
, globals(builtinTypes)
|
||||
, globalsForAutocomplete(builtinTypes)
|
||||
, globals(builtinTypes, getLuauSolverMode())
|
||||
, globalsForAutocomplete(builtinTypes, getLuauSolverMode())
|
||||
, configResolver(configResolver)
|
||||
, options(options)
|
||||
{
|
||||
}
|
||||
|
||||
void Frontend::setLuauSolverSelectionFromWorkspace(bool newSolverEnabled)
|
||||
void Frontend::setLuauSolverSelectionFromWorkspace(SolverMode mode)
|
||||
{
|
||||
useNewLuauSolver.store(newSolverEnabled);
|
||||
useNewLuauSolver.store(mode);
|
||||
}
|
||||
|
||||
bool Frontend::getLuauSolverSelection() const
|
||||
SolverMode Frontend::getLuauSolverMode() const
|
||||
{
|
||||
return useNewLuauSolver.load();
|
||||
}
|
||||
|
||||
bool Frontend::getLuauSolverSelectionFlagged() const
|
||||
{
|
||||
return FFlag::LuauUseWorkspacePropToChooseSolver ? getLuauSolverSelection() : FFlag::LuauSolverV2;
|
||||
if (FFlag::LuauUseWorkspacePropToChooseSolver)
|
||||
return useNewLuauSolver.load();
|
||||
else if (FFlag::LuauSolverV2)
|
||||
return SolverMode::New;
|
||||
else
|
||||
return SolverMode::Old;
|
||||
}
|
||||
|
||||
void Frontend::parse(const ModuleName& name)
|
||||
|
@ -464,7 +465,7 @@ CheckResult Frontend::check(const ModuleName& name, std::optional<FrontendOption
|
|||
LUAU_TIMETRACE_ARGUMENT("name", name.c_str());
|
||||
|
||||
FrontendOptions frontendOptions = optionOverride.value_or(options);
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (getLuauSolverMode() == SolverMode::New)
|
||||
frontendOptions.forAutocomplete = false;
|
||||
|
||||
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);
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (getLuauSolverMode() == SolverMode::New)
|
||||
frontendOptions.forAutocomplete = false;
|
||||
|
||||
// By taking data into locals, we make sure queue is cleared at the end, even if an ICE or a different exception is thrown
|
||||
|
@ -709,7 +710,7 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
|
|||
|
||||
std::optional<CheckResult> Frontend::getCheckResult(const ModuleName& name, bool accumulateNested, bool forAutocomplete)
|
||||
{
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (getLuauSolverMode() == SolverMode::New)
|
||||
forAutocomplete = false;
|
||||
|
||||
auto it = sourceNodes.find(name);
|
||||
|
@ -1039,7 +1040,7 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item)
|
|||
if (item.options.customModuleCheck)
|
||||
item.options.customModuleCheck(sourceModule, *module);
|
||||
|
||||
if (FFlag::LuauSolverV2 && mode == Mode::NoCheck)
|
||||
if ((getLuauSolverMode() == SolverMode::New) && mode == Mode::NoCheck)
|
||||
module->errors.clear();
|
||||
|
||||
if (item.options.runLintChecks)
|
||||
|
@ -1461,7 +1462,7 @@ ModulePtr check(
|
|||
unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit;
|
||||
unifierState.counters.iterationLimit = limits.unifierIterationLimit.value_or(FInt::LuauTypeInferIterationLimit);
|
||||
|
||||
Normalizer normalizer{&result->internalTypes, builtinTypes, NotNull{&unifierState}};
|
||||
Normalizer normalizer{&result->internalTypes, builtinTypes, NotNull{&unifierState}, SolverMode::New};
|
||||
SimplifierPtr simplifier = newSimplifier(NotNull{&result->internalTypes}, builtinTypes);
|
||||
TypeFunctionRuntime typeFunctionRuntime{iceHandler, NotNull{&limits}};
|
||||
|
||||
|
@ -1728,7 +1729,7 @@ ModulePtr Frontend::check(
|
|||
TypeCheckLimits typeCheckLimits
|
||||
)
|
||||
{
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (getLuauSolverMode() == SolverMode::New)
|
||||
{
|
||||
auto prepareModuleScopeWrap = [this, forAutocomplete](const ModuleName& name, const ScopePtr& scope)
|
||||
{
|
||||
|
@ -2057,4 +2058,10 @@ void Frontend::clear()
|
|||
requireTrace.clear();
|
||||
}
|
||||
|
||||
void Frontend::clearBuiltinEnvironments()
|
||||
{
|
||||
environments.clear();
|
||||
builtinDefinitions.clear();
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "Luau/Common.h"
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/InsertionOrderedMap.h"
|
||||
#include "Luau/OrderedSet.h"
|
||||
#include "Luau/Polarity.h"
|
||||
#include "Luau/Scope.h"
|
||||
#include "Luau/ToString.h"
|
||||
|
@ -23,68 +24,6 @@ LUAU_FASTFLAGVARIABLE(LuauGeneralizationCannotMutateAcrossModules)
|
|||
namespace Luau
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
template<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
|
||||
{
|
||||
NotNull<TypeArena> arena;
|
||||
|
|
|
@ -5,8 +5,9 @@
|
|||
namespace Luau
|
||||
{
|
||||
|
||||
GlobalTypes::GlobalTypes(NotNull<BuiltinTypes> builtinTypes)
|
||||
GlobalTypes::GlobalTypes(NotNull<BuiltinTypes> builtinTypes, SolverMode mode)
|
||||
: builtinTypes(builtinTypes)
|
||||
, mode(mode)
|
||||
{
|
||||
globalScope = 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});
|
||||
|
||||
unfreeze(*builtinTypes->arena);
|
||||
TypeId stringMetatableTy = makeStringMetatable(builtinTypes);
|
||||
TypeId stringMetatableTy = makeStringMetatable(builtinTypes, mode);
|
||||
asMutable(builtinTypes->stringType)->ty.emplace<PrimitiveType>(PrimitiveType::String, stringMetatableTy);
|
||||
persist(stringMetatableTy);
|
||||
freeze(*builtinTypes->arena);
|
||||
|
|
|
@ -265,6 +265,8 @@ static void errorToString(std::ostream& stream, const T& err)
|
|||
stream << "GenericTypePackCountMismatch { subTyGenericPackCount = " << err.subTyGenericPackCount
|
||||
<< ", superTyGenericPackCount = " << err.superTyGenericPackCount << " }";
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, MultipleNonviableOverloads>)
|
||||
stream << "MultipleNonviableOverloads { attemptedArgCount = " << err.attemptedArgCount << " }";
|
||||
else
|
||||
static_assert(always_false_v<T>, "Non-exhaustive type switch");
|
||||
}
|
||||
|
|
|
@ -192,7 +192,7 @@ struct NonStrictTypeChecker
|
|||
, ice(ice)
|
||||
, arena(arena)
|
||||
, module(module)
|
||||
, normalizer{arena, builtinTypes, unifierState, /* cache inhabitance */ true}
|
||||
, normalizer{arena, builtinTypes, unifierState, SolverMode::New, /* cache inhabitance */ true}
|
||||
, subtyping{builtinTypes, arena, simplifier, NotNull(&normalizer), typeFunctionRuntime, ice}
|
||||
, dfg(dfg)
|
||||
, limits(limits)
|
||||
|
|
|
@ -24,6 +24,7 @@ LUAU_FASTFLAG(LuauSolverV2)
|
|||
LUAU_FASTFLAGVARIABLE(LuauNormalizationIntersectTablesPreservesExternTypes)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNormalizationReorderFreeTypeIntersect)
|
||||
LUAU_FASTFLAG(LuauRefineTablesWithReadType)
|
||||
LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -392,7 +393,7 @@ NormalizationResult Normalizer::isInhabited(TypeId ty, Set<TypeId>& seen)
|
|||
{
|
||||
for (const auto& [_, prop] : ttv->props)
|
||||
{
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (useNewLuauSolver())
|
||||
{
|
||||
// A table enclosing a read property whose type is uninhabitable is also itself uninhabitable,
|
||||
// but not its write property. That just means the write property doesn't exist, and so is readonly.
|
||||
|
@ -719,11 +720,18 @@ static void assertInvariant(const NormalizedType& norm)
|
|||
#endif
|
||||
}
|
||||
|
||||
Normalizer::Normalizer(TypeArena* arena, NotNull<BuiltinTypes> builtinTypes, NotNull<UnifierSharedState> sharedState, bool cacheInhabitance)
|
||||
Normalizer::Normalizer(
|
||||
TypeArena* arena,
|
||||
NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<UnifierSharedState> sharedState,
|
||||
SolverMode solverMode,
|
||||
bool cacheInhabitance
|
||||
)
|
||||
: arena(arena)
|
||||
, builtinTypes(builtinTypes)
|
||||
, sharedState(sharedState)
|
||||
, cacheInhabitance(cacheInhabitance)
|
||||
, solverMode(solverMode)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -1583,6 +1591,11 @@ bool Normalizer::withinResourceLimits()
|
|||
return true;
|
||||
}
|
||||
|
||||
bool Normalizer::useNewLuauSolver() const
|
||||
{
|
||||
return FFlag::LuauUseWorkspacePropToChooseSolver ? (solverMode == SolverMode::New) : FFlag::LuauSolverV2;
|
||||
}
|
||||
|
||||
NormalizationResult Normalizer::intersectNormalWithNegationTy(TypeId toNegate, NormalizedType& intersect)
|
||||
{
|
||||
|
||||
|
@ -2457,7 +2470,7 @@ std::optional<TypeId> Normalizer::intersectionOfTables(TypeId here, TypeId there
|
|||
{
|
||||
const auto& [_name, tprop] = *tfound;
|
||||
// TODO: variance issues here, which can't be fixed until we have read/write property types
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (useNewLuauSolver())
|
||||
{
|
||||
if (hprop.readTy.has_value())
|
||||
{
|
||||
|
@ -3041,7 +3054,7 @@ NormalizationResult Normalizer::intersectNormalWithTy(
|
|||
}
|
||||
else if (get<TableType>(there) || get<MetatableType>(there))
|
||||
{
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauNormalizationIntersectTablesPreservesExternTypes)
|
||||
if (useNewLuauSolver() && FFlag::LuauNormalizationIntersectTablesPreservesExternTypes)
|
||||
{
|
||||
NormalizedExternType externTypes = std::move(here.externTypes);
|
||||
TypeIds tables = std::move(here.tables);
|
||||
|
@ -3323,7 +3336,7 @@ TypeId Normalizer::typeFromNormal(const NormalizedType& norm)
|
|||
if (!get<NeverType>(norm.buffers))
|
||||
result.push_back(builtinTypes->bufferType);
|
||||
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (useNewLuauSolver())
|
||||
{
|
||||
result.reserve(result.size() + norm.tables.size());
|
||||
for (auto table : norm.tables)
|
||||
|
@ -3361,30 +3374,50 @@ bool isSubtype(
|
|||
NotNull<Scope> scope,
|
||||
NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<Simplifier> simplifier,
|
||||
InternalErrorReporter& ice
|
||||
InternalErrorReporter& ice,
|
||||
SolverMode solverMode
|
||||
)
|
||||
{
|
||||
UnifierSharedState sharedState{&ice};
|
||||
TypeArena arena;
|
||||
Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}};
|
||||
TypeCheckLimits limits;
|
||||
TypeFunctionRuntime typeFunctionRuntime{
|
||||
NotNull{&ice}, NotNull{&limits}
|
||||
}; // TODO: maybe subtyping checks should not invoke user-defined type function runtime
|
||||
|
||||
// Subtyping under DCR is not implemented using unification!
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauUseWorkspacePropToChooseSolver)
|
||||
{
|
||||
Subtyping subtyping{builtinTypes, NotNull{&arena}, simplifier, NotNull{&normalizer}, NotNull{&typeFunctionRuntime}, NotNull{&ice}};
|
||||
Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}, solverMode};
|
||||
if (solverMode == SolverMode::New)
|
||||
{
|
||||
Subtyping subtyping{builtinTypes, NotNull{&arena}, simplifier, NotNull{&normalizer}, NotNull{&typeFunctionRuntime}, NotNull{&ice}};
|
||||
|
||||
return subtyping.isSubtype(subTy, superTy, scope).isSubtype;
|
||||
return subtyping.isSubtype(subTy, superTy, scope).isSubtype;
|
||||
}
|
||||
else
|
||||
{
|
||||
Unifier u{NotNull{&normalizer}, scope, Location{}, Covariant};
|
||||
|
||||
u.tryUnify(subTy, superTy);
|
||||
return !u.failure;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Unifier u{NotNull{&normalizer}, scope, Location{}, Covariant};
|
||||
Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}, FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old};
|
||||
if (FFlag::LuauSolverV2)
|
||||
{
|
||||
Subtyping subtyping{builtinTypes, NotNull{&arena}, simplifier, NotNull{&normalizer}, NotNull{&typeFunctionRuntime}, NotNull{&ice}};
|
||||
|
||||
u.tryUnify(subTy, superTy);
|
||||
return !u.failure;
|
||||
return subtyping.isSubtype(subTy, superTy, scope).isSubtype;
|
||||
}
|
||||
else
|
||||
{
|
||||
Unifier u{NotNull{&normalizer}, scope, Location{}, Covariant};
|
||||
|
||||
u.tryUnify(subTy, superTy);
|
||||
return !u.failure;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3394,30 +3427,50 @@ bool isSubtype(
|
|||
NotNull<Scope> scope,
|
||||
NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<Simplifier> simplifier,
|
||||
InternalErrorReporter& ice
|
||||
InternalErrorReporter& ice,
|
||||
SolverMode solverMode
|
||||
)
|
||||
{
|
||||
UnifierSharedState sharedState{&ice};
|
||||
TypeArena arena;
|
||||
Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}};
|
||||
TypeCheckLimits limits;
|
||||
TypeFunctionRuntime typeFunctionRuntime{
|
||||
NotNull{&ice}, NotNull{&limits}
|
||||
}; // TODO: maybe subtyping checks should not invoke user-defined type function runtime
|
||||
|
||||
// Subtyping under DCR is not implemented using unification!
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauUseWorkspacePropToChooseSolver)
|
||||
{
|
||||
Subtyping subtyping{builtinTypes, NotNull{&arena}, simplifier, NotNull{&normalizer}, NotNull{&typeFunctionRuntime}, NotNull{&ice}};
|
||||
Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}, solverMode};
|
||||
if (solverMode == SolverMode::New)
|
||||
{
|
||||
Subtyping subtyping{builtinTypes, NotNull{&arena}, simplifier, NotNull{&normalizer}, NotNull{&typeFunctionRuntime}, NotNull{&ice}};
|
||||
|
||||
return subtyping.isSubtype(subPack, superPack, scope).isSubtype;
|
||||
return subtyping.isSubtype(subPack, superPack, scope).isSubtype;
|
||||
}
|
||||
else
|
||||
{
|
||||
Unifier u{NotNull{&normalizer}, scope, Location{}, Covariant};
|
||||
|
||||
u.tryUnify(subPack, superPack);
|
||||
return !u.failure;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Unifier u{NotNull{&normalizer}, scope, Location{}, Covariant};
|
||||
Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}, FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old};
|
||||
if (FFlag::LuauSolverV2)
|
||||
{
|
||||
Subtyping subtyping{builtinTypes, NotNull{&arena}, simplifier, NotNull{&normalizer}, NotNull{&typeFunctionRuntime}, NotNull{&ice}};
|
||||
|
||||
u.tryUnify(subPack, superPack);
|
||||
return !u.failure;
|
||||
return subtyping.isSubtype(subPack, superPack, scope).isSubtype;
|
||||
}
|
||||
else
|
||||
{
|
||||
Unifier u{NotNull{&normalizer}, scope, Location{}, Covariant};
|
||||
|
||||
u.tryUnify(subPack, superPack);
|
||||
return !u.failure;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
#include "Luau/Unifier2.h"
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauArityMismatchOnUndersaturatedUnknownArguments)
|
||||
LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2)
|
||||
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -201,6 +201,43 @@ bool OverloadResolver::isLiteral(AstExpr* expr)
|
|||
expr->is<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_(
|
||||
TypeId fnTy,
|
||||
const FunctionType* fn,
|
||||
|
@ -292,14 +329,44 @@ std::pair<OverloadResolver::Analysis, ErrorVec> OverloadResolver::checkOverload_
|
|||
return {Analysis::Ok, {}};
|
||||
}
|
||||
|
||||
if (FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2)
|
||||
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping)
|
||||
{
|
||||
if (reason.subPath == TypePath::Path{{TypePath::PackField::Arguments, TypePath::PackField::Tail}} && reason.superPath == justArguments)
|
||||
// If we have an arity mismatch with generic type pack parameters, then subPath matches Args :: Tail :: ...
|
||||
// and superPath matches Args :: ...
|
||||
if (reason.subPath.components.size() >= 2 && reason.subPath.components[0] == TypePath::PackField::Arguments &&
|
||||
reason.subPath.components[1] == TypePath::PackField::Tail && reason.superPath.components.size() >= 1 &&
|
||||
reason.superPath.components[0] == TypePath::PackField::Arguments)
|
||||
{
|
||||
if (const auto [requiredHead, requiredTail] = flatten(fn->argTypes); requiredTail)
|
||||
{
|
||||
if (const auto genericTail = get<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
|
||||
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))
|
||||
{
|
||||
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
|
||||
: fnExpr->location;
|
||||
|
||||
std::optional<TypeId> failedSubTy = traverseForType(fnTy, reason.subPath, builtinTypes);
|
||||
std::optional<TypeId> failedSuperTy = traverseForType(prospectiveFunction, reason.superPath, builtinTypes);
|
||||
std::optional<TypeId> failedSubTy = FFlag::LuauReturnMappedGenericPacksFromSubtyping
|
||||
? 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)))
|
||||
{
|
||||
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> failedSuperPack = traverseForPack(prospectiveFunction, reason.superPath, builtinTypes);
|
||||
std::optional<TypePackId> failedSubPack = FFlag::LuauReturnMappedGenericPacksFromSubtyping
|
||||
? 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)
|
||||
{
|
||||
|
|
|
@ -22,6 +22,7 @@ LUAU_FASTFLAGVARIABLE(LuauSimplificationTableExternType)
|
|||
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
|
||||
LUAU_FASTFLAGVARIABLE(LuauRelateTablesAreNeverDisjoint)
|
||||
LUAU_FASTFLAG(LuauRefineTablesWithReadType)
|
||||
LUAU_FASTFLAGVARIABLE(LuauMissingSeenSetRelate)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -597,7 +598,12 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
|
|||
if (auto propInExternType = re->props.find(name); propInExternType != re->props.end())
|
||||
{
|
||||
Relation propRel;
|
||||
if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
|
||||
if (FFlag::LuauMissingSeenSetRelate)
|
||||
{
|
||||
LUAU_ASSERT(prop.readTy && propInExternType->second.readTy);
|
||||
propRel = relate(*prop.readTy, *propInExternType->second.readTy, seen);
|
||||
}
|
||||
else if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
|
||||
{
|
||||
LUAU_ASSERT(prop.readTy && propInExternType->second.readTy);
|
||||
propRel = relate(*prop.readTy, *propInExternType->second.readTy);
|
||||
|
|
|
@ -18,10 +18,10 @@
|
|||
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity)
|
||||
LUAU_FASTINTVARIABLE(LuauSubtypingReasoningLimit, 100)
|
||||
LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSubtypingCheckFunctionGenericCounts)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization4)
|
||||
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
|
||||
LUAU_FASTFLAGVARIABLE(LuauReturnMappedGenericPacksFromSubtyping)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -63,20 +63,52 @@ size_t SubtypingReasoningHash::operator()(const SubtypingReasoning& r) const
|
|||
}
|
||||
|
||||
template<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)
|
||||
return;
|
||||
|
||||
for (const SubtypingReasoning& reasoning : result.reasoning)
|
||||
{
|
||||
LUAU_ASSERT(traverse(subTy, reasoning.subPath, builtinTypes));
|
||||
LUAU_ASSERT(traverse(superTy, reasoning.superPath, builtinTypes));
|
||||
LUAU_ASSERT(traverse_DEPRECATED(subTy, reasoning.subPath, builtinTypes));
|
||||
LUAU_ASSERT(traverse_DEPRECATED(superTy, reasoning.superPath, builtinTypes));
|
||||
}
|
||||
}
|
||||
|
||||
template<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<>
|
||||
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.
|
||||
}
|
||||
|
@ -481,6 +513,11 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope
|
|||
* cacheable.
|
||||
*/
|
||||
|
||||
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping)
|
||||
{
|
||||
result.mappedGenericPacks = std::move(env.mappedGenericPacks);
|
||||
}
|
||||
|
||||
if (result.isCacheable)
|
||||
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)
|
||||
{
|
||||
SubtypingEnvironment env;
|
||||
return isCovariantWith(env, subTp, superTp, scope);
|
||||
|
||||
SubtypingResult result = isCovariantWith(env, subTp, superTp, scope);
|
||||
|
||||
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping)
|
||||
{
|
||||
if (!env.mappedGenericPacks.empty())
|
||||
result.mappedGenericPacks = std::move(env.mappedGenericPacks);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::cache(SubtypingEnvironment& env, SubtypingResult result, TypeId subTy, TypeId superTy)
|
||||
{
|
||||
const std::pair<TypeId, TypeId> p{subTy, superTy};
|
||||
|
||||
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping && !env.mappedGenericPacks.empty())
|
||||
result.mappedGenericPacks = env.mappedGenericPacks;
|
||||
|
||||
if (result.isCacheable)
|
||||
resultCache[p] = result;
|
||||
else
|
||||
|
@ -547,11 +597,27 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
|
|||
|
||||
const SubtypingResult* cachedResult = resultCache.find({subTy, superTy});
|
||||
if (cachedResult)
|
||||
{
|
||||
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping)
|
||||
{
|
||||
for (const auto& [genericTp, boundTp] : cachedResult->mappedGenericPacks)
|
||||
env.mappedGenericPacks.try_insert(genericTp, boundTp);
|
||||
}
|
||||
|
||||
return *cachedResult;
|
||||
}
|
||||
|
||||
cachedResult = env.tryFindSubtypingResult({subTy, superTy});
|
||||
if (cachedResult)
|
||||
{
|
||||
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping)
|
||||
{
|
||||
for (const auto& [genericTp, boundTp] : cachedResult->mappedGenericPacks)
|
||||
env.mappedGenericPacks.try_insert(genericTp, boundTp);
|
||||
}
|
||||
|
||||
return *cachedResult;
|
||||
}
|
||||
|
||||
// TODO: Do we care about returning a proof that this is error-suppressing?
|
||||
// e.g. given `a | error <: a | error` where both operands are pointer equal,
|
||||
|
@ -791,7 +857,10 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
|
|||
else if (auto p = get2<SingletonType, TableType>(subTy, superTy))
|
||||
result = isCovariantWith(env, p, scope);
|
||||
|
||||
assertReasoningValid(subTy, superTy, result, builtinTypes);
|
||||
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping)
|
||||
assertReasoningValid(subTy, superTy, result, builtinTypes, arena);
|
||||
else
|
||||
assertReasoningValid_DEPRECATED(subTy, superTy, result, builtinTypes);
|
||||
|
||||
return cache(env, std::move(result), subTy, superTy);
|
||||
}
|
||||
|
@ -840,14 +909,31 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
|
|||
// <X>(X) -> () <: (T) -> ()
|
||||
|
||||
// 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), begin(superHead) + headSize);
|
||||
TypePackId superTailPack = arena->addTypePack(std::move(headSlice), superTail);
|
||||
|
||||
if (TypePackId* other = env.getMappedPackBounds(*subTail))
|
||||
// TODO: TypePath can't express "slice of a pack + its tail".
|
||||
results.push_back(isCovariantWith(env, *other, superTailPack, scope).withSubComponent(TypePath::PackField::Tail));
|
||||
{
|
||||
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping)
|
||||
{
|
||||
const TypePack* tp = get<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
|
||||
env.mappedGenericPacks.try_insert(*subTail, superTailPack);
|
||||
|
||||
|
@ -897,17 +983,31 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
|
|||
// <X...>(X...) -> () <: (T) -> ()
|
||||
|
||||
// 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), begin(subHead) + headSize);
|
||||
TypePackId subTailPack = arena->addTypePack(std::move(headSlice), subTail);
|
||||
|
||||
if (TypePackId* other = env.getMappedPackBounds(*superTail))
|
||||
// TODO: TypePath can't express "slice of a pack + its tail".
|
||||
if (FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2)
|
||||
results.push_back(isCovariantWith(env, subTailPack, *other, scope).withSuperComponent(TypePath::PackField::Tail));
|
||||
{
|
||||
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping)
|
||||
{
|
||||
const TypePack* tp = get<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
|
||||
results.push_back(isContravariantWith(env, subTailPack, *other, scope).withSuperComponent(TypePath::PackField::Tail));
|
||||
}
|
||||
else
|
||||
env.mappedGenericPacks.try_insert(*superTail, subTailPack);
|
||||
|
||||
|
@ -1041,7 +1141,11 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
|
|||
}
|
||||
|
||||
SubtypingResult result = SubtypingResult::all(results);
|
||||
assertReasoningValid(subTp, superTp, result, builtinTypes);
|
||||
|
||||
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping)
|
||||
assertReasoningValid(subTp, superTp, result, builtinTypes, arena);
|
||||
else
|
||||
assertReasoningValid_DEPRECATED(subTp, superTp, result, builtinTypes);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -1073,7 +1177,10 @@ SubtypingResult Subtyping::isContravariantWith(SubtypingEnvironment& env, SubTy&
|
|||
}
|
||||
}
|
||||
|
||||
assertReasoningValid(subTy, superTy, result, builtinTypes);
|
||||
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping)
|
||||
assertReasoningValid(subTy, superTy, result, builtinTypes, arena);
|
||||
else
|
||||
assertReasoningValid_DEPRECATED(subTy, superTy, result, builtinTypes);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -1091,7 +1198,11 @@ SubtypingResult Subtyping::isInvariantWith(SubtypingEnvironment& env, SubTy&& su
|
|||
reasoning.variance = SubtypingVariance::Invariant;
|
||||
}
|
||||
|
||||
assertReasoningValid(subTy, superTy, result, builtinTypes);
|
||||
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping)
|
||||
assertReasoningValid(subTy, superTy, result, builtinTypes, arena);
|
||||
else
|
||||
assertReasoningValid_DEPRECATED(subTy, superTy, result, builtinTypes);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ LUAU_FASTFLAGVARIABLE(LuauEnableDenseTableAlias)
|
|||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSolverAgnosticStringification)
|
||||
|
||||
/*
|
||||
* Enables increasing levels of verbosity for Luau type names when stringifying.
|
||||
|
@ -86,8 +87,14 @@ struct FindCyclicTypes final : TypeVisitor
|
|||
{
|
||||
if (!visited.insert(ty))
|
||||
return false;
|
||||
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauSolverAgnosticStringification)
|
||||
{
|
||||
LUAU_ASSERT(ft.lowerBound);
|
||||
LUAU_ASSERT(ft.upperBound);
|
||||
traverse(ft.lowerBound);
|
||||
traverse(ft.upperBound);
|
||||
}
|
||||
else if (FFlag::LuauSolverV2)
|
||||
{
|
||||
// TODO: Replace these if statements with assert()s when we
|
||||
// delete FFlag::LuauSolverV2.
|
||||
|
@ -440,7 +447,7 @@ struct TypeStringifier
|
|||
|
||||
void stringify(const std::string& name, const Property& prop)
|
||||
{
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticStringification)
|
||||
return _newStringify(name, prop);
|
||||
|
||||
emitKey(name);
|
||||
|
@ -503,9 +510,44 @@ struct TypeStringifier
|
|||
{
|
||||
state.result.invalid = true;
|
||||
|
||||
// TODO: ftv.lowerBound and ftv.upperBound should always be non-nil when
|
||||
// the new solver is used. This can be replaced with an assert.
|
||||
if (FFlag::LuauSolverV2 && ftv.lowerBound && ftv.upperBound)
|
||||
// Free types are guaranteed to have upper and lower bounds now.
|
||||
if (FFlag::LuauSolverAgnosticStringification)
|
||||
{
|
||||
LUAU_ASSERT(ftv.lowerBound);
|
||||
LUAU_ASSERT(ftv.upperBound);
|
||||
const TypeId lowerBound = follow(ftv.lowerBound);
|
||||
const TypeId upperBound = follow(ftv.upperBound);
|
||||
if (get<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 upperBound = follow(ftv.upperBound);
|
||||
|
@ -545,13 +587,16 @@ struct TypeStringifier
|
|||
|
||||
state.emit(state.getName(ty));
|
||||
|
||||
if (FFlag::LuauSolverV2 && FInt::DebugLuauVerboseTypeNames >= 1)
|
||||
if (FFlag::LuauSolverAgnosticStringification && FInt::DebugLuauVerboseTypeNames >= 1)
|
||||
state.emit(ftv.polarity);
|
||||
else if (FFlag::LuauSolverV2 && FInt::DebugLuauVerboseTypeNames >= 1)
|
||||
state.emit(ftv.polarity);
|
||||
|
||||
|
||||
if (FInt::DebugLuauVerboseTypeNames >= 2)
|
||||
{
|
||||
state.emit("-");
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticStringification)
|
||||
state.emitLevel(ftv.scope);
|
||||
else
|
||||
state.emit(ftv.level);
|
||||
|
@ -583,7 +628,7 @@ struct TypeStringifier
|
|||
if (FInt::DebugLuauVerboseTypeNames >= 2)
|
||||
{
|
||||
state.emit("-");
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticStringification)
|
||||
state.emitLevel(gtv.scope);
|
||||
else
|
||||
state.emit(gtv.level);
|
||||
|
@ -686,7 +731,7 @@ struct TypeStringifier
|
|||
state.emit(">");
|
||||
}
|
||||
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticStringification)
|
||||
{
|
||||
if (ftv.isCheckedFunction)
|
||||
state.emit("@checked ");
|
||||
|
@ -783,10 +828,10 @@ struct TypeStringifier
|
|||
|
||||
std::string openbrace = "@@@";
|
||||
std::string closedbrace = "@@@?!";
|
||||
switch (state.opts.hideTableKind ? (FFlag::LuauSolverV2 ? TableState::Sealed : TableState::Unsealed) : ttv.state)
|
||||
switch (state.opts.hideTableKind ? ((FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticStringification) ? TableState::Sealed : TableState::Unsealed) : ttv.state)
|
||||
{
|
||||
case TableState::Sealed:
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticStringification)
|
||||
{
|
||||
openbrace = "{";
|
||||
closedbrace = "}";
|
||||
|
@ -799,7 +844,7 @@ struct TypeStringifier
|
|||
}
|
||||
break;
|
||||
case TableState::Unsealed:
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticStringification)
|
||||
{
|
||||
state.result.invalid = true;
|
||||
openbrace = "{|";
|
||||
|
@ -1314,7 +1359,7 @@ struct TypePackStringifier
|
|||
if (FInt::DebugLuauVerboseTypeNames >= 2)
|
||||
{
|
||||
state.emit("-");
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticStringification)
|
||||
state.emitLevel(pack.scope);
|
||||
else
|
||||
state.emit(pack.level);
|
||||
|
@ -1336,7 +1381,7 @@ struct TypePackStringifier
|
|||
if (FInt::DebugLuauVerboseTypeNames >= 2)
|
||||
{
|
||||
state.emit("-");
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticStringification)
|
||||
state.emitLevel(pack.scope);
|
||||
else
|
||||
state.emit(pack.level);
|
||||
|
|
|
@ -10,8 +10,6 @@
|
|||
#include <limits>
|
||||
#include <math.h>
|
||||
|
||||
LUAU_FASTFLAG(LuauStoreLocalAnnotationColonPositions)
|
||||
|
||||
namespace
|
||||
{
|
||||
bool isIdentifierStartChar(char c)
|
||||
|
@ -322,8 +320,7 @@ struct Printer
|
|||
writer.identifier(local.name.value);
|
||||
if (writeTypes && local.annotation)
|
||||
{
|
||||
if (FFlag::LuauStoreLocalAnnotationColonPositions)
|
||||
advance(colonPosition);
|
||||
advance(colonPosition);
|
||||
writer.symbol(":");
|
||||
visualizeTypeAnnotation(*local.annotation);
|
||||
}
|
||||
|
@ -928,7 +925,7 @@ struct Printer
|
|||
for (size_t i = 0; i < a->vars.size; i++)
|
||||
{
|
||||
varComma();
|
||||
if (FFlag::LuauStoreLocalAnnotationColonPositions && cstNode)
|
||||
if (cstNode)
|
||||
{
|
||||
LUAU_ASSERT(cstNode->varsAnnotationColonPositions.size > i);
|
||||
visualize(*a->vars.data[i], cstNode->varsAnnotationColonPositions.data[i]);
|
||||
|
@ -957,10 +954,7 @@ struct Printer
|
|||
|
||||
writer.keyword("for");
|
||||
|
||||
if (FFlag::LuauStoreLocalAnnotationColonPositions)
|
||||
visualize(*a->var, cstNode ? cstNode->annotationColonPosition : Position{0, 0});
|
||||
else
|
||||
visualize(*a->var, Position{0, 0});
|
||||
visualize(*a->var, cstNode ? cstNode->annotationColonPosition : Position{0, 0});
|
||||
|
||||
if (cstNode)
|
||||
advance(cstNode->equalsPosition);
|
||||
|
@ -994,7 +988,7 @@ struct Printer
|
|||
for (size_t i = 0; i < a->vars.size; i++)
|
||||
{
|
||||
varComma();
|
||||
if (FFlag::LuauStoreLocalAnnotationColonPositions && cstNode)
|
||||
if (cstNode)
|
||||
{
|
||||
LUAU_ASSERT(cstNode->varsAnnotationColonPositions.size > i);
|
||||
visualize(*a->vars.data[i], cstNode->varsAnnotationColonPositions.data[i]);
|
||||
|
@ -1317,7 +1311,7 @@ struct Printer
|
|||
writer.identifier(local->name.value);
|
||||
if (writeTypes && local->annotation)
|
||||
{
|
||||
if (FFlag::LuauStoreLocalAnnotationColonPositions && cstNode)
|
||||
if (cstNode)
|
||||
{
|
||||
LUAU_ASSERT(cstNode->argsAnnotationColonPositions.size > i);
|
||||
advance(cstNode->argsAnnotationColonPositions.data[i]);
|
||||
|
@ -1335,7 +1329,7 @@ struct Printer
|
|||
|
||||
if (func.varargAnnotation)
|
||||
{
|
||||
if (FFlag::LuauStoreLocalAnnotationColonPositions && cstNode)
|
||||
if (cstNode)
|
||||
{
|
||||
LUAU_ASSERT(cstNode->varargAnnotationColonPosition != Position({0, 0}));
|
||||
advance(cstNode->varargAnnotationColonPosition);
|
||||
|
|
|
@ -31,6 +31,7 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
|||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAG(LuauSubtypingCheckFunctionGenericCounts)
|
||||
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
|
||||
LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -711,7 +712,7 @@ Property Property::create(std::optional<TypeId> read, std::optional<TypeId> writ
|
|||
|
||||
TypeId Property::type_DEPRECATED() const
|
||||
{
|
||||
if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
|
||||
if (FFlag::LuauRemoveTypeCallsForReadWriteProps && !FFlag::LuauUseWorkspacePropToChooseSolver)
|
||||
LUAU_ASSERT(!FFlag::LuauSolverV2);
|
||||
|
||||
LUAU_ASSERT(readTy);
|
||||
|
@ -1001,7 +1002,7 @@ TypeId makeFunction(
|
|||
std::initializer_list<TypeId> retTypes
|
||||
);
|
||||
|
||||
TypeId makeStringMetatable(NotNull<BuiltinTypes> builtinTypes); // BuiltinDefinitions.cpp
|
||||
TypeId makeStringMetatable(NotNull<BuiltinTypes> builtinTypes, SolverMode mode); // BuiltinDefinitions.cpp
|
||||
|
||||
BuiltinTypes::BuiltinTypes()
|
||||
: arena(new TypeArena)
|
||||
|
|
|
@ -36,8 +36,11 @@ LUAU_FASTFLAG(LuauNewNonStrictFixGenericTypePacks)
|
|||
LUAU_FASTFLAGVARIABLE(LuauReportSubtypingErrors)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSkipMalformedTypeAliases)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTableLiteralSubtypeSpecificCheck2)
|
||||
LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch)
|
||||
LUAU_FASTFLAG(LuauSubtypingCheckFunctionGenericCounts)
|
||||
LUAU_FASTFLAG(LuauTableLiteralSubtypeCheckFunctionCalls)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSuppressErrorsForMultipleNonviableOverloads)
|
||||
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -317,7 +320,7 @@ TypeChecker2::TypeChecker2(
|
|||
, ice(unifierState->iceHandler)
|
||||
, sourceModule(sourceModule)
|
||||
, module(module)
|
||||
, normalizer{&module->internalTypes, builtinTypes, unifierState, /* cacheInhabitance */ true}
|
||||
, normalizer{&module->internalTypes, builtinTypes, unifierState, SolverMode::New, /* cacheInhabitance */ true}
|
||||
, _subtyping{builtinTypes, NotNull{&module->internalTypes}, simplifier, NotNull{&normalizer}, typeFunctionRuntime, NotNull{unifierState->iceHandler}}
|
||||
, subtyping(&_subtyping)
|
||||
{
|
||||
|
@ -1710,14 +1713,24 @@ void TypeChecker2::visitCall(AstExprCall* call)
|
|||
return; // Ok. Calling an uninhabited type is no-op.
|
||||
else if (!resolver.nonviableOverloads.empty())
|
||||
{
|
||||
if (resolver.nonviableOverloads.size() == 1 && !isErrorSuppressing(call->func->location, resolver.nonviableOverloads.front().first))
|
||||
reportErrors(resolver.nonviableOverloads.front().second);
|
||||
if (FFlag::LuauSuppressErrorsForMultipleNonviableOverloads)
|
||||
{
|
||||
const bool reportedErrors =
|
||||
reportNonviableOverloadErrors(resolver.nonviableOverloads, call->func->location, args.head.size(), call->location);
|
||||
if (!reportedErrors)
|
||||
return; // We did not report any errors, so we can just return.
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string s = "None of the overloads for function that accept ";
|
||||
s += std::to_string(args.head.size());
|
||||
s += " arguments are compatible.";
|
||||
reportError(GenericError{std::move(s)}, call->location);
|
||||
if (resolver.nonviableOverloads.size() == 1 && !isErrorSuppressing(call->func->location, resolver.nonviableOverloads.front().first))
|
||||
reportErrors(resolver.nonviableOverloads.front().second);
|
||||
else
|
||||
{
|
||||
std::string s = "None of the overloads for function that accept ";
|
||||
s += std::to_string(args.head.size());
|
||||
s += " arguments are compatible.";
|
||||
reportError(GenericError{std::move(s)}, call->location);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!resolver.arityMismatches.empty())
|
||||
|
@ -2046,19 +2059,24 @@ void TypeChecker2::visit(AstExprFunction* fn)
|
|||
// If the function type has a function annotation, we need to see if we can suggest an annotation
|
||||
if (normalizedFnTy)
|
||||
{
|
||||
const FunctionType* inferredFtv = get<FunctionType>(normalizedFnTy->functions.parts.front());
|
||||
LUAU_ASSERT(inferredFtv);
|
||||
|
||||
TypeFunctionReductionGuesser guesser{NotNull{&module->internalTypes}, builtinTypes, NotNull{&normalizer}};
|
||||
for (TypeId retTy : inferredFtv->retTypes)
|
||||
if (FFlag::LuauStuckTypeFunctionsStillDispatch)
|
||||
suggestAnnotations(fn, normalizedFnTy->functions.parts.front());
|
||||
else
|
||||
{
|
||||
if (get<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 (result.shouldRecommendAnnotation && !get<UnknownType>(result.guessedReturnType))
|
||||
reportError(
|
||||
ExplicitFunctionAnnotationRecommended{std::move(result.guessedFunctionAnnotations), result.guessedReturnType}, fn->location
|
||||
);
|
||||
if (get<TypeFunctionInstanceType>(follow(retTy)))
|
||||
{
|
||||
TypeFunctionReductionGuessResult result = guesser.guessTypeFunctionReductionForFunctionExpr(*fn, inferredFtv, retTy);
|
||||
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())
|
||||
continue;
|
||||
|
||||
std::optional<TypeOrPack> optSubLeaf = traverse(subTy, reasoning.subPath, builtinTypes);
|
||||
std::optional<TypeOrPack> optSuperLeaf = traverse(superTy, reasoning.superPath, builtinTypes);
|
||||
std::optional<TypeOrPack> optSubLeaf =
|
||||
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)
|
||||
ice->ice("Subtyping test returned a reasoning with an invalid path", location);
|
||||
|
@ -3473,7 +3497,7 @@ PropertyType TypeChecker2::hasIndexTypeFromType(
|
|||
{
|
||||
TypeId indexType = follow(tt->indexer->indexType);
|
||||
TypeId givenType = module->internalTypes.addType(SingletonType{StringSingleton{prop}});
|
||||
if (isSubtype(givenType, indexType, NotNull{module->getModuleScope().get()}, builtinTypes, simplifier, *ice))
|
||||
if (isSubtype(givenType, indexType, NotNull{module->getModuleScope().get()}, builtinTypes, simplifier, *ice, SolverMode::New))
|
||||
return {NormalizationResult::True, {tt->indexer->indexResultType}};
|
||||
}
|
||||
|
||||
|
@ -3550,7 +3574,47 @@ PropertyType TypeChecker2::hasIndexTypeFromType(
|
|||
return {NormalizationResult::False, {}};
|
||||
}
|
||||
|
||||
void TypeChecker2::suggestAnnotations(AstExprFunction* expr, TypeId ty)
|
||||
{
|
||||
const FunctionType* inferredFtv = get<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
|
||||
{
|
||||
|
@ -3630,5 +3694,42 @@ bool TypeChecker2::isErrorSuppressing(Location loc1, TypePackId tp1, Location lo
|
|||
return isErrorSuppressing(loc1, tp1) || isErrorSuppressing(loc2, tp2);
|
||||
}
|
||||
|
||||
bool TypeChecker2::reportNonviableOverloadErrors(
|
||||
std::vector<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
|
||||
|
|
|
@ -57,6 +57,7 @@ LUAU_FASTFLAG(LuauUserTypeFunctionAliases)
|
|||
LUAU_FASTFLAG(LuauUpdateGetMetatableTypeSignature)
|
||||
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
|
||||
LUAU_FASTFLAGVARIABLE(LuauOccursCheckForRefinement)
|
||||
LUAU_FASTFLAGVARIABLE(LuauStuckTypeFunctionsStillDispatch)
|
||||
LUAU_FASTFLAG(LuauRefineTablesWithReadType)
|
||||
LUAU_FASTFLAGVARIABLE(LuauEmptyStringInKeyOf)
|
||||
|
||||
|
@ -257,6 +258,10 @@ struct TypeFunctionReducer
|
|||
/// reducible later.
|
||||
Irreducible,
|
||||
|
||||
/// A type function that cannot be reduced any further because it has no valid reduction.
|
||||
/// eg add<number, string>
|
||||
Stuck,
|
||||
|
||||
/// Some type functions can operate on generic parameters
|
||||
Generic,
|
||||
|
||||
|
@ -313,8 +318,15 @@ struct TypeFunctionReducer
|
|||
if (seen.contains(t))
|
||||
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)
|
||||
{
|
||||
if (t == cyclicTy)
|
||||
|
@ -382,6 +394,35 @@ struct TypeFunctionReducer
|
|||
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>
|
||||
void handleTypeFunctionReduction(T subject, TypeFunctionReductionResult<T> reduction)
|
||||
{
|
||||
|
@ -402,6 +443,24 @@ struct TypeFunctionReducer
|
|||
if (FFlag::DebugLuauLogTypeFamilies)
|
||||
printf("%s is uninhabited\n", toString(subject, {true}).c_str());
|
||||
|
||||
if (FFlag::LuauStuckTypeFunctionsStillDispatch)
|
||||
{
|
||||
if (getState(subject) == TypeFunctionInstanceState::Unsolved)
|
||||
{
|
||||
if (reduction.reductionStatus == Reduction::Erroneous)
|
||||
setState(subject, TypeFunctionInstanceState::Stuck);
|
||||
else if (reduction.reductionStatus == Reduction::Irreducible)
|
||||
setState(subject, TypeFunctionInstanceState::Solved);
|
||||
else if (reduction.reductionStatus == Reduction::MaybeOk)
|
||||
{
|
||||
// We cannot make progress because something is unsolved, but we're also forcing.
|
||||
setState(subject, TypeFunctionInstanceState::Stuck);
|
||||
}
|
||||
else
|
||||
ctx.ice->ice("Unexpected TypeFunctionInstanceState");
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (std::is_same_v<T, TypeId>)
|
||||
result.errors.emplace_back(location, UninhabitedTypeFunction{subject});
|
||||
else if constexpr (std::is_same_v<T, TypePackId>)
|
||||
|
@ -409,6 +468,9 @@ struct TypeFunctionReducer
|
|||
}
|
||||
else if (reduction.reductionStatus == Reduction::MaybeOk && !force)
|
||||
{
|
||||
// We're not forcing and the reduction couldn't proceed, but it isn't obviously busted.
|
||||
// Report that this type blocks further reduction.
|
||||
|
||||
if (FFlag::DebugLuauLogTypeFamilies)
|
||||
printf(
|
||||
"%s is irreducible; blocked on %zu types, %zu packs\n",
|
||||
|
@ -423,6 +485,8 @@ struct TypeFunctionReducer
|
|||
for (TypePackId b : reduction.blockedPacks)
|
||||
result.blockedPacks.insert(b);
|
||||
}
|
||||
else
|
||||
LUAU_ASSERT(!"Unreachable");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -438,12 +502,33 @@ struct TypeFunctionReducer
|
|||
{
|
||||
SkipTestResult skip = testForSkippability(p);
|
||||
|
||||
if (skip == SkipTestResult::Stuck)
|
||||
{
|
||||
// SkipTestResult::Stuck cannot happen when this flag is unset.
|
||||
LUAU_ASSERT(FFlag::LuauStuckTypeFunctionsStillDispatch);
|
||||
if (FFlag::DebugLuauLogTypeFamilies)
|
||||
printf("%s is stuck!\n", toString(subject, {true}).c_str());
|
||||
|
||||
irreducible.insert(subject);
|
||||
setState(subject, TypeFunctionInstanceState::Stuck);
|
||||
|
||||
return false;
|
||||
}
|
||||
if (skip == SkipTestResult::Irreducible || (skip == SkipTestResult::Generic && !tfit->function->canReduceGenerics))
|
||||
{
|
||||
if (FFlag::DebugLuauLogTypeFamilies)
|
||||
printf("%s is irreducible due to a dependency on %s\n", toString(subject, {true}).c_str(), toString(p, {true}).c_str());
|
||||
{
|
||||
if (skip == SkipTestResult::Generic)
|
||||
printf("%s is solved due to a dependency on %s\n", toString(subject, {true}).c_str(), toString(p, {true}).c_str());
|
||||
else
|
||||
printf("%s is irreducible due to a dependency on %s\n", toString(subject, {true}).c_str(), toString(p, {true}).c_str());
|
||||
}
|
||||
|
||||
irreducible.insert(subject);
|
||||
|
||||
if (skip == SkipTestResult::Generic)
|
||||
setState(subject, TypeFunctionInstanceState::Solved);
|
||||
|
||||
return false;
|
||||
}
|
||||
else if (skip == SkipTestResult::Defer)
|
||||
|
@ -556,6 +641,9 @@ struct TypeFunctionReducer
|
|||
if (FFlag::DebugLuauLogTypeFamilies)
|
||||
printf("Irreducible due to irreducible/pending and a non-cyclic function\n");
|
||||
|
||||
if (tfit->state == TypeFunctionInstanceState::Stuck || tfit->state == TypeFunctionInstanceState::Solved)
|
||||
tryGuessing(subject);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -732,7 +820,14 @@ FunctionGraphReductionResult reduceTypeFunctions(TypePackId entrypoint, Location
|
|||
|
||||
bool isPending(TypeId ty, ConstraintSolver* solver)
|
||||
{
|
||||
return is<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>
|
||||
|
@ -3268,7 +3363,7 @@ bool searchPropsAndIndexer(
|
|||
indexType = follow(tblIndexer->indexResultType);
|
||||
}
|
||||
|
||||
if (isSubtype(ty, indexType, ctx->scope, ctx->builtins, ctx->simplifier, *ctx->ice))
|
||||
if (isSubtype(ty, indexType, ctx->scope, ctx->builtins, ctx->simplifier, *ctx->ice, SolverMode::New))
|
||||
{
|
||||
TypeId idxResultTy = follow(tblIndexer->indexResultType);
|
||||
|
||||
|
|
|
@ -209,7 +209,7 @@ TypeChecker::TypeChecker(const ScopePtr& globalScope, ModuleResolver* resolver,
|
|||
, builtinTypes(builtinTypes)
|
||||
, iceHandler(iceHandler)
|
||||
, unifierState(iceHandler)
|
||||
, normalizer(nullptr, builtinTypes, NotNull{&unifierState})
|
||||
, normalizer(nullptr, builtinTypes, NotNull{&unifierState}, SolverMode::Old)
|
||||
, reusableInstantiation(TxnLog::empty(), nullptr, builtinTypes, {}, nullptr)
|
||||
, nilType(builtinTypes->nilType)
|
||||
, numberType(builtinTypes->numberType)
|
||||
|
|
|
@ -453,6 +453,33 @@ std::pair<std::vector<TypeId>, std::optional<TypePackId>> flatten(TypePackId tp,
|
|||
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)
|
||||
{
|
||||
return isVariadic(tp, *TxnLog::empty());
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
|
||||
#include "Luau/TypePath.h"
|
||||
|
||||
#include "Luau/Anyification.h"
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypeArena.h"
|
||||
#include "Luau/TypeFwd.h"
|
||||
#include "Luau/TypePack.h"
|
||||
#include "Luau/TypeOrPack.h"
|
||||
|
@ -11,9 +14,9 @@
|
|||
#include <functional>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <type_traits>
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2);
|
||||
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping)
|
||||
|
||||
// Maximum number of steps to follow when traversing a path. May not always
|
||||
// equate to the number of components in a path, depending on the traversal
|
||||
|
@ -29,7 +32,6 @@ namespace TypePath
|
|||
Property::Property(std::string name)
|
||||
: name(std::move(name))
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauSolverV2);
|
||||
}
|
||||
|
||||
Property Property::read(std::string name)
|
||||
|
@ -52,6 +54,11 @@ bool Index::operator==(const Index& other) const
|
|||
return index == other.index;
|
||||
}
|
||||
|
||||
bool PackSlice::operator==(const PackSlice& other) const
|
||||
{
|
||||
return start_index == other.start_index;
|
||||
}
|
||||
|
||||
bool Reduction::operator==(const Reduction& other) const
|
||||
{
|
||||
return resultType == other.resultType;
|
||||
|
@ -129,6 +136,11 @@ size_t PathHash::operator()(const PackField& field) const
|
|||
return static_cast<size_t>(field);
|
||||
}
|
||||
|
||||
size_t PathHash::operator()(const PackSlice& slice) const
|
||||
{
|
||||
return slice.start_index;
|
||||
}
|
||||
|
||||
size_t PathHash::operator()(const Reduction& reduction) const
|
||||
{
|
||||
return std::hash<TypeId>()(reduction.resultType);
|
||||
|
@ -168,7 +180,6 @@ PathBuilder& PathBuilder::writeProp(std::string name)
|
|||
|
||||
PathBuilder& PathBuilder::prop(std::string name)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauSolverV2);
|
||||
components.push_back(Property{std::move(name)});
|
||||
return *this;
|
||||
}
|
||||
|
@ -239,6 +250,12 @@ PathBuilder& PathBuilder::tail()
|
|||
return *this;
|
||||
}
|
||||
|
||||
PathBuilder& PathBuilder::packSlice(size_t start_index)
|
||||
{
|
||||
components.emplace_back(PackSlice{start_index});
|
||||
return *this;
|
||||
}
|
||||
|
||||
} // namespace TypePath
|
||||
|
||||
namespace
|
||||
|
@ -246,19 +263,31 @@ namespace
|
|||
|
||||
struct TraversalState
|
||||
{
|
||||
TraversalState(TypeId root, NotNull<BuiltinTypes> builtinTypes)
|
||||
TraversalState(TypeId root, NotNull<BuiltinTypes> builtinTypes, const DenseHashMap<TypePackId, TypePackId>* mappedGenericPacks, TypeArena* arena)
|
||||
: current(root)
|
||||
, 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)
|
||||
, builtinTypes(builtinTypes)
|
||||
, mappedGenericPacks(mappedGenericPacks)
|
||||
, arena(arena)
|
||||
{
|
||||
}
|
||||
|
||||
TypeOrPack current;
|
||||
NotNull<BuiltinTypes> builtinTypes;
|
||||
// TODO: make these NotNull when LuauReturnMappedGenericPacksFromSubtyping is clipped
|
||||
const DenseHashMap<TypePackId, TypePackId>* mappedGenericPacks;
|
||||
TypeArena* arena;
|
||||
int steps = 0;
|
||||
|
||||
void updateCurrent(TypeId ty)
|
||||
|
@ -388,17 +417,43 @@ struct TraversalState
|
|||
{
|
||||
auto currentPack = get<TypePackId>(current);
|
||||
LUAU_ASSERT(currentPack);
|
||||
if (get<TypePack>(*currentPack))
|
||||
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping)
|
||||
{
|
||||
auto it = begin(*currentPack);
|
||||
|
||||
for (size_t i = 0; i < index.index && it != end(*currentPack); ++i)
|
||||
++it;
|
||||
|
||||
if (it != end(*currentPack))
|
||||
if (const auto tp = get<TypePack>(*currentPack))
|
||||
{
|
||||
updateCurrent(*it);
|
||||
return true;
|
||||
auto it = begin(*currentPack);
|
||||
|
||||
size_t i = 0;
|
||||
for (; i < index.index && it != end(*currentPack); ++i)
|
||||
++it;
|
||||
|
||||
if (it != end(*currentPack))
|
||||
{
|
||||
updateCurrent(*it);
|
||||
return true;
|
||||
}
|
||||
else if (tp->tail && mappedGenericPacks && mappedGenericPacks->contains(*tp->tail))
|
||||
{
|
||||
updateCurrent(*mappedGenericPacks->find(*tp->tail));
|
||||
LUAU_ASSERT(index.index >= i);
|
||||
return traverse(TypePath::Index{index.index - i, TypePath::Index::Variant::Pack});
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (get<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())
|
||||
{
|
||||
updateCurrent(*tail);
|
||||
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping && mappedGenericPacks && mappedGenericPacks->contains(*tail))
|
||||
updateCurrent(*mappedGenericPacks->find(*tail));
|
||||
else
|
||||
updateCurrent(*tail);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -531,6 +589,47 @@ struct TraversalState
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool traverse(const TypePath::PackSlice slice)
|
||||
{
|
||||
if (checkInvariants())
|
||||
return false;
|
||||
|
||||
// TODO: clip this check once LuauReturnMappedGenericPacksFromSubtyping is clipped
|
||||
// arena and mappedGenericPacks should be NonNull once that happens
|
||||
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping)
|
||||
LUAU_ASSERT(arena && mappedGenericPacks);
|
||||
else if (!arena || !mappedGenericPacks)
|
||||
return false;
|
||||
|
||||
const auto currentPack = get<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
|
||||
|
@ -614,6 +713,8 @@ std::string toString(const TypePath::Path& path, bool prefixDot)
|
|||
}
|
||||
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>)
|
||||
{
|
||||
// We need to rework the TypePath system to make subtyping failures easier to understand
|
||||
|
@ -829,6 +930,8 @@ std::string toStringHuman(const TypePath::Path& path)
|
|||
state = State::Normal;
|
||||
}
|
||||
}
|
||||
else if constexpr (std::is_same_v<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>)
|
||||
{
|
||||
if (state == State::Initial)
|
||||
|
@ -892,27 +995,57 @@ static bool traverse(TraversalState& state, const Path& path)
|
|||
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))
|
||||
return state.current;
|
||||
else
|
||||
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))
|
||||
return state.current;
|
||||
else
|
||||
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))
|
||||
{
|
||||
auto ty = get<TypeId>(state.current);
|
||||
|
@ -922,9 +1055,15 @@ std::optional<TypeId> traverseForType(TypeId root, const Path& path, NotNull<Bui
|
|||
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))
|
||||
{
|
||||
auto ty = get<TypeId>(state.current);
|
||||
|
@ -934,9 +1073,39 @@ std::optional<TypeId> traverseForType(TypePackId root, const Path& path, NotNull
|
|||
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))
|
||||
{
|
||||
auto ty = get<TypePackId>(state.current);
|
||||
|
@ -946,9 +1115,15 @@ std::optional<TypePackId> traverseForPack(TypeId root, const Path& path, NotNull
|
|||
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))
|
||||
{
|
||||
auto ty = get<TypePackId>(state.current);
|
||||
|
@ -958,4 +1133,61 @@ std::optional<TypePackId> traverseForPack(TypePackId root, const Path& path, Not
|
|||
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
|
||||
|
|
|
@ -381,9 +381,6 @@ Unifier::Unifier(NotNull<Normalizer> normalizer, NotNull<Scope> scope, const Loc
|
|||
, sharedState(*normalizer->sharedState)
|
||||
{
|
||||
LUAU_ASSERT(sharedState.iceHandler);
|
||||
|
||||
// Unifier is not usable when this flag is enabled! Please consider using Subtyping instead.
|
||||
LUAU_ASSERT(!FFlag::LuauSolverV2);
|
||||
}
|
||||
|
||||
void Unifier::tryUnify(TypeId subTy, TypeId superTy, bool isFunctionCall, bool isIntersection, const LiteralProperties* literalProperties)
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||
LUAU_FASTFLAG(LuauEnableWriteOnlyProperties)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization4)
|
||||
LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch)
|
||||
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
|
||||
LUAU_FASTFLAG(LuauRefineTablesWithReadType)
|
||||
|
||||
|
@ -105,6 +106,12 @@ static bool areCompatible(TypeId left, TypeId right)
|
|||
// returns `true` if `ty` is irressolvable and should be added to `incompleteSubtypes`.
|
||||
static bool isIrresolvable(TypeId ty)
|
||||
{
|
||||
if (FFlag::LuauStuckTypeFunctionsStillDispatch)
|
||||
{
|
||||
if (auto tfit = get<TypeFunctionInstanceType>(ty); tfit && tfit->state != TypeFunctionInstanceState::Unsolved)
|
||||
return false;
|
||||
}
|
||||
|
||||
return get<BlockedType>(ty) || get<TypeFunctionInstanceType>(ty);
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,6 @@ LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
|
|||
LUAU_FASTFLAGVARIABLE(LuauSolverV2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDeclareExternType)
|
||||
LUAU_FASTFLAGVARIABLE(LuauParseStringIndexer)
|
||||
LUAU_FASTFLAGVARIABLE(LuauStoreLocalAnnotationColonPositions)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCSTForReturnTypeFunctionTail)
|
||||
LUAU_FASTFLAGVARIABLE(LuauParseAttributeFixUninit)
|
||||
LUAU_DYNAMIC_FASTFLAGVARIABLE(DebugLuauReportReturnTypeVariadicWithTypeSuffix, false)
|
||||
|
@ -691,11 +690,7 @@ AstStat* Parser::parseFor()
|
|||
allocator.alloc<AstStatForIn>(Location(start, end), copy(vars), copy(values), body, hasIn, inLocation, hasDo, matchDo.location);
|
||||
if (options.storeCstData)
|
||||
{
|
||||
if (FFlag::LuauStoreLocalAnnotationColonPositions)
|
||||
cstNodeMap[node] =
|
||||
allocator.alloc<CstStatForIn>(extractAnnotationColonPositions(names), varsCommaPosition, copy(valuesCommaPositions));
|
||||
else
|
||||
cstNodeMap[node] = allocator.alloc<CstStatForIn>(AstArray<Position>{}, varsCommaPosition, copy(valuesCommaPositions));
|
||||
cstNodeMap[node] = allocator.alloc<CstStatForIn>(extractAnnotationColonPositions(names), varsCommaPosition, copy(valuesCommaPositions));
|
||||
}
|
||||
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);
|
||||
if (options.storeCstData)
|
||||
{
|
||||
if (FFlag::LuauStoreLocalAnnotationColonPositions)
|
||||
cstNodeMap[node] =
|
||||
allocator.alloc<CstStatLocal>(extractAnnotationColonPositions(names), varsCommaPositions, copy(valuesCommaPositions));
|
||||
else
|
||||
cstNodeMap[node] = allocator.alloc<CstStatLocal>(AstArray<Position>{}, varsCommaPositions, copy(valuesCommaPositions));
|
||||
cstNodeMap[node] = allocator.alloc<CstStatLocal>(extractAnnotationColonPositions(names), varsCommaPositions, copy(valuesCommaPositions));
|
||||
}
|
||||
|
||||
return node;
|
||||
|
@ -1608,17 +1599,11 @@ std::pair<AstExprFunction*, AstLocal*> Parser::parseFunctionBody(
|
|||
|
||||
if (lexer.current().type != ')')
|
||||
{
|
||||
if (FFlag::LuauStoreLocalAnnotationColonPositions)
|
||||
{
|
||||
if (cstNode)
|
||||
std::tie(vararg, varargLocation, varargAnnotation) =
|
||||
parseBindingList(args, /* allowDot3= */ true, &cstNode->argsCommaPositions, nullptr, &cstNode->varargAnnotationColonPosition);
|
||||
else
|
||||
std::tie(vararg, varargLocation, varargAnnotation) = parseBindingList(args, /* allowDot3= */ true);
|
||||
}
|
||||
else
|
||||
if (cstNode)
|
||||
std::tie(vararg, varargLocation, varargAnnotation) =
|
||||
parseBindingList(args, /* allowDot3= */ true, cstNode ? &cstNode->argsCommaPositions : nullptr);
|
||||
parseBindingList(args, /* allowDot3= */ true, &cstNode->argsCommaPositions, nullptr, &cstNode->varargAnnotationColonPosition);
|
||||
else
|
||||
std::tie(vararg, varargLocation, varargAnnotation) = parseBindingList(args, /* allowDot3= */ true);
|
||||
}
|
||||
|
||||
std::optional<Location> argLocation;
|
||||
|
@ -1676,8 +1661,7 @@ std::pair<AstExprFunction*, AstLocal*> Parser::parseFunctionBody(
|
|||
if (options.storeCstData)
|
||||
{
|
||||
cstNode->functionKeywordPosition = matchFunction.location.begin;
|
||||
if (FFlag::LuauStoreLocalAnnotationColonPositions)
|
||||
cstNode->argsAnnotationColonPositions = extractAnnotationColonPositions(args);
|
||||
cstNode->argsAnnotationColonPositions = extractAnnotationColonPositions(args);
|
||||
cstNodeMap[node] = cstNode;
|
||||
}
|
||||
|
||||
|
@ -1716,7 +1700,7 @@ Parser::Binding Parser::parseBinding()
|
|||
Position colonPosition = lexer.current().location.begin;
|
||||
AstType* annotation = parseOptionalType();
|
||||
|
||||
if (FFlag::LuauStoreLocalAnnotationColonPositions && options.storeCstData)
|
||||
if (options.storeCstData)
|
||||
return Binding(*name, annotation, colonPosition);
|
||||
else
|
||||
return Binding(*name, annotation);
|
||||
|
@ -1724,7 +1708,6 @@ Parser::Binding Parser::parseBinding()
|
|||
|
||||
AstArray<Position> Parser::extractAnnotationColonPositions(const TempVector<Binding>& bindings)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauStoreLocalAnnotationColonPositions);
|
||||
TempVector<Position> annotationColonPositions(scratchPosition);
|
||||
for (size_t i = 0; i < bindings.size(); ++i)
|
||||
annotationColonPositions.push_back(bindings[i].colonPosition);
|
||||
|
@ -1755,7 +1738,7 @@ std::tuple<bool, Location, AstTypePack*> Parser::parseBindingList(
|
|||
AstTypePack* tailAnnotation = nullptr;
|
||||
if (lexer.current().type == ':')
|
||||
{
|
||||
if (FFlag::LuauStoreLocalAnnotationColonPositions && varargAnnotationColonPosition)
|
||||
if (varargAnnotationColonPosition)
|
||||
*varargAnnotationColonPosition = lexer.current().location.begin;
|
||||
|
||||
nextLexeme();
|
||||
|
|
|
@ -20,7 +20,7 @@ struct ConstraintGeneratorFixture : Fixture
|
|||
ModulePtr mainModule;
|
||||
DcrLogger logger;
|
||||
UnifierSharedState sharedState{&ice};
|
||||
Normalizer normalizer{&arena, getBuiltins(), NotNull{&sharedState}};
|
||||
Normalizer normalizer{&arena, getBuiltins(), NotNull{&sharedState}, SolverMode::New};
|
||||
SimplifierPtr simplifier;
|
||||
TypeCheckLimits limits;
|
||||
TypeFunctionRuntime typeFunctionRuntime{NotNull{&ice}, NotNull{&limits}};
|
||||
|
|
|
@ -555,6 +555,18 @@ TypeId Fixture::requireExportedType(const ModuleName& moduleName, const std::str
|
|||
return it->second.type;
|
||||
}
|
||||
|
||||
std::string Fixture::canonicalize(TypeId ty)
|
||||
{
|
||||
if (!simplifier)
|
||||
simplifier = newSimplifier(NotNull{&simplifierArena}, getBuiltins());
|
||||
|
||||
auto res = eqSatSimplify(NotNull{simplifier.get()}, ty);
|
||||
if (res)
|
||||
return toString(res->result);
|
||||
else
|
||||
return toString(ty);
|
||||
}
|
||||
|
||||
std::string Fixture::decorateWithTypes(const std::string& code)
|
||||
{
|
||||
fileResolver.source[mainModuleName] = code;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "Luau/Config.h"
|
||||
#include "Luau/EqSatSimplification.h"
|
||||
#include "Luau/Error.h"
|
||||
#include "Luau/FileResolver.h"
|
||||
#include "Luau/Frontend.h"
|
||||
|
@ -145,6 +146,8 @@ struct Fixture
|
|||
TypeId requireTypeAlias(const std::string& name);
|
||||
TypeId requireExportedType(const ModuleName& moduleName, const std::string& name);
|
||||
|
||||
std::string canonicalize(TypeId ty);
|
||||
|
||||
// While most flags can be flipped inside the unit test, some code changes affect the state that is part of Fixture initialization
|
||||
// Most often those are changes related to builtin type definitions.
|
||||
// In that case, flag can be forced to 'true' using the example below:
|
||||
|
@ -187,6 +190,9 @@ protected:
|
|||
bool forAutocomplete = false;
|
||||
std::optional<Frontend> frontend;
|
||||
BuiltinTypes* builtinTypes = nullptr;
|
||||
|
||||
TypeArena simplifierArena;
|
||||
SimplifierPtr simplifier{nullptr, nullptr};
|
||||
};
|
||||
|
||||
struct BuiltinsFixture : Fixture
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include "Luau/Autocomplete.h"
|
||||
#include "Luau/BuiltinDefinitions.h"
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/FileResolver.h"
|
||||
#include "Luau/Frontend.h"
|
||||
#include "Luau/AutocompleteTypes.h"
|
||||
#include "Luau/ToString.h"
|
||||
|
@ -32,6 +33,8 @@ LUAU_FASTFLAG(LuauFragmentAcMemoryLeak)
|
|||
LUAU_FASTFLAG(LuauGlobalVariableModuleIsolation)
|
||||
LUAU_FASTFLAG(LuauFragmentAutocompleteIfRecommendations)
|
||||
LUAU_FASTFLAG(LuauPopulateRefinedTypesInFragmentFromOldSolver)
|
||||
LUAU_FASTFLAG(LuauSolverAgnosticStringification)
|
||||
LUAU_FASTFLAG(LuauFragmentRequiresCanBeResolvedToAModule)
|
||||
|
||||
static std::optional<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)
|
||||
{
|
||||
return FFlag::LuauSolverV2 ?frontend.moduleResolver : frontend.moduleResolverForAutocomplete;
|
||||
return FFlag::LuauSolverV2 ? frontend.moduleResolver : frontend.moduleResolverForAutocomplete;
|
||||
}
|
||||
|
||||
template<class BaseType>
|
||||
|
@ -157,6 +160,7 @@ struct FragmentAutocompleteFixtureImpl : BaseType
|
|||
)
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauSolverV2, true};
|
||||
this->getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::New);
|
||||
this->check(document, getOptions());
|
||||
|
||||
FragmentAutocompleteStatusResult result = autocompleteFragment(updated, cursorPos, fragmentEndPosition);
|
||||
|
@ -173,6 +177,7 @@ struct FragmentAutocompleteFixtureImpl : BaseType
|
|||
)
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauSolverV2, false};
|
||||
this->getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::Old);
|
||||
this->check(document, getOptions());
|
||||
|
||||
FragmentAutocompleteStatusResult result = autocompleteFragment(updated, cursorPos, fragmentEndPosition);
|
||||
|
@ -189,6 +194,7 @@ struct FragmentAutocompleteFixtureImpl : BaseType
|
|||
)
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauSolverV2, true};
|
||||
this->getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::New);
|
||||
this->check(document, getOptions());
|
||||
|
||||
FragmentAutocompleteStatusResult result = autocompleteFragment(updated, cursorPos, fragmentEndPosition);
|
||||
|
@ -196,6 +202,7 @@ struct FragmentAutocompleteFixtureImpl : BaseType
|
|||
assertions(result);
|
||||
|
||||
ScopedFastFlag _{FFlag::LuauSolverV2, false};
|
||||
this->getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::Old);
|
||||
this->check(document, getOptions());
|
||||
|
||||
result = autocompleteFragment(updated, cursorPos, fragmentEndPosition);
|
||||
|
@ -1317,7 +1324,7 @@ abc("bar")
|
|||
CHECK_EQ(Position{3, 1}, parent->location.end);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "respects_getFrontend().options")
|
||||
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "respects_frontend_options")
|
||||
{
|
||||
DOES_NOT_PASS_NEW_SOLVER_GUARD();
|
||||
|
||||
|
@ -1329,7 +1336,7 @@ t
|
|||
|
||||
FrontendOptions opts;
|
||||
opts.forAutocomplete = true;
|
||||
|
||||
getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
|
||||
getFrontend().check("game/A", opts);
|
||||
CHECK_NE(getFrontend().moduleResolverForAutocomplete.getModule("game/A"), nullptr);
|
||||
CHECK_EQ(getFrontend().moduleResolver.getModule("game/A"), nullptr);
|
||||
|
@ -1411,6 +1418,7 @@ TEST_SUITE_BEGIN("MixedModeTests");
|
|||
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "mixed_mode_basic_example_append")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauSolverV2, false};
|
||||
getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
|
||||
auto res = checkOldSolver(
|
||||
R"(
|
||||
local x = 4
|
||||
|
@ -1437,6 +1445,7 @@ local z = x + y
|
|||
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "mixed_mode_basic_example_inlined")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauSolverV2, false};
|
||||
getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
|
||||
auto res = checkOldSolver(
|
||||
R"(
|
||||
local x = 4
|
||||
|
@ -1461,6 +1470,7 @@ local y = 5
|
|||
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "mixed_mode_can_autocomplete_simple_property_access")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauSolverV2, false};
|
||||
getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
|
||||
auto res = checkOldSolver(
|
||||
R"(
|
||||
local tbl = { abc = 1234}
|
||||
|
@ -1521,6 +1531,7 @@ TEST_SUITE_BEGIN("FragmentAutocompleteTests");
|
|||
|
||||
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "multiple_fragment_autocomplete")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true};
|
||||
ToStringOptions opt;
|
||||
opt.exhaustive = true;
|
||||
opt.exhaustive = true;
|
||||
|
@ -1574,12 +1585,14 @@ return module)";
|
|||
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauSolverV2, false};
|
||||
checkAndExamine(source, "module", "{ }");
|
||||
fragmentACAndCheck(updated1, Position{1, 17}, "module", "{ }", "{ a: (%error-id%: unknown) -> () }");
|
||||
fragmentACAndCheck(updated2, Position{1, 18}, "module", "{ }", "{ ab: (%error-id%: unknown) -> () }");
|
||||
getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::Old);
|
||||
checkAndExamine(source, "module", "{| |}");
|
||||
fragmentACAndCheck(updated1, Position{1, 17}, "module", "{| |}", "{| a: (%error-id%: unknown) -> () |}");
|
||||
fragmentACAndCheck(updated2, Position{1, 18}, "module", "{| |}", "{| ab: (%error-id%: unknown) -> () |}");
|
||||
}
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauSolverV2, true};
|
||||
getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::New);
|
||||
checkAndExamine(source, "module", "{ }");
|
||||
// [TODO] CLI-140762 Fragment autocomplete still doesn't return correct result when LuauSolverV2 is on
|
||||
return;
|
||||
|
@ -2895,6 +2908,7 @@ end)
|
|||
|
||||
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "fragment_autocomplete_ensures_memory_isolation")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true};
|
||||
ToStringOptions opt;
|
||||
opt.exhaustive = true;
|
||||
opt.exhaustive = true;
|
||||
|
@ -2945,7 +2959,8 @@ return module)";
|
|||
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauSolverV2, false};
|
||||
checkAndExamine(source, "module", "{ }");
|
||||
getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::Old);
|
||||
checkAndExamine(source, "module", "{| |}");
|
||||
// [TODO] CLI-140762 we shouldn't mutate stale module in autocompleteFragment
|
||||
// early return since the following checking will fail, which it shouldn't!
|
||||
fragmentACAndCheck(updated1, Position{1, 17}, "module");
|
||||
|
@ -2954,6 +2969,7 @@ return module)";
|
|||
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauSolverV2, true};
|
||||
getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::New);
|
||||
checkAndExamine(source, "module", "{ }");
|
||||
// [TODO] CLI-140762 we shouldn't mutate stale module in autocompleteFragment
|
||||
// early return since the following checking will fail, which it shouldn't!
|
||||
|
@ -3865,6 +3881,38 @@ end
|
|||
});
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "inline_prop_read_on_requires_provides_results")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauFragmentRequiresCanBeResolvedToAModule, true};
|
||||
const std::string moduleA = R"(
|
||||
local mod = { prop1 = true}
|
||||
mod.prop2 = "a"
|
||||
function mod.foo(a: number)
|
||||
return a
|
||||
end
|
||||
return mod
|
||||
)";
|
||||
|
||||
const std::string mainModule = R"(
|
||||
|
||||
)";
|
||||
|
||||
fileResolver.source["MainModule"] = mainModule;
|
||||
fileResolver.source["MainModule/A"] = moduleA;
|
||||
getFrontend().check("MainModule/A", getOptions());
|
||||
getFrontend().check("MainModule", getOptions());
|
||||
|
||||
const std::string updatedMain = R"(
|
||||
require(script.A).
|
||||
)";
|
||||
|
||||
auto result = autocompleteFragment(updatedMain, Position{1, 18});
|
||||
CHECK(!result.result->acResults.entryMap.empty());
|
||||
CHECK(result.result->acResults.entryMap.count("prop1"));
|
||||
CHECK(result.result->acResults.entryMap.count("prop2"));
|
||||
CHECK(result.result->acResults.entryMap.count("foo"));
|
||||
}
|
||||
|
||||
// NOLINTEND(bugprone-unchecked-optional-access)
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -1359,7 +1359,7 @@ TEST_CASE_FIXTURE(FrontendFixture, "separate_caches_for_autocomplete")
|
|||
|
||||
FrontendOptions opts;
|
||||
opts.forAutocomplete = true;
|
||||
|
||||
getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
|
||||
getFrontend().check("game/A", opts);
|
||||
|
||||
CHECK(nullptr == getFrontend().moduleResolver.getModule("game/A"));
|
||||
|
@ -1731,6 +1731,7 @@ TEST_CASE_FIXTURE(FrontendFixture, "test_dependents_stored_on_node_as_graph_upda
|
|||
TEST_CASE_FIXTURE(FrontendFixture, "test_invalid_dependency_tracking_per_module_resolver")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, false};
|
||||
getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
|
||||
|
||||
fileResolver.source["game/Gui/Modules/A"] = "return {hello=5, world=true}";
|
||||
fileResolver.source["game/Gui/Modules/B"] = "return require(game:GetService('Gui').Modules.A)";
|
||||
|
|
|
@ -15,9 +15,8 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
|||
LUAU_FASTINT(LuauNormalizeIntersectionLimit)
|
||||
LUAU_FASTINT(LuauNormalizeUnionLimit)
|
||||
LUAU_FASTFLAG(LuauSimplifyOutOfLine2)
|
||||
LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2)
|
||||
LUAU_FASTFLAG(LuauNormalizationReorderFreeTypeIntersect)
|
||||
|
||||
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping)
|
||||
using namespace Luau;
|
||||
|
||||
namespace
|
||||
|
@ -34,7 +33,15 @@ struct IsSubtypeFixture : Fixture
|
|||
|
||||
SimplifierPtr simplifier = newSimplifier(NotNull{&module->internalTypes}, getBuiltins());
|
||||
|
||||
return ::Luau::isSubtype(a, b, NotNull{module->getModuleScope().get()}, getBuiltins(), NotNull{simplifier.get()}, ice);
|
||||
return ::Luau::isSubtype(
|
||||
a,
|
||||
b,
|
||||
NotNull{module->getModuleScope().get()},
|
||||
getBuiltins(),
|
||||
NotNull{simplifier.get()},
|
||||
ice,
|
||||
FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old
|
||||
);
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
@ -447,7 +454,7 @@ struct NormalizeFixture : Fixture
|
|||
TypeArena arena;
|
||||
InternalErrorReporter iceHandler;
|
||||
UnifierSharedState unifierState{&iceHandler};
|
||||
Normalizer normalizer{&arena, getBuiltins(), NotNull{&unifierState}};
|
||||
Normalizer normalizer{&arena, getBuiltins(), NotNull{&unifierState}, FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old};
|
||||
Scope globalScope{getBuiltins()->anyTypePack};
|
||||
|
||||
NormalizeFixture()
|
||||
|
@ -1216,7 +1223,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_union_type_pack_cycle")
|
|||
ScopedFastFlag sff[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauSimplifyOutOfLine2, true},
|
||||
{FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2, true},
|
||||
{FFlag::LuauReturnMappedGenericPacksFromSubtyping, true},
|
||||
};
|
||||
ScopedFastInt sfi{FInt::LuauTypeInferRecursionLimit, 0};
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization4)
|
||||
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
|
@ -67,7 +68,7 @@ struct SubtypeFixture : Fixture
|
|||
InternalErrorReporter iceReporter;
|
||||
UnifierSharedState sharedState{&ice};
|
||||
SimplifierPtr simplifier = newSimplifier(NotNull{&arena}, getBuiltins());
|
||||
Normalizer normalizer{&arena, getBuiltins(), NotNull{&sharedState}};
|
||||
Normalizer normalizer{&arena, getBuiltins(), NotNull{&sharedState}, FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old};
|
||||
TypeCheckLimits limits;
|
||||
TypeFunctionRuntime typeFunctionRuntime{NotNull{&iceReporter}, NotNull{&limits}};
|
||||
|
||||
|
@ -1386,6 +1387,8 @@ TEST_CASE_FIXTURE(SubtypeFixture, "<T>({ x: T }) -> T <: ({ method: <T>({ x: T }
|
|||
|
||||
TEST_CASE_FIXTURE(SubtypeFixture, "subtyping_reasonings_to_follow_a_reduced_type_function_instance")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauReturnMappedGenericPacksFromSubtyping, true};
|
||||
|
||||
TypeId longTy = arena.addType(UnionType{
|
||||
{getBuiltins()->booleanType,
|
||||
getBuiltins()->bufferType,
|
||||
|
@ -1408,8 +1411,10 @@ TEST_CASE_FIXTURE(SubtypeFixture, "subtyping_reasonings_to_follow_a_reduced_type
|
|||
if (reasoning.subPath.empty() && reasoning.superPath.empty())
|
||||
continue;
|
||||
|
||||
std::optional<TypeOrPack> optSubLeaf = traverse(subTy, reasoning.subPath, getBuiltins());
|
||||
std::optional<TypeOrPack> optSuperLeaf = traverse(superTy, reasoning.superPath, getBuiltins());
|
||||
std::optional<TypeOrPack> optSubLeaf =
|
||||
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)
|
||||
CHECK(false);
|
||||
|
|
|
@ -16,6 +16,7 @@ LUAU_FASTFLAG(LuauRecursiveTypeParameterRestriction)
|
|||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauFixEmptyTypePackStringification)
|
||||
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2)
|
||||
LUAU_FASTFLAG(LuauSolverAgnosticStringification)
|
||||
|
||||
TEST_SUITE_BEGIN("ToString");
|
||||
|
||||
|
@ -47,12 +48,22 @@ TEST_CASE_FIXTURE(Fixture, "bound_types")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "free_types")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true};
|
||||
DOES_NOT_PASS_NEW_SOLVER_GUARD();
|
||||
|
||||
CheckResult result = check("local a");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("a", toString(requireType("a")));
|
||||
CHECK_EQ("'a", toString(requireType("a")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "free_types_stringify_the_same_regardless_of_solver")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true};
|
||||
TypeArena a;
|
||||
TypeId t = a.addType(FreeType{getFrontend().globals.globalScope.get(), getFrontend().builtinTypes->neverType, getFrontend().builtinTypes->unknownType});
|
||||
|
||||
CHECK_EQ("'a", toString(t));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "cyclic_table")
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauStoreLocalAnnotationColonPositions)
|
||||
LUAU_FASTFLAG(LuauCSTForReturnTypeFunctionTail)
|
||||
|
||||
TEST_SUITE_BEGIN("TranspilerTests");
|
||||
|
@ -314,9 +313,6 @@ TEST_CASE("function_spaces_around_tokens")
|
|||
|
||||
TEST_CASE("function_with_types_spaces_around_tokens")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauStoreLocalAnnotationColonPositions, true},
|
||||
};
|
||||
std::string code = R"( function p<X, Y, Z...>(o: string, m: number, ...: any): string end )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
|
@ -1132,9 +1128,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_type_reference_spaces_around_tokens")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "transpile_type_annotation_spaces_around_tokens")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauStoreLocalAnnotationColonPositions, true},
|
||||
};
|
||||
std::string code = R"( local _: Type )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
|
@ -1153,9 +1146,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_type_annotation_spaces_around_tokens")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "transpile_for_loop_annotation_spaces_around_tokens")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauStoreLocalAnnotationColonPositions, true},
|
||||
};
|
||||
std::string code = R"( for i: number = 1, 10 do end )";
|
||||
CHECK_EQ(code, transpile(code, {}, true).code);
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ LUAU_FASTFLAG(LuauEagerGeneralization4)
|
|||
LUAU_FASTFLAG(LuauSimplifyOutOfLine2)
|
||||
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2)
|
||||
LUAU_FASTFLAG(LuauErrorSuppressionTypeFunctionArgs)
|
||||
LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch)
|
||||
LUAU_FASTFLAG(LuauEmptyStringInKeyOf)
|
||||
|
||||
struct TypeFunctionFixture : Fixture
|
||||
|
@ -743,6 +744,11 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_oss_crash_gh1161")
|
|||
if (!FFlag::LuauSolverV2)
|
||||
return;
|
||||
|
||||
ScopedFastFlag sff[] = {
|
||||
{FFlag::LuauEagerGeneralization4, true},
|
||||
{FFlag::LuauStuckTypeFunctionsStillDispatch, true}
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local EnumVariants = {
|
||||
["a"] = 1, ["b"] = 2, ["c"] = 3
|
||||
|
@ -758,9 +764,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_oss_crash_gh1161")
|
|||
fnB(result)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
CHECK(get<ConstraintSolvingIncompleteError>(result.errors[0]));
|
||||
CHECK(get<FunctionExitsWithoutReturning>(result.errors[1]));
|
||||
LUAU_CHECK_ERROR_COUNT(1, result);
|
||||
LUAU_CHECK_ERROR(result, FunctionExitsWithoutReturning);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(TypeFunctionFixture, "fuzzer_numeric_binop_doesnt_assert_on_generalizeFreeType")
|
||||
|
@ -1707,6 +1712,59 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "error_suppression_should_work_on_type_functi
|
|||
CHECK("Unknown type 'Colours'" == toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "fully_dispatch_type_function_that_is_parameterized_on_a_stuck_type_function")
|
||||
{
|
||||
// In this test, we infer
|
||||
//
|
||||
// (c + d) : add<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")
|
||||
{
|
||||
if (!FFlag::LuauSolverV2)
|
||||
|
@ -1745,7 +1803,7 @@ struct TFFixture
|
|||
InternalErrorReporter ice;
|
||||
UnifierSharedState unifierState{&ice};
|
||||
SimplifierPtr simplifier = EqSatSimplification::newSimplifier(arena, getBuiltins());
|
||||
Normalizer normalizer{arena, getBuiltins(), NotNull{&unifierState}};
|
||||
Normalizer normalizer{arena, getBuiltins(), NotNull{&unifierState}, SolverMode::New};
|
||||
TypeCheckLimits limits;
|
||||
TypeFunctionRuntime runtime{NotNull{&ice}, NotNull{&limits}};
|
||||
|
||||
|
@ -1794,4 +1852,54 @@ TEST_CASE_FIXTURE(TFFixture, "or<'a, 'b>")
|
|||
CHECK(res.reducedTypes.size() == 1);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(TFFixture, "a_type_function_parameterized_on_generics_is_solved")
|
||||
{
|
||||
TypeId a = arena->addType(GenericType{"A"});
|
||||
TypeId b = arena->addType(GenericType{"B"});
|
||||
|
||||
TypeId addTy = arena->addType(TypeFunctionInstanceType{builtinTypeFunctions.addFunc, {a, b}});
|
||||
|
||||
reduceTypeFunctions(addTy, Location{}, tfc);
|
||||
|
||||
const auto tfit = get<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();
|
||||
|
|
|
@ -14,6 +14,7 @@ LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2)
|
|||
LUAU_FASTFLAG(LuauUserTypeFunctionAliases)
|
||||
LUAU_FASTFLAG(LuauFollowTypeAlias)
|
||||
LUAU_FASTFLAG(LuauFollowExistingTypeFunction)
|
||||
LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch)
|
||||
LUAU_FASTFLAG(LuauTypeFunctionSerializeFollowMetatable)
|
||||
|
||||
TEST_SUITE_BEGIN("UserDefinedTypeFunctionTests");
|
||||
|
@ -2412,7 +2413,11 @@ TEST_CASE_FIXTURE(ExternTypeFixture, "type_alias_reduction_errors")
|
|||
if (!FFlag::LuauSolverV2)
|
||||
return;
|
||||
|
||||
ScopedFastFlag luauUserTypeFunctionAliases{FFlag::LuauUserTypeFunctionAliases, true};
|
||||
ScopedFastFlag sff[] = {
|
||||
{FFlag::LuauUserTypeFunctionAliases, true},
|
||||
{FFlag::LuauEagerGeneralization4, true},
|
||||
{FFlag::LuauStuckTypeFunctionsStillDispatch, true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type Test<T, U> = setmetatable<T, U>
|
||||
|
@ -2424,11 +2429,10 @@ end
|
|||
local function ok(idx: get<>): number return idx end
|
||||
)");
|
||||
|
||||
// TODO: type solving fails to complete in this test because of the blocked NameConstraint on the 'Test' alias
|
||||
LUAU_REQUIRE_ERROR_COUNT(5, result);
|
||||
LUAU_REQUIRE_ERROR_COUNT(4, result);
|
||||
CHECK(
|
||||
toString(result.errors[1]) ==
|
||||
R"('get' type function errored at runtime: [string "get"]:5: failed to reduce type function with: Type function instance setmetatable<number, string> is uninhabited)"
|
||||
R"(Type function instance get<> is uninhabited)"
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ LUAU_FASTFLAG(LuauWriteOnlyPropertyMangling)
|
|||
LUAU_FASTFLAG(LuauEnableWriteOnlyProperties)
|
||||
LUAU_FASTFLAG(LuauTableLiteralSubtypeCheckFunctionCalls)
|
||||
LUAU_FASTFLAG(LuauTypeCheckerStricterIndexCheck)
|
||||
LUAU_FASTFLAG(LuauSuppressErrorsForMultipleNonviableOverloads)
|
||||
|
||||
TEST_SUITE_BEGIN("BuiltinTests");
|
||||
|
||||
|
@ -1746,6 +1747,15 @@ TEST_CASE_FIXTURE(Fixture, "write_only_table_assertion")
|
|||
)"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_into_any")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauSuppressErrorsForMultipleNonviableOverloads, true};
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(check(R"(
|
||||
table.insert(1::any, 2::any)
|
||||
)"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "read_refinements_on_persistent_tables_known_property_identity")
|
||||
{
|
||||
// This will not result in a real refinement, as we refine `bnot`, a function, to be truthy
|
||||
|
|
|
@ -28,8 +28,11 @@ LUAU_FASTFLAG(LuauFormatUseLastPosition)
|
|||
LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType)
|
||||
LUAU_FASTFLAG(LuauSimplifyOutOfLine2)
|
||||
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2)
|
||||
LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch)
|
||||
LUAU_FASTFLAG(LuauAvoidGenericsLeakingDuringFunctionCallCheck)
|
||||
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
|
||||
LUAU_FASTFLAG(LuauSolverAgnosticStringification)
|
||||
LUAU_FASTFLAG(LuauSuppressErrorsForMultipleNonviableOverloads)
|
||||
|
||||
TEST_SUITE_BEGIN("TypeInferFunctions");
|
||||
|
||||
|
@ -259,6 +262,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "vararg_function_is_quantified")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "list_only_alternative_overloads_that_match_argument_count")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauSuppressErrorsForMultipleNonviableOverloads, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local multiply: ((number)->number) & ((number)->string) & ((number, number)->number)
|
||||
multiply("")
|
||||
|
@ -268,9 +273,9 @@ TEST_CASE_FIXTURE(Fixture, "list_only_alternative_overloads_that_match_argument_
|
|||
|
||||
if (FFlag::LuauSolverV2)
|
||||
{
|
||||
GenericError* g = get<GenericError>(result.errors[0]);
|
||||
REQUIRE(g);
|
||||
CHECK(g->message == "None of the overloads for function that accept 1 arguments are compatible.");
|
||||
MultipleNonviableOverloads* mno = get<MultipleNonviableOverloads>(result.errors[0]);
|
||||
REQUIRE_MESSAGE(mno, "Expected MultipleNonviableOverloads but got " << result.errors[0]);
|
||||
CHECK_EQ(mno->attemptedArgCount, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1388,6 +1393,7 @@ f(function(x) return x * 2 end)
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "infer_generic_function_function_argument")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true};
|
||||
// FIXME: CLI-116133 bidirectional type inference needs to push expected types in for higher-order function calls
|
||||
DOES_NOT_PASS_NEW_SOLVER_GUARD();
|
||||
|
||||
|
@ -1414,7 +1420,7 @@ local r = foldl(a, {s=0,c=0}, function(a, b) return {s = a.s + b, c = a.c + 1} e
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
REQUIRE_EQ("{ c: number, s: number }", toString(requireType("r")));
|
||||
REQUIRE_EQ("{| c: number, s: number |}", toString(requireType("r")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "infer_generic_function_function_argument_overloaded")
|
||||
|
@ -1690,6 +1696,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "function_decl_non_self_sealed_overwrite")
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "function_decl_non_self_sealed_overwrite_2")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauStuckTypeFunctionsStillDispatch, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local t: { f: ((x: number) -> number)? } = {}
|
||||
|
||||
|
@ -1706,9 +1714,7 @@ end
|
|||
|
||||
if (FFlag::LuauEagerGeneralization4 && FFlag::LuauSolverV2)
|
||||
{
|
||||
// FIXME CLI-151985
|
||||
LUAU_CHECK_ERROR_COUNT(3, result);
|
||||
LUAU_CHECK_ERROR(result, ConstraintSolvingIncompleteError);
|
||||
LUAU_CHECK_ERROR_COUNT(2, result);
|
||||
LUAU_CHECK_ERROR(result, WhereClauseNeeded); // x2
|
||||
}
|
||||
else if (FFlag::LuauSolverV2)
|
||||
|
@ -1776,6 +1782,8 @@ TEST_CASE_FIXTURE(Fixture, "inferred_higher_order_functions_are_quantified_at_th
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "function_decl_non_self_unsealed_overwrite")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauStuckTypeFunctionsStillDispatch, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local t = { f = nil :: ((x: number) -> number)? }
|
||||
|
||||
|
@ -1791,9 +1799,7 @@ end
|
|||
|
||||
if (FFlag::LuauEagerGeneralization4 && FFlag::LuauSolverV2)
|
||||
{
|
||||
// FIXME CLI-151985
|
||||
LUAU_CHECK_ERROR_COUNT(2, result);
|
||||
LUAU_CHECK_ERROR(result, ConstraintSolvingIncompleteError);
|
||||
LUAU_CHECK_ERROR_COUNT(1, result);
|
||||
LUAU_CHECK_ERROR(result, WhereClauseNeeded);
|
||||
}
|
||||
else if (FFlag::LuauSolverV2)
|
||||
|
@ -2669,10 +2675,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tf_suggest_arg_type_2")
|
|||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
auto err = get<NotATable>(result.errors.back());
|
||||
REQUIRE(err);
|
||||
CHECK("a" == toString(err->ty));
|
||||
LUAU_REQUIRE_ERROR(result, NotATable);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "local_function_fwd_decl_doesnt_crash")
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/TypeInfer.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/Scope.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "Fixture.h"
|
||||
|
||||
|
@ -13,12 +10,13 @@
|
|||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions)
|
||||
LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2)
|
||||
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping)
|
||||
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2)
|
||||
LUAU_FASTFLAG(LuauIntersectNotNil)
|
||||
LUAU_FASTFLAG(LuauSubtypingCheckFunctionGenericCounts)
|
||||
LUAU_FASTFLAG(LuauReportSubtypingErrors)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization4)
|
||||
LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch)
|
||||
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
|
||||
|
||||
using namespace Luau;
|
||||
|
@ -1006,7 +1004,7 @@ local TheDispatcher: Dispatcher = {
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "generic_argument_count_too_few")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2, true};
|
||||
ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function test(a: number)
|
||||
|
@ -1024,8 +1022,7 @@ wrapper(test)
|
|||
{
|
||||
const CountMismatch* cm = get<CountMismatch>(result.errors[0]);
|
||||
REQUIRE_MESSAGE(cm, "Expected CountMismatch but got " << result.errors[0]);
|
||||
// TODO: CLI-152070 fix to expect 2
|
||||
CHECK_EQ(cm->expected, 1);
|
||||
CHECK_EQ(cm->expected, 2);
|
||||
CHECK_EQ(cm->actual, 1);
|
||||
CHECK_EQ(cm->context, CountMismatch::Arg);
|
||||
}
|
||||
|
@ -1035,7 +1032,7 @@ wrapper(test)
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "generic_argument_count_too_many")
|
||||
{
|
||||
DOES_NOT_PASS_NEW_SOLVER_GUARD();
|
||||
ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function test2(a: number, b: string)
|
||||
|
@ -1049,7 +1046,16 @@ wrapper(test2, 1, "", 3)
|
|||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Argument count mismatch. Function 'wrapper' expects 3 arguments, but 4 are specified)");
|
||||
if (FFlag::LuauSolverV2)
|
||||
{
|
||||
const CountMismatch* cm = get<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")
|
||||
|
@ -1070,6 +1076,8 @@ wrapper(test2, 1, "")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "generic_argument_pack_type_inferred_from_return")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function test2(a: number)
|
||||
return "hello"
|
||||
|
@ -1081,21 +1089,24 @@ end
|
|||
wrapper(test2, 1)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
if (FFlag::LuauSolverV2)
|
||||
{
|
||||
// TODO: CLI-152070 should expect a TypeMismatch, rather than not erroring
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
const TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
||||
REQUIRE_MESSAGE(tm, "Expected TypeMismatch but got " << result.errors[0]);
|
||||
CHECK_EQ(toString(tm->wantedType), "string");
|
||||
CHECK_EQ(toString(tm->givenType), "number");
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Type 'number' could not be converted into 'string')");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "generic_argument_pack_type_inferred_from_return_no_error")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2, true};
|
||||
ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function test2(a: number)
|
||||
|
@ -1111,6 +1122,64 @@ wrapper(test2, "hello")
|
|||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "nested_generic_argument_type_packs")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function test2(a: number)
|
||||
return 3
|
||||
end
|
||||
|
||||
function foo<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")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
|
@ -1446,6 +1515,9 @@ TEST_CASE_FIXTURE(Fixture, "infer_generic_function_function_argument_overloaded"
|
|||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
// Important FIXME CLI-158432: This test exposes some problems with overload
|
||||
// selection and generic type substitution when
|
||||
// FFlag::LuauStuckTypeFunctionsStillDispatch is set.
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "do_not_infer_generic_functions")
|
||||
{
|
||||
ScopedFastFlag _[] = {
|
||||
|
@ -1461,15 +1533,20 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "do_not_infer_generic_functions")
|
|||
local function sum<a>(x: a, y: a, f: (a, a) -> a) return f(x, y) end
|
||||
|
||||
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
|
||||
|
||||
local b = sumrec(sum) -- ok
|
||||
local c = sumrec(function(x, y, f) return f(x, y) end) -- type binders are not inferred
|
||||
)");
|
||||
|
||||
CHECK_EQ("<a>(a, a, (a, a) -> a) -> a", toString(requireType("sum")));
|
||||
CHECK_EQ("<a>(a, a, (a, a) -> a) -> a", toString(requireTypeAtPosition({7, 29})));
|
||||
if (FFlag::LuauStuckTypeFunctionsStillDispatch) // FIXME CLI-158432
|
||||
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
|
||||
{
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ using namespace Luau;
|
|||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2)
|
||||
LUAU_FASTFLAG(LuauRefineTablesWithReadType)
|
||||
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping)
|
||||
|
||||
TEST_SUITE_BEGIN("IntersectionTypes");
|
||||
|
||||
|
@ -1149,6 +1150,8 @@ could not be converted into
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_4")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f<a...>()
|
||||
function g(x : ((a...) -> ()) & ((number,a...) -> number))
|
||||
|
@ -1162,15 +1165,17 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_4")
|
|||
|
||||
if (FFlag::LuauSolverV2)
|
||||
{
|
||||
const std::string expected = "Type\n\t"
|
||||
"'((a...) -> ()) & ((number, a...) -> number)'"
|
||||
"\ncould not be converted into\n\t"
|
||||
"'((a...) -> ()) & ((number, a...) -> number)'; \n"
|
||||
"this is because \n\t"
|
||||
" * in the 1st component of the intersection, the function returns is `()` in the former type and `number` in "
|
||||
"the latter type, and `()` is not a subtype of `number`\n\t"
|
||||
" * in the 2nd component of the intersection, the function takes a tail of `a...` and in the 1st component of "
|
||||
"the intersection, the function takes a tail of `a...`, and `a...` is not a supertype of `a...`";
|
||||
// TODO: CLI-159120 - this error message is bogus
|
||||
const std::string expected =
|
||||
"Type\n\t"
|
||||
"'((a...) -> ()) & ((number, a...) -> number)'"
|
||||
"\ncould not be converted into\n\t"
|
||||
"'((a...) -> ()) & ((number, a...) -> number)'; \n"
|
||||
"this is because \n\t"
|
||||
" * in the 1st component of the intersection, the function returns is `()` in the former type and `number` in "
|
||||
"the latter type, and `()` is not a subtype of `number`\n\t"
|
||||
" * in the 2nd component of the intersection, the function takes a tail of `number, a...` and in the 1st component of "
|
||||
"the intersection, the function takes a tail of `number, a...`, and `number, a...` is not a supertype of `number, a...`";
|
||||
CHECK(expected == toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
|
|
|
@ -14,7 +14,7 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
|||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization4)
|
||||
LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2)
|
||||
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
|
@ -824,7 +824,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cycles_dont_make_everything_any")
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "cross_module_function_mutation")
|
||||
{
|
||||
ScopedFastFlag _[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2, true}};
|
||||
ScopedFastFlag _[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauReturnMappedGenericPacksFromSubtyping, true}};
|
||||
|
||||
fileResolver.source["game/A"] = R"(
|
||||
function test2(a: number, b: string)
|
||||
|
|
|
@ -19,6 +19,7 @@ LUAU_FASTINT(LuauTypeInferIterationLimit)
|
|||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||
LUAU_FASTINT(LuauTypeInferTypePackLoopLimit)
|
||||
LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops)
|
||||
LUAU_FASTFLAG(LuauSolverAgnosticStringification)
|
||||
|
||||
TEST_SUITE_BEGIN("ProvisionalTests");
|
||||
|
||||
|
@ -534,6 +535,7 @@ TEST_CASE_FIXTURE(Fixture, "dcr_can_partially_dispatch_a_constraint")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "free_options_cannot_be_unified_together")
|
||||
{
|
||||
ScopedFastFlag sff_stringification{FFlag::LuauSolverAgnosticStringification, true};
|
||||
ScopedFastFlag sff{FFlag::LuauSolverV2, false};
|
||||
|
||||
TypeArena arena;
|
||||
|
@ -549,7 +551,7 @@ TEST_CASE_FIXTURE(Fixture, "free_options_cannot_be_unified_together")
|
|||
|
||||
InternalErrorReporter iceHandler;
|
||||
UnifierSharedState sharedState{&iceHandler};
|
||||
Normalizer normalizer{&arena, getBuiltins(), NotNull{&sharedState}};
|
||||
Normalizer normalizer{&arena, getBuiltins(), NotNull{&sharedState}, SolverMode::Old};
|
||||
Unifier u{NotNull{&normalizer}, NotNull{scope.get()}, Location{}, Variance::Covariant};
|
||||
|
||||
u.tryUnify(option1, option2);
|
||||
|
@ -559,10 +561,10 @@ TEST_CASE_FIXTURE(Fixture, "free_options_cannot_be_unified_together")
|
|||
u.log.commit();
|
||||
|
||||
ToStringOptions opts;
|
||||
CHECK("a?" == toString(option1, opts));
|
||||
CHECK("'a?" == toString(option1, opts));
|
||||
|
||||
// CHECK("a?" == toString(option2, opts)); // This should hold, but does not.
|
||||
CHECK("b?" == toString(option2, opts)); // This should not hold.
|
||||
CHECK("'b?" == toString(option2, opts)); // This should not hold.
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop_with_zero_iterators")
|
||||
|
@ -677,7 +679,7 @@ struct IsSubtypeFixture : Fixture
|
|||
if (!module->hasModuleScope())
|
||||
FAIL("isSubtype: module scope data is not available");
|
||||
|
||||
return ::Luau::isSubtype(a, b, NotNull{module->getModuleScope().get()}, getBuiltins(), NotNull{simplifier.get()}, ice);
|
||||
return ::Luau::isSubtype(a, b, NotNull{module->getModuleScope().get()}, getBuiltins(), NotNull{simplifier.get()}, ice, SolverMode::New);
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
@ -962,6 +964,7 @@ TEST_CASE_FIXTURE(Fixture, "floating_generics_should_not_be_allowed")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "free_options_can_be_unified_together")
|
||||
{
|
||||
ScopedFastFlag sff_stringification{FFlag::LuauSolverAgnosticStringification, true};
|
||||
ScopedFastFlag sff{FFlag::LuauSolverV2, false};
|
||||
|
||||
TypeArena arena;
|
||||
|
@ -977,7 +980,7 @@ TEST_CASE_FIXTURE(Fixture, "free_options_can_be_unified_together")
|
|||
|
||||
InternalErrorReporter iceHandler;
|
||||
UnifierSharedState sharedState{&iceHandler};
|
||||
Normalizer normalizer{&arena, getBuiltins(), NotNull{&sharedState}};
|
||||
Normalizer normalizer{&arena, getBuiltins(), NotNull{&sharedState}, SolverMode::Old};
|
||||
Unifier u{NotNull{&normalizer}, NotNull{scope.get()}, Location{}, Variance::Covariant};
|
||||
|
||||
u.tryUnify(option1, option2);
|
||||
|
@ -987,8 +990,8 @@ TEST_CASE_FIXTURE(Fixture, "free_options_can_be_unified_together")
|
|||
u.log.commit();
|
||||
|
||||
ToStringOptions opts;
|
||||
CHECK("a?" == toString(option1, opts));
|
||||
CHECK("b?" == toString(option2, opts)); // should be `a?`.
|
||||
CHECK("'a?" == toString(option1, opts));
|
||||
CHECK("'b?" == toString(option2, opts)); // should be `a?`.
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "unify_more_complex_unions_that_include_nil")
|
||||
|
@ -1279,7 +1282,7 @@ TEST_CASE_FIXTURE(Fixture, "table_containing_non_final_type_is_erroneously_cache
|
|||
TypeArena arena;
|
||||
Scope globalScope(getBuiltins()->anyTypePack);
|
||||
UnifierSharedState sharedState{&ice};
|
||||
Normalizer normalizer{&arena, getBuiltins(), NotNull{&sharedState}};
|
||||
Normalizer normalizer{&arena, getBuiltins(), NotNull{&sharedState}, SolverMode::New};
|
||||
|
||||
TypeId tableTy = arena.addType(TableType{});
|
||||
TableType* table = getMutable<TableType>(tableTy);
|
||||
|
|
|
@ -15,8 +15,10 @@ LUAU_FASTFLAG(LuauFunctionCallsAreNotNilable)
|
|||
LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions)
|
||||
LUAU_FASTFLAG(LuauSimplificationTableExternType)
|
||||
LUAU_FASTFLAG(LuauBetterCannotCallFunctionPrimitive)
|
||||
LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch)
|
||||
LUAU_FASTFLAG(LuauTypeCheckerStricterIndexCheck)
|
||||
LUAU_FASTFLAG(LuauNormalizationIntersectTablesPreservesExternTypes)
|
||||
LUAU_FASTFLAG(LuauNormalizationReorderFreeTypeIntersect)
|
||||
LUAU_FASTFLAG(LuauAvoidDoubleNegation)
|
||||
LUAU_FASTFLAG(LuauRefineTablesWithReadType)
|
||||
|
||||
|
@ -140,6 +142,7 @@ struct RefinementExternTypeFixture : BuiltinsFixture
|
|||
|
||||
for (const auto& [name, ty] : getFrontend().globals.globalScope->exportedTypeBindings)
|
||||
persist(ty.type);
|
||||
getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
|
||||
|
||||
freeze(getFrontend().globals.globalTypes);
|
||||
}
|
||||
|
@ -653,6 +656,12 @@ TEST_CASE_FIXTURE(Fixture, "lvalue_is_not_nil")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "free_type_is_equal_to_an_lvalue")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{FFlag::LuauEagerGeneralization4, true},
|
||||
{FFlag::LuauStuckTypeFunctionsStillDispatch, true},
|
||||
{FFlag::LuauNormalizationReorderFreeTypeIntersect, true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(a, b: string?)
|
||||
if a == b then
|
||||
|
@ -664,11 +673,20 @@ TEST_CASE_FIXTURE(Fixture, "free_type_is_equal_to_an_lvalue")
|
|||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::LuauSolverV2)
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "unknown"); // a == b
|
||||
else
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "a"); // a == b
|
||||
{
|
||||
CHECK(toString(requireTypeAtPosition({3, 33})) == "unknown"); // a == b
|
||||
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "string?"); // a == b
|
||||
// FIXME: This type either comes out as string? or (string?) & unknown
|
||||
// depending on which tests are run and in which order. I'm not sure
|
||||
// where the nondeterminism is coming from.
|
||||
// CHECK(toString(requireTypeAtPosition({3, 36})) == "string?"); // a == b
|
||||
CHECK(canonicalize(requireTypeAtPosition({3, 36})) == "string?"); // a == b
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "a"); // a == b
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "string?"); // a == b
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "unknown_lvalue_is_not_synonymous_with_other_on_not_equal")
|
||||
|
@ -1424,7 +1442,7 @@ TEST_CASE_FIXTURE(RefinementExternTypeFixture, "typeguard_cast_free_table_to_vec
|
|||
{
|
||||
// CLI-115286 - Refining via type(x) == 'vector' does not work in the new solver
|
||||
DOES_NOT_PASS_NEW_SOLVER_GUARD();
|
||||
|
||||
getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
|
||||
CheckResult result = check(R"(
|
||||
local function f(vec)
|
||||
local X, Y, Z = vec.X, vec.Y, vec.Z
|
||||
|
@ -2438,7 +2456,7 @@ end)
|
|||
)"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "refinements_table_intersection_limits" * doctest::timeout(0.5))
|
||||
TEST_CASE_FIXTURE(Fixture, "refinements_table_intersection_limits" * doctest::timeout(1.0))
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
|
|
|
@ -33,9 +33,11 @@ LUAU_FASTINT(LuauPrimitiveInferenceInTableLimit)
|
|||
LUAU_FASTFLAG(LuauAutocompleteMissingFollows)
|
||||
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
|
||||
LUAU_FASTFLAG(LuauRelateTablesAreNeverDisjoint)
|
||||
LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch)
|
||||
LUAU_FASTFLAG(LuauTableLiteralSubtypeCheckFunctionCalls)
|
||||
LUAU_FASTFLAG(LuauAvoidGenericsLeakingDuringFunctionCallCheck)
|
||||
LUAU_FASTFLAG(LuauRefineTablesWithReadType)
|
||||
LUAU_FASTFLAG(LuauSolverAgnosticStringification)
|
||||
|
||||
TEST_SUITE_BEGIN("TableTests");
|
||||
|
||||
|
@ -1721,6 +1723,7 @@ TEST_CASE_FIXTURE(Fixture, "right_table_missing_key")
|
|||
// Could be flaky if the fix has regressed.
|
||||
TEST_CASE_FIXTURE(Fixture, "right_table_missing_key2")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true};
|
||||
// CLI-114792 We don't report MissingProperties
|
||||
DOES_NOT_PASS_NEW_SOLVER_GUARD();
|
||||
|
||||
|
@ -1738,8 +1741,8 @@ TEST_CASE_FIXTURE(Fixture, "right_table_missing_key2")
|
|||
REQUIRE_EQ(1, mp->properties.size());
|
||||
CHECK_EQ(mp->properties[0], "a");
|
||||
|
||||
CHECK_EQ("{| [string]: string, a: string |}", toString(mp->superType));
|
||||
CHECK_EQ("{| |}", toString(mp->subType));
|
||||
CHECK_EQ("{ [string]: string, a: string }", toString(mp->superType));
|
||||
CHECK_EQ("{ }", toString(mp->subType));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "casting_unsealed_tables_with_props_into_table_with_indexer")
|
||||
|
@ -2421,6 +2424,8 @@ local t: { a: {Foo}, b: number } = {
|
|||
// since mutating properties means table properties should be invariant.
|
||||
TEST_CASE_FIXTURE(Fixture, "invariant_table_properties_means_instantiating_tables_in_assignment_is_unsound")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauStuckTypeFunctionsStillDispatch, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
local t = {}
|
||||
|
@ -2434,10 +2439,8 @@ TEST_CASE_FIXTURE(Fixture, "invariant_table_properties_means_instantiating_table
|
|||
|
||||
if (FFlag::LuauEagerGeneralization4 && FFlag::LuauSolverV2)
|
||||
{
|
||||
// FIXME CLI-151985
|
||||
LUAU_CHECK_ERROR_COUNT(2, result);
|
||||
LUAU_CHECK_ERROR_COUNT(1, result);
|
||||
LUAU_CHECK_ERROR(result, ExplicitFunctionAnnotationRecommended);
|
||||
LUAU_CHECK_ERROR(result, ConstraintSolvingIncompleteError);
|
||||
}
|
||||
else if (FFlag::LuauSolverV2)
|
||||
{
|
||||
|
@ -2768,6 +2771,7 @@ TEST_CASE_FIXTURE(Fixture, "explicitly_typed_table_with_indexer")
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "recursive_metatable_type_call")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true};
|
||||
// CLI-114782
|
||||
DOES_NOT_PASS_NEW_SOLVER_GUARD();
|
||||
|
||||
|
@ -2779,7 +2783,7 @@ b()
|
|||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Cannot call a value of type t1 where t1 = { @metatable { __call: t1 }, { } })");
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Cannot call a value of type t1 where t1 = { @metatable {| __call: t1 |}, {| |} })");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "table_subtyping_shouldn't_add_optional_properties_to_sealed_tables")
|
||||
|
|
|
@ -614,7 +614,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tc_after_error_recovery_no_replacement_name_
|
|||
{
|
||||
{
|
||||
DOES_NOT_PASS_NEW_SOLVER_GUARD();
|
||||
|
||||
getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
local t = { x = 10, y = 20 }
|
||||
|
@ -625,6 +625,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tc_after_error_recovery_no_replacement_name_
|
|||
}
|
||||
|
||||
{
|
||||
getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
export type = number
|
||||
|
@ -636,7 +637,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tc_after_error_recovery_no_replacement_name_
|
|||
|
||||
{
|
||||
DOES_NOT_PASS_NEW_SOLVER_GUARD();
|
||||
|
||||
getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
function string.() end
|
||||
|
@ -646,6 +647,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tc_after_error_recovery_no_replacement_name_
|
|||
}
|
||||
|
||||
{
|
||||
getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
local function () end
|
||||
|
@ -656,6 +658,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tc_after_error_recovery_no_replacement_name_
|
|||
}
|
||||
|
||||
{
|
||||
getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
local dm = {}
|
||||
|
@ -1153,7 +1156,7 @@ TEST_CASE_FIXTURE(Fixture, "cli_50041_committing_txnlog_in_apollo_client_error")
|
|||
"\ncaused by:\n"
|
||||
" Property 'getStoreFieldName' is not compatible.\n"
|
||||
"Type\n\t"
|
||||
"'(Policies, FieldSpecifier & {| from: number? |}) -> (a, b...)'"
|
||||
"'(Policies, FieldSpecifier & { from: number? }) -> ('a, b...)'"
|
||||
"\ncould not be converted into\n\t"
|
||||
"'(Policies, FieldSpecifier) -> string'"
|
||||
"\ncaused by:\n"
|
||||
|
@ -1161,10 +1164,10 @@ TEST_CASE_FIXTURE(Fixture, "cli_50041_committing_txnlog_in_apollo_client_error")
|
|||
"Type\n\t"
|
||||
"'FieldSpecifier'"
|
||||
"\ncould not be converted into\n\t"
|
||||
"'FieldSpecifier & {| from: number? |}'"
|
||||
"'FieldSpecifier & { from: number? }'"
|
||||
"\ncaused by:\n"
|
||||
" Not all intersection parts are compatible.\n"
|
||||
"Table type 'FieldSpecifier' not compatible with type '{| from: number? |}' because the former has extra field 'fieldName'";
|
||||
"Table type 'FieldSpecifier' not compatible with type '{ from: number? }' because the former has extra field 'fieldName'";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
|
@ -2381,6 +2384,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "is_safe_integer_example")
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "type_remover_heap_use_after_free")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauEagerGeneralization4, true};
|
||||
|
||||
LUAU_REQUIRE_ERRORS(check(R"(
|
||||
_ = if l0.n0.n0 then {n4(...,setmetatable(setmetatable(_),_)),_ == _,} elseif _.ceil._ then _ elseif _ then not _
|
||||
)"));
|
||||
|
|
|
@ -23,7 +23,7 @@ struct TryUnifyFixture : Fixture
|
|||
ScopePtr globalScope{new Scope{arena.addTypePack({TypeId{}})}};
|
||||
InternalErrorReporter iceHandler;
|
||||
UnifierSharedState unifierState{&iceHandler};
|
||||
Normalizer normalizer{&arena, getBuiltins(), NotNull{&unifierState}};
|
||||
Normalizer normalizer{&arena, getBuiltins(), NotNull{&unifierState}, SolverMode::Old};
|
||||
Unifier state{NotNull{&normalizer}, NotNull{globalScope.get()}, Location{}, Variance::Covariant};
|
||||
};
|
||||
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2);
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization4);
|
||||
LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch);
|
||||
|
||||
TEST_SUITE_BEGIN("TypeInferUnknownNever");
|
||||
|
||||
|
@ -346,6 +348,11 @@ TEST_CASE_FIXTURE(Fixture, "dont_unify_operands_if_one_of_the_operand_is_never_i
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "math_operators_and_never")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{FFlag::LuauEagerGeneralization4, true},
|
||||
{FFlag::LuauStuckTypeFunctionsStillDispatch, true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function mul(x: nil, y)
|
||||
return x ~= nil and x * y -- infers boolean | never, which is normalized into boolean
|
||||
|
@ -359,7 +366,7 @@ TEST_CASE_FIXTURE(Fixture, "math_operators_and_never")
|
|||
|
||||
// CLI-114134 Egraph-based simplification.
|
||||
// CLI-116549 x ~= nil : false when x : nil
|
||||
CHECK("<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
|
||||
{
|
||||
|
|
|
@ -21,11 +21,15 @@ LUAU_DYNAMIC_FASTINT(LuauTypePathMaximumTraverseSteps);
|
|||
struct TypePathFixture : Fixture
|
||||
{
|
||||
ScopedFastFlag sff1{FFlag::LuauSolverV2, true};
|
||||
TypeArena arena;
|
||||
const DenseHashMap<TypePackId, TypePackId> emptyMap{nullptr};
|
||||
};
|
||||
|
||||
struct TypePathBuiltinsFixture : BuiltinsFixture
|
||||
{
|
||||
ScopedFastFlag sff1{FFlag::LuauSolverV2, true};
|
||||
TypeArena arena;
|
||||
const DenseHashMap<TypePackId, TypePackId> emptyMap{nullptr};
|
||||
};
|
||||
|
||||
TEST_SUITE_BEGIN("TypePathManipulation");
|
||||
|
@ -108,7 +112,7 @@ TEST_SUITE_BEGIN("TypePathTraversal");
|
|||
|
||||
TEST_CASE_FIXTURE(TypePathFixture, "empty_traversal")
|
||||
{
|
||||
CHECK(traverseForType(getBuiltins()->numberType, kEmpty, getBuiltins()) == getBuiltins()->numberType);
|
||||
CHECK(traverseForType(getBuiltins()->numberType, kEmpty, getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}) == getBuiltins()->numberType);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(TypePathFixture, "table_property")
|
||||
|
@ -117,14 +121,22 @@ TEST_CASE_FIXTURE(TypePathFixture, "table_property")
|
|||
local x = { y = 123 }
|
||||
)");
|
||||
|
||||
CHECK(traverseForType(requireType("x"), Path(TypePath::Property{"y", true}), getBuiltins()) == getBuiltins()->numberType);
|
||||
CHECK(
|
||||
traverseForType(requireType("x"), Path(TypePath::Property{"y", true}), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}) ==
|
||||
getBuiltins()->numberType
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ExternTypeFixture, "class_property")
|
||||
{
|
||||
// Force this here because vector2InstanceType won't get initialized until the frontend has been forced
|
||||
getFrontend();
|
||||
CHECK(traverseForType(vector2InstanceType, Path(TypePath::Property{"X", true}), getBuiltins()) == getBuiltins()->numberType);
|
||||
const DenseHashMap<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")
|
||||
|
@ -151,7 +163,10 @@ TEST_CASE_FIXTURE(TypePathBuiltinsFixture, "metatable_property")
|
|||
)");
|
||||
}
|
||||
|
||||
CHECK(traverseForType(requireType("x"), Path(TypePath::Property::read("x")), getBuiltins()) == getBuiltins()->numberType);
|
||||
CHECK(
|
||||
traverseForType(requireType("x"), Path(TypePath::Property::read("x")), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}) ==
|
||||
getBuiltins()->numberType
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(TypePathFixture, "index")
|
||||
|
@ -164,12 +179,17 @@ TEST_CASE_FIXTURE(TypePathFixture, "index")
|
|||
|
||||
SUBCASE("in_bounds")
|
||||
{
|
||||
CHECK(traverseForType(requireTypeAlias("T"), Path(TypePath::Index{1}), getBuiltins()) == getBuiltins()->stringType);
|
||||
CHECK(
|
||||
traverseForType(requireTypeAlias("T"), Path(TypePath::Index{1}), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}) ==
|
||||
getBuiltins()->stringType
|
||||
);
|
||||
}
|
||||
|
||||
SUBCASE("out_of_bounds")
|
||||
{
|
||||
CHECK(traverseForType(requireTypeAlias("T"), Path(TypePath::Index{97}), getBuiltins()) == std::nullopt);
|
||||
CHECK(
|
||||
traverseForType(requireTypeAlias("T"), Path(TypePath::Index{97}), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}) == std::nullopt
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -182,7 +202,7 @@ TEST_CASE_FIXTURE(TypePathFixture, "index")
|
|||
|
||||
SUBCASE("in_bounds")
|
||||
{
|
||||
auto result = traverseForType(requireTypeAlias("T"), Path(TypePath::Index{1}), getBuiltins());
|
||||
auto result = traverseForType(requireTypeAlias("T"), Path(TypePath::Index{1}), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena});
|
||||
CHECK(result);
|
||||
|
||||
if (result)
|
||||
|
@ -191,7 +211,9 @@ TEST_CASE_FIXTURE(TypePathFixture, "index")
|
|||
|
||||
SUBCASE("out_of_bounds")
|
||||
{
|
||||
CHECK(traverseForType(requireTypeAlias("T"), Path(TypePath::Index{97}), getBuiltins()) == std::nullopt);
|
||||
CHECK(
|
||||
traverseForType(requireTypeAlias("T"), Path(TypePath::Index{97}), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}) == std::nullopt
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -205,14 +227,14 @@ TEST_CASE_FIXTURE(TypePathFixture, "index")
|
|||
SUBCASE("in_bounds")
|
||||
{
|
||||
Path path = Path({TypePath::PackField::Arguments, TypePath::Index{1}});
|
||||
auto result = traverseForType(requireTypeAlias("T"), path, getBuiltins());
|
||||
auto result = traverseForType(requireTypeAlias("T"), path, getBuiltins(), NotNull{&emptyMap}, NotNull{&arena});
|
||||
CHECK(result == getBuiltins()->stringType);
|
||||
}
|
||||
|
||||
SUBCASE("out_of_bounds")
|
||||
{
|
||||
Path path = Path({TypePath::PackField::Arguments, TypePath::Index{72}});
|
||||
auto result = traverseForType(requireTypeAlias("T"), path, getBuiltins());
|
||||
auto result = traverseForType(requireTypeAlias("T"), path, getBuiltins(), NotNull{&emptyMap}, NotNull{&arena});
|
||||
CHECK(result == std::nullopt);
|
||||
}
|
||||
}
|
||||
|
@ -221,9 +243,12 @@ TEST_CASE_FIXTURE(TypePathFixture, "index")
|
|||
TEST_CASE_FIXTURE(ExternTypeFixture, "metatables")
|
||||
{
|
||||
getFrontend();
|
||||
const DenseHashMap<TypePackId, TypePackId> emptyMap{nullptr};
|
||||
TypeArena arena;
|
||||
|
||||
SUBCASE("string")
|
||||
{
|
||||
auto result = traverseForType(getBuiltins()->stringType, Path(TypeField::Metatable), getBuiltins());
|
||||
auto result = traverseForType(getBuiltins()->stringType, Path(TypeField::Metatable), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena});
|
||||
CHECK(result == getMetatable(getBuiltins()->stringType, getBuiltins()));
|
||||
}
|
||||
|
||||
|
@ -233,7 +258,7 @@ TEST_CASE_FIXTURE(ExternTypeFixture, "metatables")
|
|||
type T = "foo"
|
||||
)");
|
||||
|
||||
auto result = traverseForType(requireTypeAlias("T"), Path(TypeField::Metatable), getBuiltins());
|
||||
auto result = traverseForType(requireTypeAlias("T"), Path(TypeField::Metatable), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena});
|
||||
CHECK(result == getMetatable(getBuiltins()->stringType, getBuiltins()));
|
||||
}
|
||||
|
||||
|
@ -248,7 +273,7 @@ TEST_CASE_FIXTURE(ExternTypeFixture, "metatables")
|
|||
)");
|
||||
|
||||
// Tricky test setup because 'setmetatable' mutates the argument 'tbl' type
|
||||
auto result = traverseForType(requireType("res"), Path(TypeField::Table), getBuiltins());
|
||||
auto result = traverseForType(requireType("res"), Path(TypeField::Table), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena});
|
||||
auto expected = lookupType("Table");
|
||||
REQUIRE(expected);
|
||||
CHECK(result == follow(*expected));
|
||||
|
@ -261,13 +286,13 @@ TEST_CASE_FIXTURE(ExternTypeFixture, "metatables")
|
|||
local tbl = setmetatable({}, mt)
|
||||
)");
|
||||
|
||||
auto result = traverseForType(requireType("tbl"), Path(TypeField::Metatable), getBuiltins());
|
||||
auto result = traverseForType(requireType("tbl"), Path(TypeField::Metatable), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena});
|
||||
CHECK(result == requireType("mt"));
|
||||
}
|
||||
|
||||
SUBCASE("class")
|
||||
{
|
||||
auto result = traverseForType(vector2InstanceType, Path(TypeField::Metatable), getBuiltins());
|
||||
auto result = traverseForType(vector2InstanceType, Path(TypeField::Metatable), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena});
|
||||
// ExternTypeFixture's Vector2 metatable is just an empty table, but it's there.
|
||||
CHECK(result);
|
||||
}
|
||||
|
@ -286,22 +311,28 @@ TEST_CASE_FIXTURE(TypePathFixture, "bounds")
|
|||
SUBCASE("upper")
|
||||
{
|
||||
ft->upperBound = getBuiltins()->numberType;
|
||||
auto result = traverseForType(ty, Path(TypeField::UpperBound), getBuiltins());
|
||||
auto result = traverseForType(ty, Path(TypeField::UpperBound), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena});
|
||||
CHECK(result == getBuiltins()->numberType);
|
||||
}
|
||||
|
||||
SUBCASE("lower")
|
||||
{
|
||||
ft->lowerBound = getBuiltins()->booleanType;
|
||||
auto result = traverseForType(ty, Path(TypeField::LowerBound), getBuiltins());
|
||||
auto result = traverseForType(ty, Path(TypeField::LowerBound), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena});
|
||||
CHECK(result == getBuiltins()->booleanType);
|
||||
}
|
||||
}
|
||||
|
||||
SUBCASE("unbounded_type")
|
||||
{
|
||||
CHECK(traverseForType(getBuiltins()->numberType, Path(TypeField::UpperBound), getBuiltins()) == std::nullopt);
|
||||
CHECK(traverseForType(getBuiltins()->numberType, Path(TypeField::LowerBound), getBuiltins()) == std::nullopt);
|
||||
CHECK(
|
||||
traverseForType(getBuiltins()->numberType, Path(TypeField::UpperBound), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}) ==
|
||||
std::nullopt
|
||||
);
|
||||
CHECK(
|
||||
traverseForType(getBuiltins()->numberType, Path(TypeField::LowerBound), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}) ==
|
||||
std::nullopt
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -315,8 +346,10 @@ TEST_CASE_FIXTURE(TypePathFixture, "indexers")
|
|||
type T = { [string]: boolean }
|
||||
)");
|
||||
|
||||
auto lookupResult = traverseForType(requireTypeAlias("T"), Path(TypeField::IndexLookup), getBuiltins());
|
||||
auto resultResult = traverseForType(requireTypeAlias("T"), Path(TypeField::IndexResult), getBuiltins());
|
||||
auto lookupResult =
|
||||
traverseForType(requireTypeAlias("T"), Path(TypeField::IndexLookup), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena});
|
||||
auto resultResult =
|
||||
traverseForType(requireTypeAlias("T"), Path(TypeField::IndexResult), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena});
|
||||
|
||||
CHECK(lookupResult == getBuiltins()->stringType);
|
||||
CHECK(resultResult == getBuiltins()->booleanType);
|
||||
|
@ -328,8 +361,10 @@ TEST_CASE_FIXTURE(TypePathFixture, "indexers")
|
|||
type T = { y: number }
|
||||
)");
|
||||
|
||||
auto lookupResult = traverseForType(requireTypeAlias("T"), Path(TypeField::IndexLookup), getBuiltins());
|
||||
auto resultResult = traverseForType(requireTypeAlias("T"), Path(TypeField::IndexResult), getBuiltins());
|
||||
auto lookupResult =
|
||||
traverseForType(requireTypeAlias("T"), Path(TypeField::IndexLookup), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena});
|
||||
auto resultResult =
|
||||
traverseForType(requireTypeAlias("T"), Path(TypeField::IndexResult), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena});
|
||||
|
||||
CHECK(lookupResult == std::nullopt);
|
||||
CHECK(resultResult == std::nullopt);
|
||||
|
@ -347,13 +382,13 @@ TEST_CASE_FIXTURE(TypePathFixture, "negated")
|
|||
unfreeze(arena);
|
||||
|
||||
TypeId ty = arena.addType(NegationType{getBuiltins()->numberType});
|
||||
auto result = traverseForType(ty, Path(TypeField::Negated), getBuiltins());
|
||||
auto result = traverseForType(ty, Path(TypeField::Negated), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena});
|
||||
CHECK(result == getBuiltins()->numberType);
|
||||
}
|
||||
|
||||
SUBCASE("not_negation")
|
||||
{
|
||||
auto result = traverseForType(getBuiltins()->numberType, Path(TypeField::Negated), getBuiltins());
|
||||
auto result = traverseForType(getBuiltins()->numberType, Path(TypeField::Negated), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena});
|
||||
CHECK(result == std::nullopt);
|
||||
}
|
||||
}
|
||||
|
@ -366,13 +401,13 @@ TEST_CASE_FIXTURE(TypePathFixture, "variadic")
|
|||
unfreeze(arena);
|
||||
|
||||
TypePackId tp = arena.addTypePack(VariadicTypePack{getBuiltins()->numberType});
|
||||
auto result = traverseForType(tp, Path(TypeField::Variadic), getBuiltins());
|
||||
auto result = traverseForType(tp, Path(TypeField::Variadic), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena});
|
||||
CHECK(result == getBuiltins()->numberType);
|
||||
}
|
||||
|
||||
SUBCASE("not_variadic")
|
||||
{
|
||||
auto result = traverseForType(getBuiltins()->numberType, Path(TypeField::Variadic), getBuiltins());
|
||||
auto result = traverseForType(getBuiltins()->numberType, Path(TypeField::Variadic), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena});
|
||||
CHECK(result == std::nullopt);
|
||||
}
|
||||
}
|
||||
|
@ -386,7 +421,7 @@ TEST_CASE_FIXTURE(TypePathFixture, "arguments")
|
|||
end
|
||||
)");
|
||||
|
||||
auto result = traverseForPack(requireType("f"), Path(PackField::Arguments), getBuiltins());
|
||||
auto result = traverseForPack(requireType("f"), Path(PackField::Arguments), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena});
|
||||
CHECK(result);
|
||||
if (result)
|
||||
CHECK(toString(*result) == "number, string");
|
||||
|
@ -394,7 +429,7 @@ TEST_CASE_FIXTURE(TypePathFixture, "arguments")
|
|||
|
||||
SUBCASE("not_function")
|
||||
{
|
||||
auto result = traverseForPack(getBuiltins()->booleanType, Path(PackField::Arguments), getBuiltins());
|
||||
auto result = traverseForPack(getBuiltins()->booleanType, Path(PackField::Arguments), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena});
|
||||
CHECK(result == std::nullopt);
|
||||
}
|
||||
}
|
||||
|
@ -409,7 +444,7 @@ TEST_CASE_FIXTURE(TypePathFixture, "returns")
|
|||
end
|
||||
)");
|
||||
|
||||
auto result = traverseForPack(requireType("f"), Path(PackField::Returns), getBuiltins());
|
||||
auto result = traverseForPack(requireType("f"), Path(PackField::Returns), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena});
|
||||
CHECK(result);
|
||||
if (result)
|
||||
CHECK(toString(*result) == "number, string");
|
||||
|
@ -417,7 +452,7 @@ TEST_CASE_FIXTURE(TypePathFixture, "returns")
|
|||
|
||||
SUBCASE("not_function")
|
||||
{
|
||||
auto result = traverseForPack(getBuiltins()->booleanType, Path(PackField::Returns), getBuiltins());
|
||||
auto result = traverseForPack(getBuiltins()->booleanType, Path(PackField::Returns), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena});
|
||||
CHECK(result == std::nullopt);
|
||||
}
|
||||
}
|
||||
|
@ -430,7 +465,8 @@ TEST_CASE_FIXTURE(TypePathFixture, "tail")
|
|||
type T = (number, string, ...boolean) -> ()
|
||||
)");
|
||||
|
||||
auto result = traverseForPack(requireTypeAlias("T"), Path({PackField::Arguments, PackField::Tail}), getBuiltins());
|
||||
auto result =
|
||||
traverseForPack(requireTypeAlias("T"), Path({PackField::Arguments, PackField::Tail}), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena});
|
||||
CHECK(result);
|
||||
if (result)
|
||||
CHECK(toString(*result) == "...boolean");
|
||||
|
@ -442,17 +478,62 @@ TEST_CASE_FIXTURE(TypePathFixture, "tail")
|
|||
type T = (number, string) -> ()
|
||||
)");
|
||||
|
||||
auto result = traverseForPack(requireTypeAlias("T"), Path({PackField::Arguments, PackField::Tail}), getBuiltins());
|
||||
auto result =
|
||||
traverseForPack(requireTypeAlias("T"), Path({PackField::Arguments, PackField::Tail}), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena});
|
||||
CHECK(result == std::nullopt);
|
||||
}
|
||||
|
||||
SUBCASE("type")
|
||||
{
|
||||
auto result = traverseForPack(getBuiltins()->stringType, Path({PackField::Arguments, PackField::Tail}), getBuiltins());
|
||||
auto result = traverseForPack(
|
||||
getBuiltins()->stringType, Path({PackField::Arguments, PackField::Tail}), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}
|
||||
);
|
||||
CHECK(result == std::nullopt);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(TypePathFixture, "pack_slice_has_tail")
|
||||
{
|
||||
TypeArena& arena = getFrontend().globals.globalTypes;
|
||||
unfreeze(arena);
|
||||
|
||||
TYPESOLVE_CODE(R"(
|
||||
type T = (number, string, ...boolean) -> ()
|
||||
)");
|
||||
|
||||
auto result =
|
||||
traverseForPack(requireTypeAlias("T"), Path({PackField::Arguments, PackSlice{1}}), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena});
|
||||
CHECK(result);
|
||||
if (result)
|
||||
CHECK(toString(*result) == "string, ...boolean");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(TypePathFixture, "pack_slice_finite_pack")
|
||||
{
|
||||
TypeArena& arena = getFrontend().globals.globalTypes;
|
||||
unfreeze(arena);
|
||||
|
||||
TYPESOLVE_CODE(R"(
|
||||
type T = (number, string) -> ()
|
||||
)");
|
||||
|
||||
auto result =
|
||||
traverseForPack(requireTypeAlias("T"), Path({PackField::Arguments, PackSlice{1}}), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena});
|
||||
CHECK(result);
|
||||
if (result)
|
||||
CHECK(toString(*result) == "string");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(TypePathFixture, "pack_slice_type")
|
||||
{
|
||||
TypeArena& arena = getFrontend().globals.globalTypes;
|
||||
unfreeze(arena);
|
||||
|
||||
auto result =
|
||||
traverseForPack(builtinTypes->stringType, Path({PackField::Arguments, PackSlice{1}}), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena});
|
||||
CHECK(result == std::nullopt);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(TypePathFixture, "cycles" * doctest::timeout(0.5))
|
||||
{
|
||||
// This will fail an occurs check, but it's a quick example of a cyclic type
|
||||
|
@ -466,7 +547,7 @@ TEST_CASE_FIXTURE(TypePathFixture, "cycles" * doctest::timeout(0.5))
|
|||
TypeId b = arena.addType(BoundType{a});
|
||||
asMutable(a)->ty.emplace<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")
|
||||
|
@ -477,7 +558,7 @@ TEST_CASE_FIXTURE(TypePathFixture, "cycles" * doctest::timeout(0.5))
|
|||
TypeId tbl = arena.addType(TableType{});
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -498,7 +579,7 @@ TEST_CASE_FIXTURE(TypePathFixture, "step_limit")
|
|||
|
||||
TypeId root = requireTypeAlias("T");
|
||||
Path path = PathBuilder().readProp("x").readProp("y").readProp("z").build();
|
||||
auto result = traverseForType(root, path, getBuiltins());
|
||||
auto result = traverseForType(root, path, getBuiltins(), NotNull{&emptyMap}, NotNull{&arena});
|
||||
CHECK(!result);
|
||||
}
|
||||
|
||||
|
@ -516,7 +597,7 @@ TEST_CASE_FIXTURE(TypePathBuiltinsFixture, "complex_chains")
|
|||
|
||||
TypeId root = requireTypeAlias("Tab");
|
||||
Path path = PathBuilder().mt().readProp("__add").rets().index(0).build();
|
||||
auto result = traverseForType(root, path, getBuiltins());
|
||||
auto result = traverseForType(root, path, getBuiltins(), NotNull{&emptyMap}, NotNull{&arena});
|
||||
CHECK(result == getBuiltins()->numberType);
|
||||
}
|
||||
|
||||
|
@ -530,7 +611,7 @@ TEST_CASE_FIXTURE(TypePathBuiltinsFixture, "complex_chains")
|
|||
|
||||
TypeId root = requireTypeAlias("Obj");
|
||||
Path path = PathBuilder().readProp("method").index(0).args().index(1).build();
|
||||
auto result = traverseForType(root, path, getBuiltins());
|
||||
auto result = traverseForType(root, path, getBuiltins(), NotNull{&emptyMap}, NotNull{&arena});
|
||||
CHECK(*result == getBuiltins()->falseType);
|
||||
}
|
||||
}
|
||||
|
@ -564,6 +645,14 @@ TEST_CASE("human_property_then_metatable_portion")
|
|||
CHECK(toStringHuman(PathBuilder().writeProp("a").mt().build()) == "writing to `a` has the metatable portion as ");
|
||||
}
|
||||
|
||||
TEST_CASE("pack_slice")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauSolverV2, true};
|
||||
|
||||
CHECK(toString(PathBuilder().packSlice(1).build()) == "[1:]");
|
||||
CHECK(toStringHuman(PathBuilder().packSlice(1).build()) == "the portion of the type pack starting at index 1 to the end");
|
||||
}
|
||||
|
||||
TEST_SUITE_END(); // TypePathToString
|
||||
|
||||
TEST_SUITE_BEGIN("TypePathBuilder");
|
||||
|
@ -623,5 +712,9 @@ TEST_CASE("chained")
|
|||
Path({Index{0}, TypePath::Property::read("foo"), TypeField::Metatable, TypePath::Property::read("bar"), PackField::Arguments, Index{1}})
|
||||
);
|
||||
}
|
||||
TEST_CASE("pack_slice")
|
||||
{
|
||||
CHECK(PathBuilder().packSlice(3).build() == Path(PackSlice{3}));
|
||||
}
|
||||
|
||||
TEST_SUITE_END(); // TypePathBuilder
|
||||
|
|
Loading…
Add table
Reference in a new issue