Skip to content

Commit

Permalink
Sync to upstream/release/625 (#1252)
Browse files Browse the repository at this point in the history
# What's changed?

* Fix warning issued when Cmake version is too low (contributed by OSS
community)

## New Solver

* Fix an issue with inhabitance testing of tables with cyclic properties
* Preserve error suppression during type unification
* Overhaul type reference counting in the constraint solver
* Other miscellaneous constraint ordering fixes

## Native Codegen

* Fix incorrect assertion check in loadBytecodeTypeInfo

---

## 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: Vighnesh Vijay <vvijay@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>

---------

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: Aviral Goel <agoel@roblox.com>
Co-authored-by: David Cope <dcope@roblox.com>
Co-authored-by: Lily Brown <lbrown@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
  • Loading branch information
8 people committed May 10, 2024
1 parent a775bbc commit 2a80f5e
Show file tree
Hide file tree
Showing 45 changed files with 1,534 additions and 465 deletions.
4 changes: 3 additions & 1 deletion Analysis/include/Luau/Constraint.h
Expand Up @@ -284,11 +284,13 @@ struct Constraint

std::vector<NotNull<Constraint>> dependencies;

DenseHashSet<TypeId> getFreeTypes() const;
DenseHashSet<TypeId> getMaybeMutatedFreeTypes() const;
};

using ConstraintPtr = std::unique_ptr<Constraint>;

bool isReferenceCountedType(const TypeId typ);

