Skip to content

Commit

Permalink
Sync to upstream/release/619 (#1218)
Browse files Browse the repository at this point in the history
# What's Changed

## New Type Solver
- Many fixes to crashes, assertions, and hangs
- Binary type family aliases now have a default parameter
- Added a debug check for unsolved types escaping the constraint solver
- Overloaded functions are no longer inferred
- Unification creates additional subtyping constraints for blocked types
- Attempt to guess the result type for type families that are too large
to resolve timely

## Native Code Generation
- Fixed `IrCmd::CHECK_TRUTHY` lowering in a specific case
- Detailed compilation errors are now supported
- More work on the new allocator

---

# Internal Contributors

Co-authored-by: Aaron Weiss <aaronweiss@roblox.com>
Co-authored-by: Alexander McCord <amccord@roblox.com>
Co-authored-by: Andy Friesen <afriesen@roblox.com>
Co-authored-by: James McNellis <jmcnellis@roblox.com>
Co-authored-by: Lily Brown <lbrown@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
Co-authored-by: Vighnesh Vijay <vvijay@roblox.com>
  • Loading branch information
7 people committed Mar 30, 2024
1 parent bac8511 commit 47ad768
Show file tree
Hide file tree
Showing 59 changed files with 1,846 additions and 619 deletions.
19 changes: 16 additions & 3 deletions Analysis/include/Luau/Constraint.h
Expand Up @@ -224,7 +224,6 @@ struct HasIndexerConstraint
// If the table is a free or unsealed table, we augment it with a new indexer.
struct SetIndexerConstraint
{
TypeId resultType;
TypeId subjectType;
TypeId indexType;
TypeId propType;
Expand Down Expand Up @@ -256,6 +255,20 @@ struct UnpackConstraint
bool resultIsLValue = false;
};

// resultType ~ unpack sourceType
//
// The same as UnpackConstraint, but specialized for a pair of types as opposed to packs.
struct Unpack1Constraint
{
TypeId resultType;
TypeId sourceType;

// UnpackConstraint is sometimes used to resolve the types of assignments.
// When this is the case, any LocalTypes in resultPack can have their
// domains extended by the corresponding type from sourcePack.
bool resultIsLValue = false;
};

// resultType ~ T0 op T1 op ... op TN
//
// op is either union or intersection. If any of the input types are blocked,
Expand Down Expand Up @@ -290,8 +303,8 @@ struct ReducePackConstraint

using ConstraintV = Variant<SubtypeConstraint, PackSubtypeConstraint, GeneralizationConstraint, InstantiationConstraint, IterableConstraint,
NameConstraint, TypeAliasExpansionConstraint, FunctionCallConstraint, FunctionCheckConstraint, PrimitiveTypeConstraint, HasPropConstraint,
SetPropConstraint, HasIndexerConstraint, SetIndexerConstraint, SingletonOrTopTypeConstraint, UnpackConstraint, SetOpConstraint, ReduceConstraint, ReducePackConstraint,
EqualityConstraint>;
SetPropConstraint, HasIndexerConstraint, SetIndexerConstraint, SingletonOrTopTypeConstraint, UnpackConstraint, Unpack1Constraint,
SetOpConstraint, ReduceConstraint, ReducePackConstraint, EqualityConstraint>;

struct Constraint
{
Expand Down
28 changes: 18 additions & 10 deletions Analysis/include/Luau/ConstraintGenerator.h
Expand Up @@ -95,6 +95,10 @@ struct ConstraintGenerator
// will enqueue them during solving.
std::vector<ConstraintPtr> unqueuedConstraints;

// Type family instances created by the generator. This is used to ensure
// that these instances are reduced fully by the solver.
std::vector<TypeId> familyInstances;

// The private scope of type aliases for which the type parameters belong to.
DenseHashMap<const AstStatTypeAlias*, ScopePtr> astTypeAliasDefiningScopes{nullptr};

Expand Down Expand Up @@ -254,16 +258,18 @@ struct ConstraintGenerator
Inference check(const ScopePtr& scope, AstExprTable* expr, std::optional<TypeId> expectedType);
std::tuple<TypeId, TypeId, RefinementId> checkBinary(const ScopePtr& scope, AstExprBinary* binary, std::optional<TypeId> expectedType);

/**
* Generate constraints to assign assignedTy to the expression expr
* @returns the type of the expression. This may or may not be assignedTy itself.
*/
std::optional<TypeId> checkLValue(const ScopePtr& scope, AstExpr* expr, TypeId assignedTy, bool transform);
std::optional<TypeId> checkLValue(const ScopePtr& scope, AstExprLocal* local, TypeId assignedTy, bool transform);
std::optional<TypeId> checkLValue(const ScopePtr& scope, AstExprGlobal* global, TypeId assignedTy);
std::optional<TypeId> checkLValue(const ScopePtr& scope, AstExprIndexName* indexName, TypeId assignedTy);
std::optional<TypeId> checkLValue(const ScopePtr& scope, AstExprIndexExpr* indexExpr, TypeId assignedTy);
TypeId updateProperty(const ScopePtr& scope, AstExpr* expr, TypeId assignedTy);
struct LValueBounds
{
std::optional<TypeId> annotationTy;
std::optional<TypeId> assignedTy;
};

LValueBounds checkLValue(const ScopePtr& scope, AstExpr* expr, bool transform);
LValueBounds checkLValue(const ScopePtr& scope, AstExprLocal* local, bool transform);
LValueBounds checkLValue(const ScopePtr& scope, AstExprGlobal* global);
LValueBounds checkLValue(const ScopePtr& scope, AstExprIndexName* indexName);
LValueBounds checkLValue(const ScopePtr& scope, AstExprIndexExpr* indexExpr);
LValueBounds updateProperty(const ScopePtr& scope, AstExpr* expr);

struct FunctionSignature
{
Expand Down Expand Up @@ -370,6 +376,8 @@ struct ConstraintGenerator
* yields a vector of size 1, with value: [number | string]
*/
std::vector<std::optional<TypeId>> getExpectedCallTypesForFunctionOverloads(const TypeId fnType);

TypeId createFamilyInstance(TypeFamilyInstanceType instance, const ScopePtr& scope, Location location);
};

/** Borrow a vector of pointers from a vector of owning pointers to constraints.
Expand Down
35 changes: 19 additions & 16 deletions Analysis/include/Luau/ConstraintSolver.h
Expand Up @@ -25,6 +25,8 @@ enum class ValueContext;

struct DcrLogger;

class AstExpr;

// TypeId, TypePackId, or Constraint*. It is impossible to know which, but we
// never dereference this pointer.
using BlockedConstraintId = Variant<TypeId, TypePackId, const Constraint*>;
Expand Down Expand Up @@ -73,6 +75,9 @@ struct ConstraintSolver
// A constraint can be both blocked and unsolved, for instance.
std::vector<NotNull<const Constraint>> unsolvedConstraints;

// This is a set of type families that need to be reduced after all constraints have been dispatched.
DenseHashSet<TypeId> familyInstances{nullptr};

// A mapping of constraint pointer to how many things the constraint is
// blocked on. Can be empty or 0 for constraints that are not blocked on
// anything.
Expand Down Expand Up @@ -130,14 +135,23 @@ struct ConstraintSolver
bool tryDispatch(const FunctionCheckConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const PrimitiveTypeConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const HasPropConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const SetPropConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const SetPropConstraint& c, NotNull<const Constraint> constraint);

bool tryDispatchHasIndexer(int& recursionDepth, NotNull<const Constraint> constraint, TypeId subjectType, TypeId indexType, TypeId resultType);
bool tryDispatch(const HasIndexerConstraint& c, NotNull<const Constraint> constraint);

/// (dispatched, found) where
/// - dispatched: this constraint can be considered having dispatched.
/// - found: true if adding an indexer for a particular type was allowed.
std::pair<bool, bool> tryDispatchSetIndexer(NotNull<const Constraint> constraint, TypeId subjectType, TypeId indexType, TypeId propType, bool expandFreeTypeBounds);
bool tryDispatch(const SetIndexerConstraint& c, NotNull<const Constraint> constraint, bool force);

bool tryDispatch(const SingletonOrTopTypeConstraint& c, NotNull<const Constraint> constraint);

bool tryDispatchUnpack1(NotNull<const Constraint> constraint, TypeId resultType, TypeId sourceType, bool resultIsLValue);
bool tryDispatch(const UnpackConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const Unpack1Constraint& c, NotNull<const Constraint> constraint);

bool tryDispatch(const SetOpConstraint& 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);
Expand All @@ -151,10 +165,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, ValueContext context, bool inConditional = false, bool suppressSimplification = false);
std::pair<std::vector<TypeId>, std::optional<TypeId>> lookupTableProp(
TypeId subjectType, const std::string& propName, ValueContext context, bool inConditional, bool suppressSimplification, DenseHashSet<TypeId>& seen);
std::pair<std::vector<TypeId>, std::optional<TypeId>> lookupTableProp(NotNull<const Constraint> constraint, TypeId subjectType,
const std::string& propName, ValueContext context, bool inConditional = false, bool suppressSimplification = false);
std::pair<std::vector<TypeId>, std::optional<TypeId>> lookupTableProp(NotNull<const Constraint> constraint, TypeId subjectType,
const std::string& propName, ValueContext context, bool inConditional, bool suppressSimplification, DenseHashSet<TypeId>& seen);

void block(NotNull<const Constraint> target, NotNull<const Constraint> constraint);
/**
Expand Down Expand Up @@ -242,17 +256,6 @@ struct ConstraintSolver
*/
bool hasUnresolvedConstraints(TypeId ty);

/**
* Creates a new Unifier and performs a single unification operation.
*
* @param subType the sub-type to unify.
* @param superType the super-type to unify.
* @returns true if the unification succeeded. False if the unification was
* too complex.
*/
template <typename TID>
bool unify(NotNull<Scope> scope, Location location, TID subType, TID superType);

/** Attempts to unify subTy with superTy. If doing so would require unifying
* BlockedTypes, fail and block the constraint on those BlockedTypes.
*
Expand Down
1 change: 1 addition & 0 deletions Analysis/include/Luau/Type.h
Expand Up @@ -299,6 +299,7 @@ using MagicFunction = std::function<std::optional<WithPredicate<TypePackId>>(
struct MagicFunctionCallContext
{
NotNull<struct ConstraintSolver> solver;
NotNull<const Constraint> constraint;
const class AstExprCall* callSite;
TypePackId arguments;
TypePackId result;
Expand Down
6 changes: 5 additions & 1 deletion Analysis/include/Luau/TypeFamilyReductionGuesser.h
Expand Up @@ -13,6 +13,7 @@
#include "Luau/TypeFwd.h"
#include "Luau/VisitType.h"
#include "Luau/NotNull.h"
#include "TypeArena.h"

namespace Luau
{
Expand Down Expand Up @@ -42,11 +43,14 @@ struct TypeFamilyReductionGuesser
DenseHashSet<TypeId> cyclicInstances{nullptr};

// Utilities
NotNull<TypeArena> arena;
NotNull<BuiltinTypes> builtins;
NotNull<Normalizer> normalizer;

TypeFamilyReductionGuesser(NotNull<BuiltinTypes> builtins, NotNull<Normalizer> normalizer);
TypeFamilyReductionGuesser(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtins, NotNull<Normalizer> normalizer);

std::optional<TypeId> guess(TypeId typ);
std::optional<TypePackId> guess(TypePackId typ);
TypeFamilyReductionGuessResult guessTypeFamilyReductionForFunction(const AstExprFunction& expr, const FunctionType* ftv, TypeId retTy);

private:
Expand Down
6 changes: 5 additions & 1 deletion Analysis/include/Luau/Unifier2.h
Expand Up @@ -2,11 +2,12 @@

#pragma once

#include "Luau/Constraint.h"
#include "Luau/DenseHash.h"
#include "Luau/NotNull.h"
#include "Luau/TypePairHash.h"
#include "Luau/TypeCheckLimits.h"
#include "Luau/TypeFwd.h"
#include "Luau/TypePairHash.h"

#include <optional>
#include <vector>
Expand Down Expand Up @@ -46,6 +47,8 @@ struct Unifier2
int recursionCount = 0;
int recursionLimit = 0;

std::vector<ConstraintV> incompleteSubtypes;

Unifier2(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<Scope> scope, NotNull<InternalErrorReporter> ice);

/** Attempt to commit the subtype relation subTy <: superTy to the type
Expand All @@ -61,6 +64,7 @@ struct Unifier2
* free TypePack to another and encounter an occurs check violation.
*/
bool unify(TypeId subTy, TypeId superTy);
bool unifyFreeWithType(TypeId subTy, TypeId superTy);
bool unify(const LocalType* subTy, TypeId superFn);
bool unify(TypeId subTy, const FunctionType* superFn);
bool unify(const UnionType* subUnion, TypeId superTy);
Expand Down
8 changes: 8 additions & 0 deletions Analysis/include/Luau/VisitType.h
Expand Up @@ -64,6 +64,9 @@ inline void unsee(DenseHashSet<void*>& seen, const void* tv)

} // namespace visit_detail

// recursion counter is equivalent here, but we'd like a better name to express the intent.
using TypeFamilyDepthCounter = RecursionCounter;

template<typename S>
struct GenericTypeVisitor
{
Expand All @@ -72,6 +75,7 @@ struct GenericTypeVisitor
Set seen;
bool skipBoundTypes = false;
int recursionCounter = 0;
int typeFamilyDepth = 0;

GenericTypeVisitor() = default;

Expand Down Expand Up @@ -400,6 +404,8 @@ struct GenericTypeVisitor
}
else if (auto tfit = get<TypeFamilyInstanceType>(ty))
{
TypeFamilyDepthCounter tfdc{&typeFamilyDepth};

if (visit(ty, *tfit))
{
for (TypeId p : tfit->typeArguments)
Expand Down Expand Up @@ -460,6 +466,8 @@ struct GenericTypeVisitor
visit(tp, *btp);
else if (auto tfitp = get<TypeFamilyInstanceTypePack>(tp))
{
TypeFamilyDepthCounter tfdc{&typeFamilyDepth};

if (visit(tp, *tfitp))
{
for (TypeId t : tfitp->typeArguments)
Expand Down
14 changes: 7 additions & 7 deletions Analysis/src/BuiltinDefinitions.cpp
Expand Up @@ -431,7 +431,7 @@ static bool dcrMagicFunctionFormat(MagicFunctionCallContext context)
// unify the prefix one argument at a time
for (size_t i = 0; i < expected.size() && i + paramOffset < params.size(); ++i)
{
context.solver->unify(context.solver->rootScope, context.callSite->location, params[i + paramOffset], expected[i]);
context.solver->unify(context.constraint, params[i + paramOffset], expected[i]);
}

// if we know the argument count or if we have too many arguments for sure, we can issue an error
Expand Down Expand Up @@ -561,7 +561,7 @@ static bool dcrMagicFunctionGmatch(MagicFunctionCallContext context)
if (returnTypes.empty())
return false;

context.solver->unify(context.solver->rootScope, context.callSite->location, params[0], context.solver->builtinTypes->stringType);
context.solver->unify(context.constraint, params[0], context.solver->builtinTypes->stringType);

const TypePackId emptyPack = arena->addTypePack({});
const TypePackId returnList = arena->addTypePack(returnTypes);
Expand Down Expand Up @@ -630,13 +630,13 @@ static bool dcrMagicFunctionMatch(MagicFunctionCallContext context)
if (returnTypes.empty())
return false;

context.solver->unify(context.solver->rootScope, context.callSite->location, params[0], context.solver->builtinTypes->stringType);
context.solver->unify(context.constraint, params[0], context.solver->builtinTypes->stringType);

const TypeId optionalNumber = arena->addType(UnionType{{context.solver->builtinTypes->nilType, context.solver->builtinTypes->numberType}});

size_t initIndex = context.callSite->self ? 1 : 2;
if (params.size() == 3 && context.callSite->args.size > initIndex)
context.solver->unify(context.solver->rootScope, context.callSite->location, params[2], optionalNumber);
context.solver->unify(context.constraint, params[2], optionalNumber);

const TypePackId returnList = arena->addTypePack(returnTypes);
asMutable(context.result)->ty.emplace<BoundTypePack>(returnList);
Expand Down Expand Up @@ -733,17 +733,17 @@ static bool dcrMagicFunctionFind(MagicFunctionCallContext context)
return false;
}

context.solver->unify(context.solver->rootScope, context.callSite->location, params[0], builtinTypes->stringType);
context.solver->unify(context.constraint, params[0], builtinTypes->stringType);

const TypeId optionalNumber = arena->addType(UnionType{{builtinTypes->nilType, builtinTypes->numberType}});
const TypeId optionalBoolean = arena->addType(UnionType{{builtinTypes->nilType, builtinTypes->booleanType}});

size_t initIndex = context.callSite->self ? 1 : 2;
if (params.size() >= 3 && context.callSite->args.size > initIndex)
context.solver->unify(context.solver->rootScope, context.callSite->location, params[2], optionalNumber);
context.solver->unify(context.constraint, params[2], optionalNumber);

if (params.size() == 4 && context.callSite->args.size > plainIndex)
context.solver->unify(context.solver->rootScope, context.callSite->location, params[3], optionalBoolean);
context.solver->unify(context.constraint, params[3], optionalBoolean);

returnTypes.insert(returnTypes.begin(), {optionalNumber, optionalNumber});

Expand Down

0 comments on commit 47ad768

Please sign in to comment.