Sync to upstream/release/577 (#934)
Lots of things going on this week: * Fix a crash that could occur in the presence of a cyclic union. We shouldn't be creating cyclic unions, but we shouldn't be crashing when they arise either. * Minor cleanup of `luau_precall` * Internal change to make L->top handling slightly more uniform * Optimize SETGLOBAL & GETGLOBAL fallback C functions. * https://github.com/Roblox/luau/pull/929 * The syntax to the `luau-reduce` commandline tool has changed. It now accepts a script, a command to execute, and an error to search for. It no longer automatically passes the script to the command which makes it a lot more flexible. Also be warned that it edits the script it is passed **in place**. Do not point it at something that is not in source control! New solver * Switch to a greedier but more fallible algorithm for simplifying union and intersection types that are created as part of refinement calculation. This has much better and more predictable performance. * Fix a constraint cycle in recursive function calls. * Much improved inference of binary addition. Functions like `function add(x, y) return x + y end` can now be inferred without annotations. We also accurately typecheck calls to functions like this. * Many small bugfixes surrounding things like table indexers * Add support for indexers on class types. This was previously added to the old solver; we now add it to the new one for feature parity. JIT * https://github.com/Roblox/luau/pull/931 * Fuse key.value and key.tt loads for CEHCK_SLOT_MATCH in A64 * Implement remaining aliases of BFM for A64 * Implement new callinfo flag for A64 * Add instruction simplification for int->num->int conversion chains * Don't even load execdata for X64 calls * Treat opcode fallbacks the same as manually written fallbacks --------- Co-authored-by: Arseny Kapoulkine <arseny.kapoulkine@gmail.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
This commit is contained in:
parent
da0458bf6e
commit
721f6e10fb
|
@ -144,6 +144,24 @@ struct HasPropConstraint
|
|||
TypeId resultType;
|
||||
TypeId subjectType;
|
||||
std::string prop;
|
||||
|
||||
// HACK: We presently need types like true|false or string|"hello" when
|
||||
// deciding whether a particular literal expression should have a singleton
|
||||
// type. This boolean is set to true when extracting the property type of a
|
||||
// value that may be a union of tables.
|
||||
//
|
||||
// For example, in the following code fragment, we want the lookup of the
|
||||
// success property to yield true|false when extracting an expectedType in
|
||||
// this expression:
|
||||
//
|
||||
// type Result<T, E> = {success:true, result: T} | {success:false, error: E}
|
||||
//
|
||||
// local r: Result<number, string> = {success=true, result=9}
|
||||
//
|
||||
// If we naively simplify the expectedType to boolean, we will erroneously
|
||||
// compute the type boolean for the success property of the table literal.
|
||||
// This causes type checking to fail.
|
||||
bool suppressSimplification = false;
|
||||
};
|
||||
|
||||
// result ~ setProp subjectType ["prop", "prop2", ...] propType
|
||||
|
@ -198,6 +216,24 @@ struct UnpackConstraint
|
|||
TypePackId sourcePack;
|
||||
};
|
||||
|
||||
// resultType ~ refine type mode discriminant
|
||||
//
|
||||
// Compute type & discriminant (or type | discriminant) as soon as possible (but
|
||||
// no sooner), simplify, and bind resultType to that type.
|
||||
struct RefineConstraint
|
||||
{
|
||||
enum
|
||||
{
|
||||
Intersection,
|
||||
Union
|
||||
} mode;
|
||||
|
||||
TypeId resultType;
|
||||
|
||||
TypeId type;
|
||||
TypeId discriminant;
|
||||
};
|
||||
|
||||
// ty ~ reduce ty
|
||||
//
|
||||
// Try to reduce ty, if it is a TypeFamilyInstanceType. Otherwise, do nothing.
|
||||
|
@ -214,10 +250,10 @@ struct ReducePackConstraint
|
|||
TypePackId tp;
|
||||
};
|
||||
|
||||
using ConstraintV =
|
||||
Variant<SubtypeConstraint, PackSubtypeConstraint, GeneralizationConstraint, InstantiationConstraint, UnaryConstraint, BinaryConstraint,
|
||||
IterableConstraint, NameConstraint, TypeAliasExpansionConstraint, FunctionCallConstraint, PrimitiveTypeConstraint, HasPropConstraint,
|
||||
SetPropConstraint, SetIndexerConstraint, SingletonOrTopTypeConstraint, UnpackConstraint, ReduceConstraint, ReducePackConstraint>;
|
||||
using ConstraintV = Variant<SubtypeConstraint, PackSubtypeConstraint, GeneralizationConstraint, InstantiationConstraint, UnaryConstraint,
|
||||
BinaryConstraint, IterableConstraint, NameConstraint, TypeAliasExpansionConstraint, FunctionCallConstraint, PrimitiveTypeConstraint,
|
||||
HasPropConstraint, SetPropConstraint, SetIndexerConstraint, SingletonOrTopTypeConstraint, UnpackConstraint, RefineConstraint, ReduceConstraint,
|
||||
ReducePackConstraint>;
|
||||
|
||||
struct Constraint
|
||||
{
|
||||
|
|
|
@ -188,6 +188,7 @@ struct ConstraintGraphBuilder
|
|||
Inference check(const ScopePtr& scope, AstExprGlobal* global);
|
||||
Inference check(const ScopePtr& scope, AstExprIndexName* indexName);
|
||||
Inference check(const ScopePtr& scope, AstExprIndexExpr* indexExpr);
|
||||
Inference check(const ScopePtr& scope, AstExprFunction* func, std::optional<TypeId> expectedType);
|
||||
Inference check(const ScopePtr& scope, AstExprUnary* unary);
|
||||
Inference check(const ScopePtr& scope, AstExprBinary* binary, std::optional<TypeId> expectedType);
|
||||
Inference check(const ScopePtr& scope, AstExprIfElse* ifElse, std::optional<TypeId> expectedType);
|
||||
|
@ -213,7 +214,8 @@ struct ConstraintGraphBuilder
|
|||
ScopePtr bodyScope;
|
||||
};
|
||||
|
||||
FunctionSignature checkFunctionSignature(const ScopePtr& parent, AstExprFunction* fn, std::optional<TypeId> expectedType = {});
|
||||
FunctionSignature checkFunctionSignature(
|
||||
const ScopePtr& parent, AstExprFunction* fn, std::optional<TypeId> expectedType = {}, std::optional<Location> originalName = {});
|
||||
|
||||
/**
|
||||
* Checks the body of a function expression.
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
#include "Luau/Normalize.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypeReduction.h"
|
||||
#include "Luau/Variant.h"
|
||||
|
||||
#include <vector>
|
||||
|
@ -121,6 +120,7 @@ struct ConstraintSolver
|
|||
bool tryDispatch(const SetIndexerConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||
bool tryDispatch(const SingletonOrTopTypeConstraint& c, NotNull<const Constraint> constraint);
|
||||
bool tryDispatch(const UnpackConstraint& c, NotNull<const Constraint> constraint);
|
||||
bool tryDispatch(const RefineConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||
bool tryDispatch(const ReduceConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||
bool tryDispatch(const ReducePackConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||
|
||||
|
@ -132,8 +132,10 @@ struct ConstraintSolver
|
|||
bool tryDispatchIterableFunction(
|
||||
TypeId nextTy, TypeId tableTy, TypeId firstIndexTy, const IterableConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||
|
||||
std::pair<std::vector<TypeId>, std::optional<TypeId>> lookupTableProp(TypeId subjectType, const std::string& propName);
|
||||
std::pair<std::vector<TypeId>, std::optional<TypeId>> lookupTableProp(TypeId subjectType, const std::string& propName, std::unordered_set<TypeId>& seen);
|
||||
std::pair<std::vector<TypeId>, std::optional<TypeId>> lookupTableProp(
|
||||
TypeId subjectType, const std::string& propName, bool suppressSimplification = false);
|
||||
std::pair<std::vector<TypeId>, std::optional<TypeId>> lookupTableProp(
|
||||
TypeId subjectType, const std::string& propName, bool suppressSimplification, std::unordered_set<TypeId>& seen);
|
||||
|
||||
void block(NotNull<const Constraint> target, NotNull<const Constraint> constraint);
|
||||
/**
|
||||
|
@ -143,6 +145,16 @@ struct ConstraintSolver
|
|||
bool block(TypeId target, NotNull<const Constraint> constraint);
|
||||
bool block(TypePackId target, NotNull<const Constraint> constraint);
|
||||
|
||||
// Block on every target
|
||||
template<typename T>
|
||||
bool block(const T& targets, NotNull<const Constraint> constraint)
|
||||
{
|
||||
for (TypeId target : targets)
|
||||
block(target, constraint);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* For all constraints that are blocked on one constraint, make them block
|
||||
* on a new constraint.
|
||||
|
@ -151,15 +163,15 @@ struct ConstraintSolver
|
|||
*/
|
||||
void inheritBlocks(NotNull<const Constraint> source, NotNull<const Constraint> addition);
|
||||
|
||||
// Traverse the type. If any blocked or pending types are found, block
|
||||
// the constraint on them.
|
||||
// Traverse the type. If any pending types are found, block the constraint
|
||||
// on them.
|
||||
//
|
||||
// Returns false if a type blocks the constraint.
|
||||
//
|
||||
// FIXME: This use of a boolean for the return result is an appalling
|
||||
// interface.
|
||||
bool recursiveBlock(TypeId target, NotNull<const Constraint> constraint);
|
||||
bool recursiveBlock(TypePackId target, NotNull<const Constraint> constraint);
|
||||
bool blockOnPendingTypes(TypeId target, NotNull<const Constraint> constraint);
|
||||
bool blockOnPendingTypes(TypePackId target, NotNull<const Constraint> constraint);
|
||||
|
||||
void unblock(NotNull<const Constraint> progressed);
|
||||
void unblock(TypeId progressed);
|
||||
|
@ -255,6 +267,8 @@ private:
|
|||
|
||||
TypeId unionOfTypes(TypeId a, TypeId b, NotNull<Scope> scope, bool unifyFreeTypes);
|
||||
|
||||
TypePackId anyifyModuleReturnTypePackGenerics(TypePackId tp);
|
||||
|
||||
ToStringOptions opts;
|
||||
};
|
||||
|
||||
|
|
|
@ -85,14 +85,11 @@ struct Module
|
|||
DenseHashMap<const AstNode*, TypeId> astOverloadResolvedTypes{nullptr};
|
||||
|
||||
DenseHashMap<const AstType*, TypeId> astResolvedTypes{nullptr};
|
||||
DenseHashMap<const AstType*, TypeId> astOriginalResolvedTypes{nullptr};
|
||||
DenseHashMap<const AstTypePack*, TypePackId> astResolvedTypePacks{nullptr};
|
||||
|
||||
// Map AST nodes to the scope they create. Cannot be NotNull<Scope> because we need a sentinel value for the map.
|
||||
DenseHashMap<const AstNode*, Scope*> astScopes{nullptr};
|
||||
|
||||
std::unique_ptr<struct TypeReduction> reduction;
|
||||
|
||||
std::unordered_map<Name, TypeId> declaredGlobals;
|
||||
ErrorVec errors;
|
||||
LintResult lintResult;
|
||||
|
|
|
@ -267,8 +267,18 @@ struct NormalizedType
|
|||
|
||||
NormalizedType(NormalizedType&&) = default;
|
||||
NormalizedType& operator=(NormalizedType&&) = default;
|
||||
|
||||
// IsType functions
|
||||
|
||||
/// Returns true if the type is a subtype of function. This includes any and unknown.
|
||||
bool isFunction() const;
|
||||
|
||||
/// Returns true if the type is a subtype of number. This includes any and unknown.
|
||||
bool isNumber() const;
|
||||
};
|
||||
|
||||
|
||||
|
||||
class Normalizer
|
||||
{
|
||||
std::unordered_map<TypeId, std::unique_ptr<NormalizedType>> cachedNormals;
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Type.h"
|
||||
|
||||
#include <set>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct TypeArena;
|
||||
struct BuiltinTypes;
|
||||
|
||||
struct SimplifyResult
|
||||
{
|
||||
TypeId result;
|
||||
|
||||
std::set<TypeId> blockedTypes;
|
||||
};
|
||||
|
||||
SimplifyResult simplifyIntersection(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId ty, TypeId discriminant);
|
||||
SimplifyResult simplifyUnion(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId ty, TypeId discriminant);
|
||||
|
||||
enum class Relation
|
||||
{
|
||||
Disjoint, // No A is a B or vice versa
|
||||
Coincident, // Every A is in B and vice versa
|
||||
Intersects, // Some As are in B and some Bs are in A. ex (number | string) <-> (string | boolean)
|
||||
Subset, // Every A is in B
|
||||
Superset, // Every B is in A
|
||||
};
|
||||
|
||||
Relation relate(TypeId left, TypeId right);
|
||||
|
||||
} // namespace Luau
|
|
@ -99,10 +99,7 @@ inline std::string toString(const Constraint& c, ToStringOptions&& opts)
|
|||
return toString(c, opts);
|
||||
}
|
||||
|
||||
inline std::string toString(const Constraint& c)
|
||||
{
|
||||
return toString(c, ToStringOptions{});
|
||||
}
|
||||
std::string toString(const Constraint& c);
|
||||
|
||||
std::string toString(const Type& tv, ToStringOptions& opts);
|
||||
std::string toString(const TypePackVar& tp, ToStringOptions& opts);
|
||||
|
|
|
@ -308,6 +308,12 @@ public:
|
|||
// used. Else we use the embedded Scope*.
|
||||
bool useScopes = false;
|
||||
|
||||
// It is sometimes the case under DCR that we speculatively rebind
|
||||
// GenericTypes to other types as though they were free. We mark logs that
|
||||
// contain these kinds of substitutions as radioactive so that we know that
|
||||
// we must never commit one.
|
||||
bool radioactive = false;
|
||||
|
||||
// Used to avoid infinite recursion when types are cyclic.
|
||||
// Shared with all the descendent TxnLogs.
|
||||
std::vector<std::pair<TypeOrPackId, TypeOrPackId>>* sharedSeen;
|
||||
|
|
|
@ -349,7 +349,9 @@ struct FunctionType
|
|||
DcrMagicFunction dcrMagicFunction = nullptr;
|
||||
DcrMagicRefinement dcrMagicRefinement = nullptr;
|
||||
bool hasSelf;
|
||||
bool hasNoGenerics = false;
|
||||
// `hasNoFreeOrGenericTypes` should be true if and only if the type does not have any free or generic types present inside it.
|
||||
// this flag is used as an optimization to exit early from procedures that manipulate free or generic types.
|
||||
bool hasNoFreeOrGenericTypes = false;
|
||||
};
|
||||
|
||||
enum class TableState
|
||||
|
@ -530,7 +532,7 @@ struct ClassType
|
|||
*/
|
||||
struct TypeFamilyInstanceType
|
||||
{
|
||||
NotNull<TypeFamily> family;
|
||||
NotNull<const TypeFamily> family;
|
||||
|
||||
std::vector<TypeId> typeArguments;
|
||||
std::vector<TypePackId> packArguments;
|
||||
|
|
|
@ -21,6 +21,7 @@ using TypePackId = const TypePackVar*;
|
|||
struct TypeArena;
|
||||
struct BuiltinTypes;
|
||||
struct TxnLog;
|
||||
class Normalizer;
|
||||
|
||||
/// Represents a reduction result, which may have successfully reduced the type,
|
||||
/// may have concretely failed to reduce the type, or may simply be stuck
|
||||
|
@ -52,8 +53,8 @@ struct TypeFamily
|
|||
std::string name;
|
||||
|
||||
/// The reducer function for the type family.
|
||||
std::function<TypeFamilyReductionResult<TypeId>(
|
||||
std::vector<TypeId>, std::vector<TypePackId>, NotNull<TypeArena>, NotNull<BuiltinTypes>, NotNull<const TxnLog> log)>
|
||||
std::function<TypeFamilyReductionResult<TypeId>(std::vector<TypeId>, std::vector<TypePackId>, NotNull<TypeArena>, NotNull<BuiltinTypes>,
|
||||
NotNull<TxnLog>, NotNull<Scope>, NotNull<Normalizer>)>
|
||||
reducer;
|
||||
};
|
||||
|
||||
|
@ -66,8 +67,8 @@ struct TypePackFamily
|
|||
std::string name;
|
||||
|
||||
/// The reducer function for the type pack family.
|
||||
std::function<TypeFamilyReductionResult<TypePackId>(
|
||||
std::vector<TypeId>, std::vector<TypePackId>, NotNull<TypeArena>, NotNull<BuiltinTypes>, NotNull<const TxnLog> log)>
|
||||
std::function<TypeFamilyReductionResult<TypePackId>(std::vector<TypeId>, std::vector<TypePackId>, NotNull<TypeArena>, NotNull<BuiltinTypes>,
|
||||
NotNull<TxnLog>, NotNull<Scope>, NotNull<Normalizer>)>
|
||||
reducer;
|
||||
};
|
||||
|
||||
|
@ -93,8 +94,8 @@ struct FamilyGraphReductionResult
|
|||
* against the TxnLog, otherwise substitutions will directly mutate the type
|
||||
* graph. Do not provide the empty TxnLog, as a result.
|
||||
*/
|
||||
FamilyGraphReductionResult reduceFamilies(
|
||||
TypeId entrypoint, Location location, NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtins, TxnLog* log = nullptr, bool force = false);
|
||||
FamilyGraphReductionResult reduceFamilies(TypeId entrypoint, Location location, NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtins,
|
||||
NotNull<Scope> scope, NotNull<Normalizer> normalizer, TxnLog* log = nullptr, bool force = false);
|
||||
|
||||
/**
|
||||
* Attempt to reduce all instances of any type or type pack family in the type
|
||||
|
@ -109,7 +110,16 @@ FamilyGraphReductionResult reduceFamilies(
|
|||
* against the TxnLog, otherwise substitutions will directly mutate the type
|
||||
* graph. Do not provide the empty TxnLog, as a result.
|
||||
*/
|
||||
FamilyGraphReductionResult reduceFamilies(
|
||||
TypePackId entrypoint, Location location, NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtins, TxnLog* log = nullptr, bool force = false);
|
||||
FamilyGraphReductionResult reduceFamilies(TypePackId entrypoint, Location location, NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtins,
|
||||
NotNull<Scope> scope, NotNull<Normalizer> normalizer, TxnLog* log = nullptr, bool force = false);
|
||||
|
||||
struct BuiltinTypeFamilies
|
||||
{
|
||||
BuiltinTypeFamilies();
|
||||
|
||||
TypeFamily addFamily;
|
||||
};
|
||||
|
||||
const BuiltinTypeFamilies kBuiltinTypeFamilies{};
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -1,85 +0,0 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypeArena.h"
|
||||
#include "Luau/TypePack.h"
|
||||
#include "Luau/Variant.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
namespace detail
|
||||
{
|
||||
template<typename T>
|
||||
struct ReductionEdge
|
||||
{
|
||||
T type = nullptr;
|
||||
bool irreducible = false;
|
||||
};
|
||||
|
||||
struct TypeReductionMemoization
|
||||
{
|
||||
TypeReductionMemoization() = default;
|
||||
|
||||
TypeReductionMemoization(const TypeReductionMemoization&) = delete;
|
||||
TypeReductionMemoization& operator=(const TypeReductionMemoization&) = delete;
|
||||
|
||||
TypeReductionMemoization(TypeReductionMemoization&&) = default;
|
||||
TypeReductionMemoization& operator=(TypeReductionMemoization&&) = default;
|
||||
|
||||
DenseHashMap<TypeId, ReductionEdge<TypeId>> types{nullptr};
|
||||
DenseHashMap<TypePackId, ReductionEdge<TypePackId>> typePacks{nullptr};
|
||||
|
||||
bool isIrreducible(TypeId ty);
|
||||
bool isIrreducible(TypePackId tp);
|
||||
|
||||
TypeId memoize(TypeId ty, TypeId reducedTy);
|
||||
TypePackId memoize(TypePackId tp, TypePackId reducedTp);
|
||||
|
||||
// Reducing A into B may have a non-irreducible edge A to B for which B is not irreducible, which means B could be reduced into C.
|
||||
// Because reduction should always be transitive, A should point to C if A points to B and B points to C.
|
||||
std::optional<ReductionEdge<TypeId>> memoizedof(TypeId ty) const;
|
||||
std::optional<ReductionEdge<TypePackId>> memoizedof(TypePackId tp) const;
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
struct TypeReductionOptions
|
||||
{
|
||||
/// If it's desirable for type reduction to allocate into a different arena than the TypeReduction instance you have, you will need
|
||||
/// to create a temporary TypeReduction in that case, and set [`TypeReductionOptions::allowTypeReductionsFromOtherArenas`] to true.
|
||||
/// This is because TypeReduction caches the reduced type.
|
||||
bool allowTypeReductionsFromOtherArenas = false;
|
||||
};
|
||||
|
||||
struct TypeReduction
|
||||
{
|
||||
explicit TypeReduction(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<struct InternalErrorReporter> handle,
|
||||
const TypeReductionOptions& opts = {});
|
||||
|
||||
TypeReduction(const TypeReduction&) = delete;
|
||||
TypeReduction& operator=(const TypeReduction&) = delete;
|
||||
|
||||
TypeReduction(TypeReduction&&) = default;
|
||||
TypeReduction& operator=(TypeReduction&&) = default;
|
||||
|
||||
std::optional<TypeId> reduce(TypeId ty);
|
||||
std::optional<TypePackId> reduce(TypePackId tp);
|
||||
std::optional<TypeFun> reduce(const TypeFun& fun);
|
||||
|
||||
private:
|
||||
NotNull<TypeArena> arena;
|
||||
NotNull<BuiltinTypes> builtinTypes;
|
||||
NotNull<struct InternalErrorReporter> handle;
|
||||
|
||||
TypeReductionOptions options;
|
||||
detail::TypeReductionMemoization memoization;
|
||||
|
||||
// Computes an *estimated length* of the cartesian product of the given type.
|
||||
size_t cartesianProductSize(TypeId ty) const;
|
||||
|
||||
bool hasExceededCartesianProductLimit(TypeId ty) const;
|
||||
bool hasExceededCartesianProductLimit(TypePackId tp) const;
|
||||
};
|
||||
|
||||
} // namespace Luau
|
|
@ -7,7 +7,6 @@
|
|||
#include "Luau/ToString.h"
|
||||
#include "Luau/TypeInfer.h"
|
||||
#include "Luau/TypePack.h"
|
||||
#include "Luau/TypeReduction.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <unordered_set>
|
||||
|
|
|
@ -11,6 +11,7 @@ LUAU_FASTFLAG(LuauClonePublicInterfaceLess2)
|
|||
LUAU_FASTFLAG(DebugLuauReadWriteProperties)
|
||||
|
||||
LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 300)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCloneCyclicUnions, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -282,7 +283,7 @@ void TypeCloner::operator()(const FunctionType& t)
|
|||
ftv->argTypes = clone(t.argTypes, dest, cloneState);
|
||||
ftv->argNames = t.argNames;
|
||||
ftv->retTypes = clone(t.retTypes, dest, cloneState);
|
||||
ftv->hasNoGenerics = t.hasNoGenerics;
|
||||
ftv->hasNoFreeOrGenericTypes = t.hasNoFreeOrGenericTypes;
|
||||
}
|
||||
|
||||
void TypeCloner::operator()(const TableType& t)
|
||||
|
@ -373,14 +374,30 @@ void TypeCloner::operator()(const AnyType& t)
|
|||
|
||||
void TypeCloner::operator()(const UnionType& t)
|
||||
{
|
||||
std::vector<TypeId> options;
|
||||
options.reserve(t.options.size());
|
||||
if (FFlag::LuauCloneCyclicUnions)
|
||||
{
|
||||
TypeId result = dest.addType(FreeType{nullptr});
|
||||
seenTypes[typeId] = result;
|
||||
|
||||
for (TypeId ty : t.options)
|
||||
options.push_back(clone(ty, dest, cloneState));
|
||||
std::vector<TypeId> options;
|
||||
options.reserve(t.options.size());
|
||||
|
||||
TypeId result = dest.addType(UnionType{std::move(options)});
|
||||
seenTypes[typeId] = result;
|
||||
for (TypeId ty : t.options)
|
||||
options.push_back(clone(ty, dest, cloneState));
|
||||
|
||||
asMutable(result)->ty.emplace<UnionType>(std::move(options));
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<TypeId> options;
|
||||
options.reserve(t.options.size());
|
||||
|
||||
for (TypeId ty : t.options)
|
||||
options.push_back(clone(ty, dest, cloneState));
|
||||
|
||||
TypeId result = dest.addType(UnionType{std::move(options)});
|
||||
seenTypes[typeId] = result;
|
||||
}
|
||||
}
|
||||
|
||||
void TypeCloner::operator()(const IntersectionType& t)
|
||||
|
|
|
@ -13,6 +13,9 @@
|
|||
#include "Luau/Scope.h"
|
||||
#include "Luau/TypeUtils.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypeFamily.h"
|
||||
#include "Luau/Simplify.h"
|
||||
#include "Luau/VisitType.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
|
@ -195,8 +198,23 @@ struct RefinementPartition
|
|||
|
||||
using RefinementContext = std::unordered_map<DefId, RefinementPartition>;
|
||||
|
||||
static void unionRefinements(const RefinementContext& lhs, const RefinementContext& rhs, RefinementContext& dest, NotNull<TypeArena> arena)
|
||||
static void unionRefinements(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, const RefinementContext& lhs, const RefinementContext& rhs,
|
||||
RefinementContext& dest, std::vector<ConstraintV>* constraints)
|
||||
{
|
||||
const auto intersect = [&](const std::vector<TypeId>& types) {
|
||||
if (1 == types.size())
|
||||
return types[0];
|
||||
else if (2 == types.size())
|
||||
{
|
||||
// TODO: It may be advantageous to create a RefineConstraint here when there are blockedTypes.
|
||||
SimplifyResult sr = simplifyIntersection(builtinTypes, arena, types[0], types[1]);
|
||||
if (sr.blockedTypes.empty())
|
||||
return sr.result;
|
||||
}
|
||||
|
||||
return arena->addType(IntersectionType{types});
|
||||
};
|
||||
|
||||
for (auto& [def, partition] : lhs)
|
||||
{
|
||||
auto rhsIt = rhs.find(def);
|
||||
|
@ -206,55 +224,54 @@ static void unionRefinements(const RefinementContext& lhs, const RefinementConte
|
|||
LUAU_ASSERT(!partition.discriminantTypes.empty());
|
||||
LUAU_ASSERT(!rhsIt->second.discriminantTypes.empty());
|
||||
|
||||
TypeId leftDiscriminantTy =
|
||||
partition.discriminantTypes.size() == 1 ? partition.discriminantTypes[0] : arena->addType(IntersectionType{partition.discriminantTypes});
|
||||
TypeId leftDiscriminantTy = partition.discriminantTypes.size() == 1 ? partition.discriminantTypes[0] : intersect(partition.discriminantTypes);
|
||||
|
||||
TypeId rightDiscriminantTy = rhsIt->second.discriminantTypes.size() == 1 ? rhsIt->second.discriminantTypes[0]
|
||||
: arena->addType(IntersectionType{rhsIt->second.discriminantTypes});
|
||||
TypeId rightDiscriminantTy =
|
||||
rhsIt->second.discriminantTypes.size() == 1 ? rhsIt->second.discriminantTypes[0] : intersect(rhsIt->second.discriminantTypes);
|
||||
|
||||
dest[def].discriminantTypes.push_back(arena->addType(UnionType{{leftDiscriminantTy, rightDiscriminantTy}}));
|
||||
dest[def].discriminantTypes.push_back(simplifyUnion(builtinTypes, arena, leftDiscriminantTy, rightDiscriminantTy).result);
|
||||
dest[def].shouldAppendNilType |= partition.shouldAppendNilType || rhsIt->second.shouldAppendNilType;
|
||||
}
|
||||
}
|
||||
|
||||
static void computeRefinement(const ScopePtr& scope, RefinementId refinement, RefinementContext* refis, bool sense, NotNull<TypeArena> arena, bool eq,
|
||||
std::vector<ConstraintV>* constraints)
|
||||
static void computeRefinement(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, const ScopePtr& scope, RefinementId refinement,
|
||||
RefinementContext* refis, bool sense, bool eq, std::vector<ConstraintV>* constraints)
|
||||
{
|
||||
if (!refinement)
|
||||
return;
|
||||
else if (auto variadic = get<Variadic>(refinement))
|
||||
{
|
||||
for (RefinementId refi : variadic->refinements)
|
||||
computeRefinement(scope, refi, refis, sense, arena, eq, constraints);
|
||||
computeRefinement(builtinTypes, arena, scope, refi, refis, sense, eq, constraints);
|
||||
}
|
||||
else if (auto negation = get<Negation>(refinement))
|
||||
return computeRefinement(scope, negation->refinement, refis, !sense, arena, eq, constraints);
|
||||
return computeRefinement(builtinTypes, arena, scope, negation->refinement, refis, !sense, eq, constraints);
|
||||
else if (auto conjunction = get<Conjunction>(refinement))
|
||||
{
|
||||
RefinementContext lhsRefis;
|
||||
RefinementContext rhsRefis;
|
||||
|
||||
computeRefinement(scope, conjunction->lhs, sense ? refis : &lhsRefis, sense, arena, eq, constraints);
|
||||
computeRefinement(scope, conjunction->rhs, sense ? refis : &rhsRefis, sense, arena, eq, constraints);
|
||||
computeRefinement(builtinTypes, arena, scope, conjunction->lhs, sense ? refis : &lhsRefis, sense, eq, constraints);
|
||||
computeRefinement(builtinTypes, arena, scope, conjunction->rhs, sense ? refis : &rhsRefis, sense, eq, constraints);
|
||||
|
||||
if (!sense)
|
||||
unionRefinements(lhsRefis, rhsRefis, *refis, arena);
|
||||
unionRefinements(builtinTypes, arena, lhsRefis, rhsRefis, *refis, constraints);
|
||||
}
|
||||
else if (auto disjunction = get<Disjunction>(refinement))
|
||||
{
|
||||
RefinementContext lhsRefis;
|
||||
RefinementContext rhsRefis;
|
||||
|
||||
computeRefinement(scope, disjunction->lhs, sense ? &lhsRefis : refis, sense, arena, eq, constraints);
|
||||
computeRefinement(scope, disjunction->rhs, sense ? &rhsRefis : refis, sense, arena, eq, constraints);
|
||||
computeRefinement(builtinTypes, arena, scope, disjunction->lhs, sense ? &lhsRefis : refis, sense, eq, constraints);
|
||||
computeRefinement(builtinTypes, arena, scope, disjunction->rhs, sense ? &rhsRefis : refis, sense, eq, constraints);
|
||||
|
||||
if (sense)
|
||||
unionRefinements(lhsRefis, rhsRefis, *refis, arena);
|
||||
unionRefinements(builtinTypes, arena, lhsRefis, rhsRefis, *refis, constraints);
|
||||
}
|
||||
else if (auto equivalence = get<Equivalence>(refinement))
|
||||
{
|
||||
computeRefinement(scope, equivalence->lhs, refis, sense, arena, true, constraints);
|
||||
computeRefinement(scope, equivalence->rhs, refis, sense, arena, true, constraints);
|
||||
computeRefinement(builtinTypes, arena, scope, equivalence->lhs, refis, sense, true, constraints);
|
||||
computeRefinement(builtinTypes, arena, scope, equivalence->rhs, refis, sense, true, constraints);
|
||||
}
|
||||
else if (auto proposition = get<Proposition>(refinement))
|
||||
{
|
||||
|
@ -300,6 +317,63 @@ static void computeRefinement(const ScopePtr& scope, RefinementId refinement, Re
|
|||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
/*
|
||||
* Constraint generation may be called upon to simplify an intersection or union
|
||||
* of types that are not sufficiently solved yet. We use
|
||||
* FindSimplificationBlockers to recognize these types and defer the
|
||||
* simplification until constraint solution.
|
||||
*/
|
||||
struct FindSimplificationBlockers : TypeOnceVisitor
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
bool visit(TypeId) override
|
||||
{
|
||||
return !found;
|
||||
}
|
||||
|
||||
bool visit(TypeId, const BlockedType&) override
|
||||
{
|
||||
found = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(TypeId, const FreeType&) override
|
||||
{
|
||||
found = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(TypeId, const PendingExpansionType&) override
|
||||
{
|
||||
found = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// We do not need to know anything at all about a function's argument or
|
||||
// return types in order to simplify it in an intersection or union.
|
||||
bool visit(TypeId, const FunctionType&) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(TypeId, const ClassType&) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
bool mustDeferIntersection(TypeId ty)
|
||||
{
|
||||
FindSimplificationBlockers bts;
|
||||
bts.traverse(ty);
|
||||
return bts.found;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void ConstraintGraphBuilder::applyRefinements(const ScopePtr& scope, Location location, RefinementId refinement)
|
||||
{
|
||||
if (!refinement)
|
||||
|
@ -307,7 +381,7 @@ void ConstraintGraphBuilder::applyRefinements(const ScopePtr& scope, Location lo
|
|||
|
||||
RefinementContext refinements;
|
||||
std::vector<ConstraintV> constraints;
|
||||
computeRefinement(scope, refinement, &refinements, /*sense*/ true, arena, /*eq*/ false, &constraints);
|
||||
computeRefinement(builtinTypes, arena, scope, refinement, &refinements, /*sense*/ true, /*eq*/ false, &constraints);
|
||||
|
||||
for (auto& [def, partition] : refinements)
|
||||
{
|
||||
|
@ -317,8 +391,24 @@ void ConstraintGraphBuilder::applyRefinements(const ScopePtr& scope, Location lo
|
|||
if (partition.shouldAppendNilType)
|
||||
ty = arena->addType(UnionType{{ty, builtinTypes->nilType}});
|
||||
|
||||
partition.discriminantTypes.push_back(ty);
|
||||
scope->dcrRefinements[def] = arena->addType(IntersectionType{std::move(partition.discriminantTypes)});
|
||||
// Intersect ty with every discriminant type. If either type is not
|
||||
// sufficiently solved, we queue the intersection up via an
|
||||
// IntersectConstraint.
|
||||
|
||||
for (TypeId dt : partition.discriminantTypes)
|
||||
{
|
||||
if (mustDeferIntersection(ty) || mustDeferIntersection(dt))
|
||||
{
|
||||
TypeId r = arena->addType(BlockedType{});
|
||||
addConstraint(scope, location, RefineConstraint{RefineConstraint::Intersection, r, ty, dt});
|
||||
|
||||
ty = r;
|
||||
}
|
||||
else
|
||||
ty = simplifyIntersection(builtinTypes, arena, ty, dt).result;
|
||||
}
|
||||
|
||||
scope->dcrRefinements[def] = ty;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -708,7 +798,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocalFun
|
|||
functionType = arena->addType(BlockedType{});
|
||||
scope->bindings[function->name] = Binding{functionType, function->name->location};
|
||||
|
||||
FunctionSignature sig = checkFunctionSignature(scope, function->func);
|
||||
FunctionSignature sig = checkFunctionSignature(scope, function->func, /* expectedType */ std::nullopt, function->name->location);
|
||||
sig.bodyScope->bindings[function->name] = Binding{sig.signature, function->func->location};
|
||||
|
||||
BreadcrumbId bc = dfg->getBreadcrumb(function->name);
|
||||
|
@ -741,10 +831,12 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction
|
|||
TypeId generalizedType = arena->addType(BlockedType{});
|
||||
|
||||
Checkpoint start = checkpoint(this);
|
||||
FunctionSignature sig = checkFunctionSignature(scope, function->func);
|
||||
FunctionSignature sig = checkFunctionSignature(scope, function->func, /* expectedType */ std::nullopt, function->name->location);
|
||||
|
||||
std::unordered_set<Constraint*> excludeList;
|
||||
|
||||
const NullableBreadcrumbId functionBreadcrumb = dfg->getBreadcrumb(function->name);
|
||||
|
||||
if (AstExprLocal* localName = function->name->as<AstExprLocal>())
|
||||
{
|
||||
std::optional<TypeId> existingFunctionTy = scope->lookup(localName->local);
|
||||
|
@ -759,6 +851,9 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction
|
|||
scope->bindings[localName->local] = Binding{generalizedType, localName->location};
|
||||
|
||||
sig.bodyScope->bindings[localName->local] = Binding{sig.signature, localName->location};
|
||||
|
||||
if (functionBreadcrumb)
|
||||
sig.bodyScope->dcrRefinements[functionBreadcrumb->def] = sig.signature;
|
||||
}
|
||||
else if (AstExprGlobal* globalName = function->name->as<AstExprGlobal>())
|
||||
{
|
||||
|
@ -769,6 +864,9 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction
|
|||
generalizedType = *existingFunctionTy;
|
||||
|
||||
sig.bodyScope->bindings[globalName->name] = Binding{sig.signature, globalName->location};
|
||||
|
||||
if (functionBreadcrumb)
|
||||
sig.bodyScope->dcrRefinements[functionBreadcrumb->def] = sig.signature;
|
||||
}
|
||||
else if (AstExprIndexName* indexName = function->name->as<AstExprIndexName>())
|
||||
{
|
||||
|
@ -795,8 +893,8 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction
|
|||
if (generalizedType == nullptr)
|
||||
ice->ice("generalizedType == nullptr", function->location);
|
||||
|
||||
if (NullableBreadcrumbId bc = dfg->getBreadcrumb(function->name))
|
||||
scope->dcrRefinements[bc->def] = generalizedType;
|
||||
if (functionBreadcrumb)
|
||||
scope->dcrRefinements[functionBreadcrumb->def] = generalizedType;
|
||||
|
||||
checkFunctionBody(sig.bodyScope, function->func);
|
||||
Checkpoint end = checkpoint(this);
|
||||
|
@ -1469,21 +1567,7 @@ Inference ConstraintGraphBuilder::check(
|
|||
else if (auto call = expr->as<AstExprCall>())
|
||||
result = flattenPack(scope, expr->location, checkPack(scope, call)); // TODO: needs predicates too
|
||||
else if (auto a = expr->as<AstExprFunction>())
|
||||
{
|
||||
Checkpoint startCheckpoint = checkpoint(this);
|
||||
FunctionSignature sig = checkFunctionSignature(scope, a, expectedType);
|
||||
checkFunctionBody(sig.bodyScope, a);
|
||||
Checkpoint endCheckpoint = checkpoint(this);
|
||||
|
||||
TypeId generalizedTy = arena->addType(BlockedType{});
|
||||
NotNull<Constraint> gc = addConstraint(sig.signatureScope, expr->location, GeneralizationConstraint{generalizedTy, sig.signature});
|
||||
|
||||
forEachConstraint(startCheckpoint, endCheckpoint, this, [gc](const ConstraintPtr& constraint) {
|
||||
gc->dependencies.emplace_back(constraint.get());
|
||||
});
|
||||
|
||||
result = Inference{generalizedTy};
|
||||
}
|
||||
result = check(scope, a, expectedType);
|
||||
else if (auto indexName = expr->as<AstExprIndexName>())
|
||||
result = check(scope, indexName);
|
||||
else if (auto indexExpr = expr->as<AstExprIndexExpr>())
|
||||
|
@ -1651,6 +1735,23 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIndexExpr*
|
|||
return Inference{result};
|
||||
}
|
||||
|
||||
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprFunction* func, std::optional<TypeId> expectedType)
|
||||
{
|
||||
Checkpoint startCheckpoint = checkpoint(this);
|
||||
FunctionSignature sig = checkFunctionSignature(scope, func, expectedType);
|
||||
checkFunctionBody(sig.bodyScope, func);
|
||||
Checkpoint endCheckpoint = checkpoint(this);
|
||||
|
||||
TypeId generalizedTy = arena->addType(BlockedType{});
|
||||
NotNull<Constraint> gc = addConstraint(sig.signatureScope, func->location, GeneralizationConstraint{generalizedTy, sig.signature});
|
||||
|
||||
forEachConstraint(startCheckpoint, endCheckpoint, this, [gc](const ConstraintPtr& constraint) {
|
||||
gc->dependencies.emplace_back(constraint.get());
|
||||
});
|
||||
|
||||
return Inference{generalizedTy};
|
||||
}
|
||||
|
||||
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprUnary* unary)
|
||||
{
|
||||
auto [operandType, refinement] = check(scope, unary->expr);
|
||||
|
@ -1667,6 +1768,17 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprBinary* bi
|
|||
{
|
||||
auto [leftType, rightType, refinement] = checkBinary(scope, binary, expectedType);
|
||||
|
||||
if (binary->op == AstExprBinary::Op::Add)
|
||||
{
|
||||
TypeId resultType = arena->addType(TypeFamilyInstanceType{
|
||||
NotNull{&kBuiltinTypeFamilies.addFamily},
|
||||
{leftType, rightType},
|
||||
{},
|
||||
});
|
||||
addConstraint(scope, binary->location, ReduceConstraint{resultType});
|
||||
return Inference{resultType, std::move(refinement)};
|
||||
}
|
||||
|
||||
TypeId resultType = arena->addType(BlockedType{});
|
||||
addConstraint(scope, binary->location,
|
||||
BinaryConstraint{binary->op, leftType, rightType, resultType, binary, &module->astOriginalCallTypes, &module->astOverloadResolvedTypes});
|
||||
|
@ -1686,7 +1798,7 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIfElse* if
|
|||
applyRefinements(elseScope, ifElse->falseExpr->location, refinementArena.negation(refinement));
|
||||
TypeId elseType = check(elseScope, ifElse->falseExpr, ValueContext::RValue, expectedType).ty;
|
||||
|
||||
return Inference{expectedType ? *expectedType : arena->addType(UnionType{{thenType, elseType}})};
|
||||
return Inference{expectedType ? *expectedType : simplifyUnion(builtinTypes, arena, thenType, elseType).result};
|
||||
}
|
||||
|
||||
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprTypeAssertion* typeAssert)
|
||||
|
@ -1902,6 +2014,8 @@ TypeId ConstraintGraphBuilder::checkLValue(const ScopePtr& scope, AstExpr* expr)
|
|||
}
|
||||
else if (auto indexExpr = e->as<AstExprIndexExpr>())
|
||||
{
|
||||
// We need to populate the type for the index value
|
||||
check(scope, indexExpr->index, ValueContext::RValue);
|
||||
if (auto strIndex = indexExpr->index->as<AstExprConstantString>())
|
||||
{
|
||||
segments.push_back(std::string(strIndex->value.data, strIndex->value.size));
|
||||
|
@ -2018,12 +2132,12 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprTable* exp
|
|||
else
|
||||
{
|
||||
expectedValueType = arena->addType(BlockedType{});
|
||||
addConstraint(scope, item.value->location, HasPropConstraint{*expectedValueType, *expectedType, stringKey->value.data});
|
||||
addConstraint(scope, item.value->location,
|
||||
HasPropConstraint{*expectedValueType, *expectedType, stringKey->value.data, /*suppressSimplification*/ true});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// We'll resolve the expected index result type here with the following priority:
|
||||
// 1. Record table types - in which key, value pairs must be handled on a k,v pair basis.
|
||||
// In this case, the above if-statement will populate expectedValueType
|
||||
|
@ -2079,7 +2193,7 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprTable* exp
|
|||
}
|
||||
|
||||
ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionSignature(
|
||||
const ScopePtr& parent, AstExprFunction* fn, std::optional<TypeId> expectedType)
|
||||
const ScopePtr& parent, AstExprFunction* fn, std::optional<TypeId> expectedType, std::optional<Location> originalName)
|
||||
{
|
||||
ScopePtr signatureScope = nullptr;
|
||||
ScopePtr bodyScope = nullptr;
|
||||
|
@ -2235,12 +2349,18 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS
|
|||
// TODO: Preserve argument names in the function's type.
|
||||
|
||||
FunctionType actualFunction{TypeLevel{}, parent.get(), arena->addTypePack(argTypes, varargPack), returnType};
|
||||
actualFunction.hasNoGenerics = !hasGenerics;
|
||||
actualFunction.generics = std::move(genericTypes);
|
||||
actualFunction.genericPacks = std::move(genericTypePacks);
|
||||
actualFunction.argNames = std::move(argNames);
|
||||
actualFunction.hasSelf = fn->self != nullptr;
|
||||
|
||||
FunctionDefinition defn;
|
||||
defn.definitionModuleName = module->name;
|
||||
defn.definitionLocation = fn->location;
|
||||
defn.varargLocation = fn->vararg ? std::make_optional(fn->varargLocation) : std::nullopt;
|
||||
defn.originalNameLocation = originalName.value_or(Location(fn->location.begin, 0));
|
||||
actualFunction.definition = defn;
|
||||
|
||||
TypeId actualFunctionType = arena->addType(std::move(actualFunction));
|
||||
LUAU_ASSERT(actualFunctionType);
|
||||
module->astTypes[fn] = actualFunctionType;
|
||||
|
@ -2283,6 +2403,7 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b
|
|||
if (ref->parameters.size != 1 || !ref->parameters.data[0].type)
|
||||
{
|
||||
reportError(ty->location, GenericError{"_luau_print requires one generic parameter"});
|
||||
module->astResolvedTypes[ty] = builtinTypes->errorRecoveryType();
|
||||
return builtinTypes->errorRecoveryType();
|
||||
}
|
||||
else
|
||||
|
@ -2420,7 +2541,6 @@ TypeId ConstraintGraphBuilder::resolveType(const ScopePtr& scope, AstType* ty, b
|
|||
|
||||
// This replicates the behavior of the appropriate FunctionType
|
||||
// constructors.
|
||||
ftv.hasNoGenerics = !hasGenerics;
|
||||
ftv.generics = std::move(genericTypes);
|
||||
ftv.genericPacks = std::move(genericTypePacks);
|
||||
|
||||
|
|
|
@ -11,12 +11,13 @@
|
|||
#include "Luau/Metamethods.h"
|
||||
#include "Luau/ModuleResolver.h"
|
||||
#include "Luau/Quantify.h"
|
||||
#include "Luau/Simplify.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/TypeUtils.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypeFamily.h"
|
||||
#include "Luau/TypeUtils.h"
|
||||
#include "Luau/Unifier.h"
|
||||
#include "Luau/VisitType.h"
|
||||
#include "Luau/TypeFamily.h"
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolver, false);
|
||||
LUAU_FASTFLAG(LuauRequirePathTrueModuleName)
|
||||
|
@ -73,7 +74,7 @@ static std::pair<std::vector<TypeId>, std::vector<TypePackId>> saturateArguments
|
|||
// mutually exclusive with the type pack -> type conversion we do below:
|
||||
// extraTypes will only have elements in it if we have more types than we
|
||||
// have parameter slots for them to go into.
|
||||
if (!extraTypes.empty())
|
||||
if (!extraTypes.empty() && !fn.typePackParams.empty())
|
||||
{
|
||||
saturatedPackArguments.push_back(arena->addTypePack(extraTypes));
|
||||
}
|
||||
|
@ -89,7 +90,7 @@ static std::pair<std::vector<TypeId>, std::vector<TypePackId>> saturateArguments
|
|||
{
|
||||
saturatedTypeArguments.push_back(*first(tp));
|
||||
}
|
||||
else
|
||||
else if (saturatedPackArguments.size() < fn.typePackParams.size())
|
||||
{
|
||||
saturatedPackArguments.push_back(tp);
|
||||
}
|
||||
|
@ -426,7 +427,9 @@ void ConstraintSolver::finalizeModule()
|
|||
rootScope->returnType = builtinTypes->errorTypePack;
|
||||
}
|
||||
else
|
||||
rootScope->returnType = *returnType;
|
||||
{
|
||||
rootScope->returnType = anyifyModuleReturnTypePackGenerics(*returnType);
|
||||
}
|
||||
}
|
||||
|
||||
bool ConstraintSolver::tryDispatch(NotNull<const Constraint> constraint, bool force)
|
||||
|
@ -468,6 +471,8 @@ bool ConstraintSolver::tryDispatch(NotNull<const Constraint> constraint, bool fo
|
|||
success = tryDispatch(*sottc, constraint);
|
||||
else if (auto uc = get<UnpackConstraint>(*constraint))
|
||||
success = tryDispatch(*uc, constraint);
|
||||
else if (auto rc = get<RefineConstraint>(*constraint))
|
||||
success = tryDispatch(*rc, constraint, force);
|
||||
else if (auto rc = get<ReduceConstraint>(*constraint))
|
||||
success = tryDispatch(*rc, constraint, force);
|
||||
else if (auto rpc = get<ReducePackConstraint>(*constraint))
|
||||
|
@ -541,15 +546,25 @@ bool ConstraintSolver::tryDispatch(const InstantiationConstraint& c, NotNull<con
|
|||
if (isBlocked(c.superType))
|
||||
return block(c.superType, constraint);
|
||||
|
||||
if (!recursiveBlock(c.superType, constraint))
|
||||
if (!blockOnPendingTypes(c.superType, constraint))
|
||||
return false;
|
||||
|
||||
Instantiation inst(TxnLog::empty(), arena, TypeLevel{}, constraint->scope);
|
||||
|
||||
std::optional<TypeId> instantiated = inst.substitute(c.superType);
|
||||
LUAU_ASSERT(instantiated); // TODO FIXME HANDLE THIS
|
||||
|
||||
LUAU_ASSERT(get<BlockedType>(c.subType));
|
||||
|
||||
if (!instantiated.has_value())
|
||||
{
|
||||
reportError(UnificationTooComplex{}, constraint->location);
|
||||
|
||||
asMutable(c.subType)->ty.emplace<BoundType>(errorRecoveryType());
|
||||
unblock(c.subType);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
asMutable(c.subType)->ty.emplace<BoundType>(*instantiated);
|
||||
|
||||
InstantiationQueuer queuer{constraint->scope, constraint->location, this};
|
||||
|
@ -759,9 +774,11 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNull<const Cons
|
|||
case AstExprBinary::Op::Div:
|
||||
case AstExprBinary::Op::Pow:
|
||||
case AstExprBinary::Op::Mod:
|
||||
{
|
||||
const NormalizedType* normLeftTy = normalizer->normalize(leftType);
|
||||
if (hasTypeInIntersection<FreeType>(leftType) && force)
|
||||
asMutable(leftType)->ty.emplace<BoundType>(anyPresent ? builtinTypes->anyType : builtinTypes->numberType);
|
||||
if (isNumber(leftType))
|
||||
if (normLeftTy && normLeftTy->isNumber())
|
||||
{
|
||||
unify(leftType, rightType, constraint->scope);
|
||||
asMutable(resultType)->ty.emplace<BoundType>(anyPresent ? builtinTypes->anyType : leftType);
|
||||
|
@ -770,6 +787,7 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNull<const Cons
|
|||
}
|
||||
|
||||
break;
|
||||
}
|
||||
// For concatenation, if the LHS is a string, the RHS must be a string as
|
||||
// well. The result will also be a string.
|
||||
case AstExprBinary::Op::Concat:
|
||||
|
@ -809,9 +827,9 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNull<const Cons
|
|||
// truthy.
|
||||
case AstExprBinary::Op::And:
|
||||
{
|
||||
TypeId leftFilteredTy = arena->addType(IntersectionType{{builtinTypes->falsyType, leftType}});
|
||||
TypeId leftFilteredTy = simplifyIntersection(builtinTypes, arena, leftType, builtinTypes->falsyType).result;
|
||||
|
||||
asMutable(resultType)->ty.emplace<BoundType>(arena->addType(UnionType{{leftFilteredTy, rightType}}));
|
||||
asMutable(resultType)->ty.emplace<BoundType>(simplifyUnion(builtinTypes, arena, rightType, leftFilteredTy).result);
|
||||
unblock(resultType);
|
||||
return true;
|
||||
}
|
||||
|
@ -819,9 +837,9 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNull<const Cons
|
|||
// LHS is falsey.
|
||||
case AstExprBinary::Op::Or:
|
||||
{
|
||||
TypeId leftFilteredTy = arena->addType(IntersectionType{{builtinTypes->truthyType, leftType}});
|
||||
TypeId leftFilteredTy = simplifyIntersection(builtinTypes, arena, leftType, builtinTypes->truthyType).result;
|
||||
|
||||
asMutable(resultType)->ty.emplace<BoundType>(arena->addType(UnionType{{leftFilteredTy, rightType}}));
|
||||
asMutable(resultType)->ty.emplace<BoundType>(simplifyUnion(builtinTypes, arena, rightType, leftFilteredTy).result);
|
||||
unblock(resultType);
|
||||
return true;
|
||||
}
|
||||
|
@ -1266,7 +1284,12 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
|
|||
overload = follow(overload);
|
||||
|
||||
std::optional<TypeId> instantiated = inst.substitute(overload);
|
||||
LUAU_ASSERT(instantiated); // TODO FIXME HANDLE THIS
|
||||
|
||||
if (!instantiated.has_value())
|
||||
{
|
||||
reportError(UnificationTooComplex{}, constraint->location);
|
||||
return true;
|
||||
}
|
||||
|
||||
Unifier u{normalizer, Mode::Strict, constraint->scope, Location{}, Covariant};
|
||||
u.enableScopeTests();
|
||||
|
@ -1374,7 +1397,7 @@ bool ConstraintSolver::tryDispatch(const HasPropConstraint& c, NotNull<const Con
|
|||
return true;
|
||||
}
|
||||
|
||||
auto [blocked, result] = lookupTableProp(subjectType, c.prop);
|
||||
auto [blocked, result] = lookupTableProp(subjectType, c.prop, c.suppressSimplification);
|
||||
if (!blocked.empty())
|
||||
{
|
||||
for (TypeId blocked : blocked)
|
||||
|
@ -1632,7 +1655,9 @@ bool ConstraintSolver::tryDispatch(const SingletonOrTopTypeConstraint& c, NotNul
|
|||
else if (!c.negated && get<SingletonType>(followed))
|
||||
*asMutable(c.resultType) = BoundType{c.discriminantType};
|
||||
else
|
||||
*asMutable(c.resultType) = BoundType{builtinTypes->unknownType};
|
||||
*asMutable(c.resultType) = BoundType{builtinTypes->anyType};
|
||||
|
||||
unblock(c.resultType);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -1700,10 +1725,131 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull<const Cons
|
|||
return true;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
/*
|
||||
* Search for types that prevent us from being ready to dispatch a particular
|
||||
* RefineConstraint.
|
||||
*/
|
||||
struct FindRefineConstraintBlockers : TypeOnceVisitor
|
||||
{
|
||||
std::unordered_set<TypeId> found;
|
||||
bool visit(TypeId ty, const BlockedType&) override
|
||||
{
|
||||
found.insert(ty);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const PendingExpansionType&) override
|
||||
{
|
||||
found.insert(ty);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
static bool isNegatedAny(TypeId ty)
|
||||
{
|
||||
ty = follow(ty);
|
||||
const NegationType* nt = get<NegationType>(ty);
|
||||
if (!nt)
|
||||
return false;
|
||||
TypeId negatedTy = follow(nt->ty);
|
||||
return bool(get<AnyType>(negatedTy));
|
||||
}
|
||||
|
||||
bool ConstraintSolver::tryDispatch(const RefineConstraint& c, NotNull<const Constraint> constraint, bool force)
|
||||
{
|
||||
if (isBlocked(c.discriminant))
|
||||
return block(c.discriminant, constraint);
|
||||
|
||||
FindRefineConstraintBlockers fbt;
|
||||
fbt.traverse(c.discriminant);
|
||||
|
||||
if (!fbt.found.empty())
|
||||
{
|
||||
bool foundOne = false;
|
||||
|
||||
for (TypeId blocked : fbt.found)
|
||||
{
|
||||
if (blocked == c.type)
|
||||
continue;
|
||||
|
||||
block(blocked, constraint);
|
||||
foundOne = true;
|
||||
}
|
||||
|
||||
if (foundOne)
|
||||
return false;
|
||||
}
|
||||
|
||||
/* HACK: Refinements sometimes produce a type T & ~any under the assumption
|
||||
* that ~any is the same as any. This is so so weird, but refinements needs
|
||||
* some way to say "I may refine this, but I'm not sure."
|
||||
*
|
||||
* It does this by refining on a blocked type and deferring the decision
|
||||
* until it is unblocked.
|
||||
*
|
||||
* Refinements also get negated, so we wind up with types like T & ~*blocked*
|
||||
*
|
||||
* We need to treat T & ~any as T in this case.
|
||||
*/
|
||||
|
||||
if (c.mode == RefineConstraint::Intersection && isNegatedAny(c.discriminant))
|
||||
{
|
||||
asMutable(c.resultType)->ty.emplace<BoundType>(c.type);
|
||||
unblock(c.resultType);
|
||||
return true;
|
||||
}
|
||||
|
||||
const TypeId type = follow(c.type);
|
||||
|
||||
LUAU_ASSERT(get<BlockedType>(c.resultType));
|
||||
|
||||
if (type == c.resultType)
|
||||
{
|
||||
/*
|
||||
* Sometimes, we get a constraint of the form
|
||||
*
|
||||
* *blocked-N* ~ refine *blocked-N* & U
|
||||
*
|
||||
* The constraint essentially states that a particular type is a
|
||||
* refinement of itself. This is weird and I think vacuous.
|
||||
*
|
||||
* I *believe* it is safe to replace the result with a fresh type that
|
||||
* is constrained by U. We effect this by minting a fresh type for the
|
||||
* result when U = any, else we bind the result to whatever discriminant
|
||||
* was offered.
|
||||
*/
|
||||
if (get<AnyType>(follow(c.discriminant)))
|
||||
asMutable(c.resultType)->ty.emplace<FreeType>(constraint->scope);
|
||||
else
|
||||
asMutable(c.resultType)->ty.emplace<BoundType>(c.discriminant);
|
||||
|
||||
unblock(c.resultType);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto [result, blockedTypes] = c.mode == RefineConstraint::Intersection ? simplifyIntersection(builtinTypes, NotNull{arena}, type, c.discriminant)
|
||||
: simplifyUnion(builtinTypes, NotNull{arena}, type, c.discriminant);
|
||||
|
||||
if (!force && !blockedTypes.empty())
|
||||
return block(blockedTypes, constraint);
|
||||
|
||||
asMutable(c.resultType)->ty.emplace<BoundType>(result);
|
||||
|
||||
unblock(c.resultType);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConstraintSolver::tryDispatch(const ReduceConstraint& c, NotNull<const Constraint> constraint, bool force)
|
||||
{
|
||||
TypeId ty = follow(c.ty);
|
||||
FamilyGraphReductionResult result = reduceFamilies(ty, constraint->location, NotNull{arena}, builtinTypes, nullptr, force);
|
||||
FamilyGraphReductionResult result =
|
||||
reduceFamilies(ty, constraint->location, NotNull{arena}, builtinTypes, constraint->scope, normalizer, nullptr, force);
|
||||
|
||||
for (TypeId r : result.reducedTypes)
|
||||
unblock(r);
|
||||
|
@ -1726,7 +1872,8 @@ bool ConstraintSolver::tryDispatch(const ReduceConstraint& c, NotNull<const Cons
|
|||
bool ConstraintSolver::tryDispatch(const ReducePackConstraint& c, NotNull<const Constraint> constraint, bool force)
|
||||
{
|
||||
TypePackId tp = follow(c.tp);
|
||||
FamilyGraphReductionResult result = reduceFamilies(tp, constraint->location, NotNull{arena}, builtinTypes, nullptr, force);
|
||||
FamilyGraphReductionResult result =
|
||||
reduceFamilies(tp, constraint->location, NotNull{arena}, builtinTypes, constraint->scope, normalizer, nullptr, force |