Skip to content

Commit

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

* Check interrupt handler inside the pattern match engine to eliminate
potential for programs to hang during string library function execution.
* Allow iteration over table properties to pass the old type solver. 

### Native Code Generation

* Use in-place memory operands for math library operations on x64.
* Replace opaque bools with separate enum classes in IrDump to improve
code maintainability.
* Translate operations on inferred vectors to IR.
* Enable support for debugging native-compiled functions in Roblox
Studio.

### New Type Solver

* Rework type inference for boolean and string literals to introduce
bounded free types (bounded below by the singleton type, and above by
the primitive type) and reworked primitive type constraint to decide
which is the appropriate type for the literal.
* Introduce `FunctionCheckConstraint` to handle bidirectional
typechecking for function calls, pushing the expected parameter types
from the function onto the arguments.
* Introduce `union` and `intersect` type families to compute deferred
simplified unions and intersections to be employed by the constraint
generation logic in the new solver.
* Implement support for expanding the domain of local types in
`Unifier2`.
* Rework type inference for iteration variables bound by for in loops to
use local types.
* Change constraint blocking logic to use a set to prevent accidental
re-blocking.
* Add logic to detect missing return statements in functions.

### 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: Aviral Goel <agoel@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>

---------

Co-authored-by: Alexander McCord <amccord@roblox.com>
Co-authored-by: Andy Friesen <afriesen@roblox.com>
Co-authored-by: Vighnesh <vvijay@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 authored Jan 27, 2024
1 parent cdd1a38 commit 9c588be
Show file tree
Hide file tree
Showing 59 changed files with 2,032 additions and 485 deletions.
42 changes: 33 additions & 9 deletions Analysis/include/Luau/Constraint.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,18 +90,42 @@ struct FunctionCallConstraint
DenseHashMap<const AstNode*, TypeId>* astOverloadResolvedTypes = nullptr;
};

// result ~ prim ExpectedType SomeSingletonType MultitonType
// function_check fn argsPack
//
// If ExpectedType is potentially a singleton (an actual singleton or a union
// that contains a singleton), then result ~ SomeSingletonType
// If fn is a function type and argsPack is a partially solved
// pack of arguments to be supplied to the function, propagate the argument
// types of fn into the types of argsPack. This is used to implement
// bidirectional inference of lambda arguments.
struct FunctionCheckConstraint
{
TypeId fn;
TypePackId argsPack;

class AstExprCall* callSite = nullptr;
};

// prim FreeType ExpectedType PrimitiveType
//
// else result ~ MultitonType
// FreeType is bounded below by the singleton type and above by PrimitiveType
// initially. When this constraint is resolved, it will check that the bounds
// of the free type are well-formed by subtyping.
//
// If they are not well-formed, then FreeType is replaced by its lower bound
//
// If they are well-formed and ExpectedType is potentially a singleton (an
// actual singleton or a union that contains a singleton),
// then FreeType is replaced by its lower bound
//
// else FreeType is replaced by PrimitiveType
struct PrimitiveTypeConstraint
{
TypeId resultType;
TypeId expectedType;
TypeId singletonType;
TypeId multitonType;
TypeId freeType;

// potentially gets used to force the lower bound?
std::optional<TypeId> expectedType;

// the primitive type to check against
TypeId primitiveType;
};

// result ~ hasProp type "prop_name"
Expand Down Expand Up @@ -230,7 +254,7 @@ struct ReducePackConstraint
};

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

struct Constraint
Expand Down
11 changes: 8 additions & 3 deletions Analysis/include/Luau/ConstraintGenerator.h
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ struct ConstraintGenerator
*/
ScopePtr childScope(AstNode* node, const ScopePtr& parent);

std::optional<TypeId> lookup(Scope* scope, DefId def, bool prototype = true);
std::optional<TypeId> lookup(const ScopePtr& scope, DefId def, bool prototype = true);

