Skip to content

Commit

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

* Fix up the `std::iterator_traits` definitions for some Luau data
structures.
* Replace some of the usages of `std::unordered_set` and
`std::unordered_map` with Luau-provided data structures to increase
performance and reduce overall number of heap allocations.
* Update some of the documentation links in comments throughout the
codebase to correctly point to the moved repository.
* Expanded JSON encoder for AST to support singleton types.
* Fixed a bug in `luau-analyze` where exceptions in the last module
being checked during multithreaded analysis would not be rethrown.

### New type solver

* Introduce a `refine` type family to handle deferred refinements during
type inference, replacing the old `RefineConstraint`.
* Continued work on the implementation of type states, fixing some known
bugs/blockers.
* Added support for variadic functions in new non-strict mode, enabling
broader support for builtins and the Roblox API.

### Internal Contributors

Co-authored-by: Aaron Weiss <[email protected]>
Co-authored-by: Alexander McCord <[email protected]>
Co-authored-by: Andy Friesen <[email protected]>
Co-authored-by: Vyacheslav Egorov <[email protected]>

---------

Co-authored-by: Alexander McCord <[email protected]>
Co-authored-by: Andy Friesen <[email protected]>
Co-authored-by: Vighnesh <[email protected]>
Co-authored-by: Aviral Goel <[email protected]>
Co-authored-by: David Cope <[email protected]>
Co-authored-by: Lily Brown <[email protected]>
Co-authored-by: Vyacheslav Egorov <[email protected]>
  • Loading branch information
8 people authored Dec 15, 2023
1 parent 2173938 commit ff502f0
Show file tree
Hide file tree
Showing 38 changed files with 885 additions and 535 deletions.
20 changes: 1 addition & 19 deletions Analysis/include/Luau/Constraint.h
Original file line number Diff line number Diff line change
Expand Up @@ -197,24 +197,6 @@ struct UnpackConstraint
bool resultIsLValue = false;
};

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

// 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 @@ -249,7 +231,7 @@ struct ReducePackConstraint

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

struct Constraint
{
Expand Down
1 change: 0 additions & 1 deletion Analysis/include/Luau/ConstraintSolver.h
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,6 @@ 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 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 Down
4 changes: 2 additions & 2 deletions Analysis/include/Luau/DataFlowGraph.h
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,8 @@ struct DataFlowGraphBuilder
DfgScope* childScope(DfgScope* scope, DfgScope::ScopeType scopeType = DfgScope::Linear);

void join(DfgScope* p, DfgScope* a, DfgScope* b);
void joinBindings(DfgScope::Bindings& p, const DfgScope::Bindings& a, const DfgScope::Bindings& b);
void joinProps(DfgScope::Props& p, const DfgScope::Props& a, const DfgScope::Props& b);
void joinBindings(DfgScope* p, const DfgScope& a, const DfgScope& b);
void joinProps(DfgScope* p, const DfgScope& a, const DfgScope& b);

DefId lookup(DfgScope* scope, Symbol symbol);
DefId lookup(DfgScope* scope, DefId def, const std::string& key);
Expand Down
6 changes: 6 additions & 0 deletions Analysis/include/Luau/Set.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,12 @@ class Set
class const_iterator
{
public:
using value_type = T;
using reference = T&;
using pointer = T*;
using difference_type = ptrdiff_t;
using iterator_category = std::forward_iterator_tag;

const_iterator(typename Impl::const_iterator impl, typename Impl::const_iterator end)
: impl(impl)
, end(end)
Expand Down
2 changes: 1 addition & 1 deletion Analysis/include/Luau/Type.h
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ struct PrimitiveType
}
};

// Singleton types https://github.com/Roblox/luau/blob/master/rfcs/syntax-singleton-types.md
// Singleton types https://github.com/luau-lang/rfcs/blob/master/docs/syntax-singleton-types.md
// Types for true and false
struct BooleanSingleton
{
Expand Down
2 changes: 2 additions & 0 deletions Analysis/include/Luau/TypeFamily.h
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ struct BuiltinTypeFamilies
TypeFamily leFamily;
TypeFamily eqFamily;

TypeFamily refineFamily;

void addToScope(NotNull<TypeArena> arena, NotNull<Scope> scope) const;
};

Expand Down
34 changes: 34 additions & 0 deletions Analysis/src/AstJsonEncoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,23 @@ struct AstJsonEncoder : public AstVisitor
{
writeString(name.value ? name.value : "");
}
void write(std::optional<AstArgumentName> name)
{
if (name)
write(*name);
else
writeRaw("null");
}
void write(AstArgumentName name)
{
writeRaw("{");
bool c = pushComma();
writeType("AstArgumentName");
write("name", name.first);
write("location", name.second);
popComma(c);
writeRaw("}");
}