inline Constraint& asMutable(const Constraint& c)
{
return const_cast<Constraint&>(c);
Expand Down
18 changes: 18 additions & 0 deletions Analysis/include/Luau/ConstraintSolver.h
Expand Up @@ -242,6 +242,24 @@ struct ConstraintSolver
void reportError(TypeErrorData&& data, const Location& location);
void reportError(TypeError e);

/**
* Shifts the count of references from `source` to `target`. This should be paired
* with any instance of binding a free type in order to maintain accurate refcounts.
* If `target` is not a free type, this is a noop.
* @param source the free type which is being bound
* @param target the type which the free type is being bound to
*/
void shiftReferences(TypeId source, TypeId target);

/**
* Generalizes the given free type if the reference counting allows it.
* @param the scope to generalize in
* @param type the free type we want to generalize
* @returns a non-free type that generalizes the argument, or `std::nullopt` if one
* does not exist
*/
std::optional<TypeId> generalizeFreeType(NotNull<Scope> scope, TypeId type);

/**
* Checks the existing set of constraints to see if there exist any that contain
* the provided free type, indicating that it is not yet ready to be replaced by
Expand Down
13 changes: 8 additions & 5 deletions Analysis/include/Luau/Normalize.h
Expand Up @@ -307,6 +307,9 @@ struct NormalizedType
/// Returns true if the type is a subtype of string(it could be a singleton). Behaves like Type::isString()
bool isSubtypeOfString() const;

/// Returns true if the type is a subtype of boolean(it could be a singleton). Behaves like Type::isBoolean()
bool isSubtypeOfBooleans() const;

/// Returns true if this type should result in error suppressing behavior.
bool shouldSuppressErrors() const;

Expand Down Expand Up @@ -360,7 +363,6 @@ class Normalizer
Normalizer& operator=(Normalizer&) = delete;

// If this returns null, the typechecker should emit a "too complex" error
const NormalizedType* DEPRECATED_normalize(TypeId ty);
std::shared_ptr<const NormalizedType> normalize(TypeId ty);
void clearNormal(NormalizedType& norm);

Expand Down Expand Up @@ -395,7 +397,7 @@ class Normalizer
TypeId negate(TypeId there);
void subtractPrimitive(NormalizedType& here, TypeId ty);
void subtractSingleton(NormalizedType& here, TypeId ty);
NormalizationResult intersectNormalWithNegationTy(TypeId toNegate, NormalizedType& intersect, bool useDeprecated = false);
NormalizationResult intersectNormalWithNegationTy(TypeId toNegate, NormalizedType& intersect);

// ------- Normalizing intersections
TypeId intersectionOfTops(TypeId here, TypeId there);
Expand All @@ -404,16 +406,16 @@ class Normalizer
void intersectClassesWithClass(NormalizedClassType& heres, TypeId there);
void intersectStrings(NormalizedStringType& here, const NormalizedStringType& there);
std::optional<TypePackId> intersectionOfTypePacks(TypePackId here, TypePackId there);
std::optional<TypeId> intersectionOfTables(TypeId here, TypeId there);
void intersectTablesWithTable(TypeIds& heres, TypeId there);
std::optional<TypeId> intersectionOfTables(TypeId here, TypeId there, Set<TypeId>& seenSet);
void intersectTablesWithTable(TypeIds& heres, TypeId there, Set<TypeId>& seenSetTypes);
void intersectTables(TypeIds& heres, const TypeIds& theres);
std::optional<TypeId> intersectionOfFunctions(TypeId here, TypeId there);
void intersectFunctionsWithFunction(NormalizedFunctionType& heress, TypeId there);
void intersectFunctions(NormalizedFunctionType& heress, const NormalizedFunctionType& theress);
NormalizationResult intersectTyvarsWithTy(NormalizedTyvars& here, TypeId there, Set<TypeId>& seenSetTypes);
NormalizationResult intersectNormals(NormalizedType& here, const NormalizedType& there, int ignoreSmallerTyvars = -1);
NormalizationResult intersectNormalWithTy(NormalizedType& here, TypeId there, Set<TypeId>& seenSetTypes);
NormalizationResult normalizeIntersections(const std::vector<TypeId>& intersections, NormalizedType& outType);
NormalizationResult normalizeIntersections(const std::vector<TypeId>& intersections, NormalizedType& outType, Set<TypeId>& seenSet);

// Check for inhabitance
NormalizationResult isInhabited(TypeId ty);
Expand All @@ -423,6 +425,7 @@ class Normalizer

// Check for intersections being inhabited
NormalizationResult isIntersectionInhabited(TypeId left, TypeId right);
NormalizationResult isIntersectionInhabited(TypeId left, TypeId right, Set<TypeId>& seenSet);

// -------- Convert back from a normalized type to a type
TypeId typeFromNormal(const NormalizedType& norm);
Expand Down
8 changes: 2 additions & 6 deletions Analysis/include/Luau/Set.h
Expand Up @@ -4,7 +4,6 @@
#include "Luau/Common.h"
#include "Luau/DenseHash.h"

LUAU_FASTFLAG(LuauFixSetIter)
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)

namespace Luau
Expand Down Expand Up @@ -143,11 +142,8 @@ class Set
: impl(impl_)
, end(end_)
{
if (FFlag::LuauFixSetIter || FFlag::DebugLuauDeferredConstraintResolution)
{
while (impl != end && impl->second == false)
++impl;
}
while (impl != end && impl->second == false)
++impl;
}

const T& operator*() const
Expand Down
5 changes: 5 additions & 0 deletions Analysis/include/Luau/Unifier2.h
Expand Up @@ -78,6 +78,11 @@ struct Unifier2
bool unify(TableType* subTable, const TableType* superTable);
bool unify(const MetatableType* subMetatable, const MetatableType* superMetatable);

bool unify(const AnyType* subAny, const FunctionType* superFn);
bool unify(const FunctionType* subFn, const AnyType* superAny);
bool unify(const AnyType* subAny, const TableType* superTable);
bool unify(const TableType* subTable, const AnyType* superAny);

// TODO think about this one carefully. We don't do unions or intersections of type packs
bool unify(TypePackId subTp, TypePackId superTp);

Expand Down
102 changes: 90 additions & 12 deletions Analysis/src/Constraint.cpp
Expand Up @@ -13,12 +13,12 @@ Constraint::Constraint(NotNull<Scope> scope, const Location& location, Constrain
{
}

struct FreeTypeCollector : TypeOnceVisitor
struct ReferenceCountInitializer : TypeOnceVisitor
{

DenseHashSet<TypeId>* result;

FreeTypeCollector(DenseHashSet<TypeId>* result)
ReferenceCountInitializer(DenseHashSet<TypeId>* result)
: result(result)
{
}
Expand All @@ -29,33 +29,111 @@ struct FreeTypeCollector : TypeOnceVisitor
return false;
}

bool visit(TypeId ty, const BlockedType&) override
{
result->insert(ty);
return false;
}

bool visit(TypeId ty, const PendingExpansionType&) override
{
result->insert(ty);
return false;
}

bool visit(TypeId ty, const ClassType&) override
{
// ClassTypes never contain free types.
return false;
}
};

DenseHashSet<TypeId> Constraint::getFreeTypes() const
bool isReferenceCountedType(const TypeId typ)
{
// n.b. this should match whatever `ReferenceCountInitializer` includes.
return get<FreeType>(typ) || get<BlockedType>(typ) || get<PendingExpansionType>(typ);
}

DenseHashSet<TypeId> Constraint::getMaybeMutatedFreeTypes() const
{
DenseHashSet<TypeId> types{{}};
FreeTypeCollector ftc{&types};
ReferenceCountInitializer rci{&types};

if (auto sc = get<SubtypeConstraint>(*this))
if (auto ec = get<EqualityConstraint>(*this))
{
rci.traverse(ec->resultType);
// `EqualityConstraints` should not mutate `assignmentType`.
}
else if (auto sc = get<SubtypeConstraint>(*this))
{
ftc.traverse(sc->subType);
ftc.traverse(sc->superType);
rci.traverse(sc->subType);
rci.traverse(sc->superType);
}
else if (auto psc = get<PackSubtypeConstraint>(*this))
{
ftc.traverse(psc->subPack);
ftc.traverse(psc->superPack);
rci.traverse(psc->subPack);
rci.traverse(psc->superPack);
}
else if (auto gc = get<GeneralizationConstraint>(*this))
{
rci.traverse(gc->generalizedType);
// `GeneralizationConstraints` should not mutate `sourceType` or `interiorTypes`.
}
else if (auto itc = get<IterableConstraint>(*this))
{
rci.traverse(itc->variables);
// `IterableConstraints` should not mutate `iterator`.
}
else if (auto nc = get<NameConstraint>(*this))
{
rci.traverse(nc->namedType);
}
else if (auto taec = get<TypeAliasExpansionConstraint>(*this))
{
rci.traverse(taec->target);
}
else if (auto ptc = get<PrimitiveTypeConstraint>(*this))
{
// we need to take into account primitive type constraints to prevent type families from reducing on
// primitive whose types we have not yet selected to be singleton or not.
ftc.traverse(ptc->freeType);
rci.traverse(ptc->freeType);
}
else if (auto hpc = get<HasPropConstraint>(*this))
{
rci.traverse(hpc->resultType);
// `HasPropConstraints` should not mutate `subjectType`.
}
else if (auto spc = get<SetPropConstraint>(*this))
{
rci.traverse(spc->resultType);
// `SetPropConstraints` should not mutate `subjectType` or `propType`.
// TODO: is this true? it "unifies" with `propType`, so maybe mutates that one too?
}
else if (auto hic = get<HasIndexerConstraint>(*this))
{
rci.traverse(hic->resultType);
// `HasIndexerConstraint` should not mutate `subjectType` or `indexType`.
}
else if (auto sic = get<SetIndexerConstraint>(*this))
{
rci.traverse(sic->propType);
// `SetIndexerConstraints` should not mutate `subjectType` or `indexType`.
}
else if (auto uc = get<UnpackConstraint>(*this))
{
rci.traverse(uc->resultPack);
// `UnpackConstraint` should not mutate `sourcePack`.
}
else if (auto u1c = get<Unpack1Constraint>(*this))
{
rci.traverse(u1c->resultType);
// `Unpack1Constraint` should not mutate `sourceType`.
}
else if (auto rc = get<ReduceConstraint>(*this))
{
rci.traverse(rc->ty);
}
else if (auto rpc = get<ReducePackConstraint>(*this))
{
rci.traverse(rpc->tp);
}

return types;
Expand Down

0 comments on commit 2a80f5e

Please sign in to comment.