/**
* Adds a new constraint with no dependencies to a given scope.
Expand Down Expand Up @@ -178,8 +178,8 @@ struct ConstraintGenerator
};

using RefinementContext = InsertionOrderedMap<DefId, RefinementPartition>;
void unionRefinements(const RefinementContext& lhs, const RefinementContext& rhs, RefinementContext& dest, std::vector<ConstraintV>* constraints);
void computeRefinement(const ScopePtr& scope, RefinementId refinement, RefinementContext* refis, bool sense, bool eq, std::vector<ConstraintV>* constraints);
void unionRefinements(const ScopePtr& scope, Location location, const RefinementContext& lhs, const RefinementContext& rhs, RefinementContext& dest, std::vector<ConstraintV>* constraints);
void computeRefinement(const ScopePtr& scope, Location location, RefinementId refinement, RefinementContext* refis, bool sense, bool eq, std::vector<ConstraintV>* constraints);
void applyRefinements(const ScopePtr& scope, Location location, RefinementId refinement);

ControlFlow visitBlockWithoutChildScope(const ScopePtr& scope, AstStatBlock* block);
Expand Down Expand Up @@ -329,6 +329,11 @@ struct ConstraintGenerator
void reportError(Location location, TypeErrorData err);
void reportCodeTooComplex(Location location);

// make a union type family of these two types
TypeId makeUnion(const ScopePtr& scope, Location location, TypeId lhs, TypeId rhs);
// make an intersect type family of these two types
TypeId makeIntersect(const ScopePtr& scope, Location location, TypeId lhs, TypeId rhs);

/** Scan the program for global definitions.
*
* ConstraintGenerator needs to differentiate between globals and accesses to undefined symbols. Doing this "for
Expand Down
5 changes: 3 additions & 2 deletions Analysis/include/Luau/ConstraintSolver.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ struct ConstraintSolver
// anything.
std::unordered_map<NotNull<const Constraint>, size_t> blockedConstraints;
// A mapping of type/pack pointers to the constraints they block.
std::unordered_map<BlockedConstraintId, std::vector<NotNull<const Constraint>>, HashBlockedConstraintId> blocked;
std::unordered_map<BlockedConstraintId, DenseHashSet<const Constraint*>, HashBlockedConstraintId> blocked;
// Memoized instantiations of type aliases.
DenseHashMap<InstantiationSignature, TypeId, HashInstantiationSignature> instantiatedAliases{{}};
// Breadcrumbs for where a free type's upper bound was expanded. We use
Expand Down Expand Up @@ -126,6 +126,7 @@ struct ConstraintSolver
bool tryDispatch(const NameConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const TypeAliasExpansionConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const FunctionCallConstraint& c, NotNull<const Constraint> constraint);
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);
Expand Down Expand Up @@ -285,7 +286,7 @@ struct ConstraintSolver
* @param target the type or type pack pointer that the constraint is blocked on.
* @param constraint the constraint to block.
**/
void block_(BlockedConstraintId target, NotNull<const Constraint> constraint);
bool block_(BlockedConstraintId target, NotNull<const Constraint> constraint);

/**
* Informs the solver that progress has been made on a type or type pack. The
Expand Down
3 changes: 3 additions & 0 deletions Analysis/include/Luau/TypeFamily.h
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,9 @@ struct BuiltinTypeFamilies
TypeFamily eqFamily;

TypeFamily refineFamily;
TypeFamily unionFamily;
TypeFamily intersectFamily;

TypeFamily keyofFamily;
TypeFamily rawkeyofFamily;

Expand Down
1 change: 1 addition & 0 deletions Analysis/include/Luau/Unifier2.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ struct Unifier2
* free TypePack to another and encounter an occurs check violation.
*/
bool unify(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);
bool unify(TypeId subTy, const UnionType* superUnion);
Expand Down
21 changes: 2 additions & 19 deletions Analysis/src/AstJsonEncoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@

#include <math.h>

LUAU_FASTFLAG(LuauClipExtraHasEndProps);

namespace Luau
{

Expand Down Expand Up @@ -393,8 +391,6 @@ struct AstJsonEncoder : public AstVisitor
PROP(body);
PROP(functionDepth);
PROP(debugname);
if (!FFlag::LuauClipExtraHasEndProps)
write("hasEnd", node->DEPRECATED_hasEnd);
});
}