void write(const Position& position)
{
Expand Down Expand Up @@ -848,6 +865,7 @@ struct AstJsonEncoder : public AstVisitor
PROP(generics);
PROP(genericPacks);
PROP(argTypes);
PROP(argNames);
PROP(returnTypes);
});
}
Expand Down Expand Up @@ -902,6 +920,22 @@ struct AstJsonEncoder : public AstVisitor
});
}

bool visit(class AstTypeSingletonBool* node) override
{
writeNode(node, "AstTypeSingletonBool", [&]() {
write("value", node->value);
});
return false;
}

bool visit(class AstTypeSingletonString* node) override
{
writeNode(node, "AstTypeSingletonString", [&]() {
write("value", node->value);
});
return false;
}

bool visit(class AstExprGroup* node) override
{
write(node);
Expand Down
17 changes: 9 additions & 8 deletions Analysis/src/ConstraintGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ void ConstraintGenerator::unionRefinements(const RefinementContext& lhs, const R
return types[0];
else if (2 == types.size())
{
// TODO: It may be advantageous to create a RefineConstraint here when there are blockedTypes.
// TODO: It may be advantageous to introduce a refine type family here when there are blockedTypes.
SimplifyResult sr = simplifyIntersection(builtinTypes, arena, types[0], types[1]);
if (sr.blockedTypes.empty())
return sr.result;
Expand Down Expand Up @@ -441,10 +441,14 @@ void ConstraintGenerator::applyRefinements(const ScopePtr& scope, Location locat
{
if (mustDeferIntersection(ty) || mustDeferIntersection(dt))
{
TypeId r = arena->addType(BlockedType{});
addConstraint(scope, location, RefineConstraint{RefineConstraint::Intersection, r, ty, dt});

ty = r;
TypeId resultType = arena->addType(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.refineFamily},
{ty, dt},
{},
});
addConstraint(scope, location, ReduceConstraint{resultType});

ty = resultType;
}
else
{
Expand Down Expand Up @@ -1005,9 +1009,6 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatAssign* ass

checkLValue(scope, lvalue, assignee);
assignees.push_back(assignee);

DefId def = dfg->getDef(lvalue);
scope->lvalueTypes[def] = assignee;
}

TypePackId resultPack = checkPack(scope, assign->values).tp;
Expand Down
153 changes: 3 additions & 150 deletions Analysis/src/ConstraintSolver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -545,8 +545,6 @@ 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 soc = get<SetOpConstraint>(*constraint))
success = tryDispatch(*soc, constraint, force);
else if (auto rc = get<ReduceConstraint>(*constraint))
Expand Down Expand Up @@ -887,9 +885,9 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
// In order to prevent infinite types from being expanded and causing us to
// cycle infinitely, we need to scan the type function for cases where we
// expand the same alias with different type saturatedTypeArguments. See
// https://github.com/Roblox/luau/pull/68 for the RFC responsible for this.
// This is a little nicer than using a recursion limit because we can catch
// the infinite expansion before actually trying to expand it.
// https://github.com/luau-lang/luau/pull/68 for the RFC responsible for
// this. This is a little nicer than using a recursion limit because we can
// catch the infinite expansion before actually trying to expand it.
InfiniteTypeFinder itf{this, signature, constraint->scope};
itf.traverse(tf->type);

Expand Down Expand Up @@ -1505,151 +1503,6 @@ 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
{
DenseHashSet<TypeId> found{nullptr};
bool visit(TypeId ty, const BlockedType&) override
{
found.insert(ty);
return false;
}

bool visit(TypeId ty, const PendingExpansionType&) override
{
found.insert(ty);
return false;
}

bool visit(TypeId ty, const ClassType&) override
{
return false;
}
};

} // namespace

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, constraint->location);
return true;
}

const TypeId type = follow(c.type);

if (hasUnresolvedConstraints(type))
return block(type, constraint);

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)))
{
TypeId f = freshType(arena, builtinTypes, constraint->scope);
asMutable(c.resultType)->ty.emplace<BoundType>(f);
}
else
asMutable(c.resultType)->ty.emplace<BoundType>(c.discriminant);

unblock(c.resultType, constraint->location);
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);

switch (shouldSuppressErrors(normalizer, c.type))
{
case ErrorSuppression::Suppress:
{
auto resultOrError = simplifyUnion(builtinTypes, arena, result, builtinTypes->errorType).result;
asMutable(c.resultType)->ty.emplace<BoundType>(resultOrError);
break;
}
case ErrorSuppression::DoNotSuppress:
asMutable(c.resultType)->ty.emplace<BoundType>(result);
break;
case ErrorSuppression::NormalizationFailed:
reportError(NormalizationTooComplex{}, constraint->location);
break;
}

unblock(c.resultType, constraint->location);

return true;
}

bool ConstraintSolver::tryDispatch(const SetOpConstraint& c, NotNull<const Constraint> constraint, bool force)
{
bool blocked = false;
Expand Down
Loading

0 comments on commit ff502f0

Please sign in to comment.