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:
Andy Friesen 2023-05-19 12:37:30 -07:00 committed by GitHub
parent da0458bf6e
commit 721f6e10fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
85 changed files with 4058 additions and 4494 deletions

View File

@ -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
{

View File

@ -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.

View File

@ -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;
};

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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)

View File

@ -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);

View File

@ -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