Expand Down Expand Up @@ -591,11 +587,8 @@ struct AstJsonEncoder : public AstVisitor
void write(class AstStatBlock* node)
{
writeNode(node, "AstStatBlock", [&]() {
if (FFlag::LuauClipExtraHasEndProps)
{
writeRaw(",\"hasEnd\":");
write(node->hasEnd);
}
writeRaw(",\"hasEnd\":");
write(node->hasEnd);
writeRaw(",\"body\":[");
bool comma = false;
for (AstStat* stat : node->body)
Expand All @@ -619,8 +612,6 @@ struct AstJsonEncoder : public AstVisitor
if (node->elsebody)
PROP(elsebody);
write("hasThen", node->thenLocation.has_value());
if (!FFlag::LuauClipExtraHasEndProps)
write("hasEnd", node->DEPRECATED_hasEnd);
});
}

Expand All @@ -630,8 +621,6 @@ struct AstJsonEncoder : public AstVisitor
PROP(condition);
PROP(body);
PROP(hasDo);
if (!FFlag::LuauClipExtraHasEndProps)
write("hasEnd", node->DEPRECATED_hasEnd);
});
}

Expand All @@ -640,8 +629,6 @@ struct AstJsonEncoder : public AstVisitor
writeNode(node, "AstStatRepeat", [&]() {
PROP(condition);
PROP(body);
if (!FFlag::LuauClipExtraHasEndProps)
write("hasUntil", node->DEPRECATED_hasUntil);
});
}

Expand Down Expand Up @@ -687,8 +674,6 @@ struct AstJsonEncoder : public AstVisitor
PROP(step);
PROP(body);
PROP(hasDo);
if (!FFlag::LuauClipExtraHasEndProps)
write("hasEnd", node->DEPRECATED_hasEnd);
});
}

Expand All @@ -700,8 +685,6 @@ struct AstJsonEncoder : public AstVisitor
PROP(body);
PROP(hasIn);
PROP(hasDo);
if (!FFlag::LuauClipExtraHasEndProps)
write("hasEnd", node->DEPRECATED_hasEnd);
});
}

Expand Down
82 changes: 22 additions & 60 deletions Analysis/src/Autocomplete.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
LUAU_FASTFLAG(DebugLuauReadWriteProperties);
LUAU_FASTFLAG(LuauClipExtraHasEndProps);
LUAU_FASTFLAGVARIABLE(LuauAutocompleteStringLiteralBounds, false);

static const std::unordered_set<std::string> kStatementStartingKeywords = {
Expand Down Expand Up @@ -1068,51 +1067,30 @@ static AutocompleteEntryMap autocompleteStatement(
for (const auto& kw : kStatementStartingKeywords)
result.emplace(kw, AutocompleteEntry{AutocompleteEntryKind::Keyword});

if (FFlag::LuauClipExtraHasEndProps)
for (auto it = ancestry.rbegin(); it != ancestry.rend(); ++it)
{
for (auto it = ancestry.rbegin(); it != ancestry.rend(); ++it)
if (AstStatForIn* statForIn = (*it)->as<AstStatForIn>(); statForIn && !statForIn->body->hasEnd)
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
else if (AstStatFor* statFor = (*it)->as<AstStatFor>(); statFor && !statFor->body->hasEnd)
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
else if (AstStatIf* statIf = (*it)->as<AstStatIf>())
{
if (AstStatForIn* statForIn = (*it)->as<AstStatForIn>(); statForIn && !statForIn->body->hasEnd)
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
else if (AstStatFor* statFor = (*it)->as<AstStatFor>(); statFor && !statFor->body->hasEnd)
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
else if (AstStatIf* statIf = (*it)->as<AstStatIf>())
bool hasEnd = statIf->thenbody->hasEnd;
if (statIf->elsebody)
{
bool hasEnd = statIf->thenbody->hasEnd;
if (statIf->elsebody)
{
if (AstStatBlock* elseBlock = statIf->elsebody->as<AstStatBlock>())
hasEnd = elseBlock->hasEnd;
}

if (!hasEnd)
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
if (AstStatBlock* elseBlock = statIf->elsebody->as<AstStatBlock>())
hasEnd = elseBlock->hasEnd;
}
else if (AstStatWhile* statWhile = (*it)->as<AstStatWhile>(); statWhile && !statWhile->body->hasEnd)
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
else if (AstExprFunction* exprFunction = (*it)->as<AstExprFunction>(); exprFunction && !exprFunction->body->hasEnd)
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
if (AstStatBlock* exprBlock = (*it)->as<AstStatBlock>(); exprBlock && !exprBlock->hasEnd)
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
}
}
else
{
for (auto it = ancestry.rbegin(); it != ancestry.rend(); ++it)
{
if (AstStatForIn* statForIn = (*it)->as<AstStatForIn>(); statForIn && !statForIn->DEPRECATED_hasEnd)
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
else if (AstStatFor* statFor = (*it)->as<AstStatFor>(); statFor && !statFor->DEPRECATED_hasEnd)
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
else if (AstStatIf* statIf = (*it)->as<AstStatIf>(); statIf && !statIf->DEPRECATED_hasEnd)
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
else if (AstStatWhile* statWhile = (*it)->as<AstStatWhile>(); statWhile && !statWhile->DEPRECATED_hasEnd)
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
else if (AstExprFunction* exprFunction = (*it)->as<AstExprFunction>(); exprFunction && !exprFunction->DEPRECATED_hasEnd)
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
if (AstStatBlock* exprBlock = (*it)->as<AstStatBlock>(); exprBlock && !exprBlock->hasEnd)

if (!hasEnd)
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
}
else if (AstStatWhile* statWhile = (*it)->as<AstStatWhile>(); statWhile && !statWhile->body->hasEnd)
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
else if (AstExprFunction* exprFunction = (*it)->as<AstExprFunction>(); exprFunction && !exprFunction->body->hasEnd)
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
if (AstStatBlock* exprBlock = (*it)->as<AstStatBlock>(); exprBlock && !exprBlock->hasEnd)
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
}

if (ancestry.size() >= 2)
Expand All @@ -1127,16 +1105,8 @@ static AutocompleteEntryMap autocompleteStatement(
}
}

if (FFlag::LuauClipExtraHasEndProps)
{
if (AstStatRepeat* statRepeat = parent->as<AstStatRepeat>(); statRepeat && !statRepeat->body->hasEnd)
result.emplace("until", AutocompleteEntry{AutocompleteEntryKind::Keyword});
}
else
{
if (AstStatRepeat* statRepeat = parent->as<AstStatRepeat>(); statRepeat && !statRepeat->DEPRECATED_hasUntil)
result.emplace("until", AutocompleteEntry{AutocompleteEntryKind::Keyword});
}
if (AstStatRepeat* statRepeat = parent->as<AstStatRepeat>(); statRepeat && !statRepeat->body->hasEnd)
result.emplace("until", AutocompleteEntry{AutocompleteEntryKind::Keyword});
}

if (ancestry.size() >= 4)
Expand All @@ -1150,16 +1120,8 @@ static AutocompleteEntryMap autocompleteStatement(
}
}

if (FFlag::LuauClipExtraHasEndProps)
{
if (AstStatRepeat* statRepeat = extractStat<AstStatRepeat>(ancestry); statRepeat && !statRepeat->body->hasEnd)
result.emplace("until", AutocompleteEntry{AutocompleteEntryKind::Keyword});
}
else
{
if (AstStatRepeat* statRepeat = extractStat<AstStatRepeat>(ancestry); statRepeat && !statRepeat->DEPRECATED_hasUntil)
result.emplace("until", AutocompleteEntry{AutocompleteEntryKind::Keyword});
}
if (AstStatRepeat* statRepeat = extractStat<AstStatRepeat>(ancestry); statRepeat && !statRepeat->body->hasEnd)
result.emplace("until", AutocompleteEntry{AutocompleteEntryKind::Keyword});

return result;
}
Expand Down
6 changes: 6 additions & 0 deletions Analysis/src/Constraint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ DenseHashSet<TypeId> Constraint::getFreeTypes() const
ftc.traverse(psc->subPack);
ftc.traverse(psc->superPack);
}
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);
}

return types;
}
Expand Down
Loading

0 comments on commit 9c588be

Please sign in to comment.