Skip to content

Commit

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

### New Type Solver

- Unification of two fresh types no longer binds them together.
- Replaced uses of raw `emplace` with `emplaceType` to catch cyclic
bound types when they are created.
- `SetIndexerConstraint` is blocked until the indexer result type is not
blocked.
- Fix a case where a blocked type got past the constraint solver.
- Searching for free types should no longer traverse into `ClassType`s.
- Fix a corner case that could result in the non-testable type `~{}`.
- Fix incorrect flagging when `any` was a parameter of some checked
function in nonstrict type checker.
- `IterableConstraint` now consider tables without `__iter` to be
iterables.

### Native Code Generation

- Improve register type info lookup by program counter.
- Generate type information for locals and upvalues

---

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

---------

Co-authored-by: Aaron Weiss <aaronweiss@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 committed Apr 25, 2024
1 parent 68bd1b2 commit 259e509
Show file tree
Hide file tree
Showing 63 changed files with 1,828 additions and 550 deletions.
4 changes: 2 additions & 2 deletions Analysis/include/Luau/ConstraintGenerator.h
Expand Up @@ -165,7 +165,7 @@ struct ConstraintGenerator
*/
ScopePtr childScope(AstNode* node, const ScopePtr& parent);

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

/**
* Adds a new constraint with no dependencies to a given scope.
Expand Down Expand Up @@ -242,7 +242,7 @@ struct ConstraintGenerator
Inference check(const ScopePtr& scope, AstExprConstantBool* bool_, std::optional<TypeId> expectedType, bool forceSingleton);
Inference check(const ScopePtr& scope, AstExprLocal* local);
Inference check(const ScopePtr& scope, AstExprGlobal* global);
Inference checkIndexName(const ScopePtr& scope, const RefinementKey* key, AstExpr* indexee, std::string index);
Inference checkIndexName(const ScopePtr& scope, const RefinementKey* key, AstExpr* indexee, const std::string& index, Location indexLocation);
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, bool generalize);
Expand Down
2 changes: 0 additions & 2 deletions Analysis/include/Luau/ToString.h
Expand Up @@ -12,7 +12,6 @@

LUAU_FASTINT(LuauTableTypeMaximumStringifierLength)
LUAU_FASTINT(LuauTypeMaximumStringifierLength)
LUAU_FASTFLAG(LuauToStringSimpleCompositeTypesSingleLine)

namespace Luau
{
Expand All @@ -36,7 +35,6 @@ struct ToStringOptions
{
ToStringOptions(bool exhaustive = false)
: exhaustive(exhaustive)
, compositeTypesSingleLineLimit(FFlag::LuauToStringSimpleCompositeTypesSingleLine ? 5 : 0)
{
}

Expand Down
3 changes: 3 additions & 0 deletions Analysis/include/Luau/Type.h
Expand Up @@ -1103,4 +1103,7 @@ LUAU_NOINLINE T* emplaceType(Type* ty, Args&&... args)
return &ty->ty.emplace<T>(std::forward<Args>(args)...);
}

template<>
LUAU_NOINLINE Unifiable::Bound<TypeId>* emplaceType<BoundType>(Type* ty, TypeId& tyArg);

} // namespace Luau
14 changes: 14 additions & 0 deletions Analysis/include/Luau/TypePack.h
Expand Up @@ -233,4 +233,18 @@ bool isVariadicTail(TypePackId tp, const TxnLog& log, bool includeHiddenVariadic

bool containsNever(TypePackId tp);

/*
* Use this to change the kind of a particular type pack.
*
* LUAU_NOINLINE so that the calling frame doesn't have to pay the stack storage for the new variant.
*/
template<typename T, typename... Args>
LUAU_NOINLINE T* emplaceTypePack(TypePackVar* ty, Args&&... args)
{
return &ty->ty.emplace<T>(std::forward<Args>(args)...);
}

template<>
LUAU_NOINLINE Unifiable::Bound<TypePackId>* emplaceTypePack<BoundTypePack>(TypePackVar* ty, TypePackId& tyArg);

} // namespace Luau
9 changes: 9 additions & 0 deletions Analysis/include/Luau/TypeUtils.h
Expand Up @@ -201,6 +201,15 @@ const T* get(std::optional<Ty> ty)
return nullptr;
}

template<typename T, typename Ty>
T* getMutable(std::optional<Ty> ty)
{
if (ty)
return getMutable<T>(*ty);
else
return nullptr;
}

template<typename Ty>
std::optional<Ty> follow(std::optional<Ty> ty)
{
Expand Down
6 changes: 6 additions & 0 deletions Analysis/src/Constraint.cpp
Expand Up @@ -28,6 +28,12 @@ struct FreeTypeCollector : TypeOnceVisitor
result->insert(ty);
return false;
}

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

DenseHashSet<TypeId> Constraint::getFreeTypes() const
Expand Down
67 changes: 36 additions & 31 deletions Analysis/src/ConstraintGenerator.cpp
Expand Up @@ -287,7 +287,7 @@ ScopePtr ConstraintGenerator::childScope(AstNode* node, const ScopePtr& parent)
return scope;
}

std::optional<TypeId> ConstraintGenerator::lookup(const ScopePtr& scope, DefId def, bool prototype)
std::optional<TypeId> ConstraintGenerator::lookup(const ScopePtr& scope, Location location, DefId def, bool prototype)
{
if (get<Cell>(def))
return scope->lookup(def);
Expand All @@ -296,7 +296,7 @@ std::optional<TypeId> ConstraintGenerator::lookup(const ScopePtr& scope, DefId d
if (auto found = scope->lookup(def))
return *found;
else if (!prototype && phi->operands.size() == 1)
return lookup(scope, phi->operands.at(0), prototype);
return lookup(scope, location, phi->operands.at(0), prototype);
else if (!prototype)
return std::nullopt;

Expand All @@ -307,14 +307,14 @@ std::optional<TypeId> ConstraintGenerator::lookup(const ScopePtr& scope, DefId d
// `scope->lookup(operand)` may return nothing because we only bind a type to that operand
// once we've seen that particular `DefId`. In this case, we need to prototype those types
// and use those at a later time.
std::optional<TypeId> ty = lookup(scope, operand, /*prototype*/ false);
std::optional<TypeId> ty = lookup(scope, location, operand, /*prototype*/ false);
if (!ty)
{
ty = arena->addType(BlockedType{});
rootScope->lvalueTypes[operand] = *ty;
}

res = makeUnion(scope, Location{} /* TODO: can we provide a real location here? */, res, *ty);
res = makeUnion(scope, location, res, *ty);
}

scope->lvalueTypes[def] = res;
Expand Down Expand Up @@ -512,7 +512,7 @@ void ConstraintGenerator::applyRefinements(const ScopePtr& scope, Location locat

for (auto& [def, partition] : refinements)
{
if (std::optional<TypeId> defTy = lookup(scope, def))
if (std::optional<TypeId> defTy = lookup(scope, location, def))
{
TypeId ty = *defTy;
if (partition.shouldAppendNilType)
Expand Down Expand Up @@ -918,7 +918,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocalFuncti

bool sigFullyDefined = !hasFreeType(sig.signature);
if (sigFullyDefined)
asMutable(functionType)->ty.emplace<BoundType>(sig.signature);
emplaceType<BoundType>(asMutable(functionType), sig.signature);

DefId def = dfg->getDef(function->name);
scope->lvalueTypes[def] = functionType;
Expand Down Expand Up @@ -969,15 +969,15 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f
bool sigFullyDefined = !hasFreeType(sig.signature);

if (sigFullyDefined)
asMutable(generalizedType)->ty.emplace<BoundType>(sig.signature);
emplaceType<BoundType>(asMutable(generalizedType), sig.signature);

DenseHashSet<Constraint*> excludeList{nullptr};

DefId def = dfg->getDef(function->name);
std::optional<TypeId> existingFunctionTy = lookup(scope, def);
std::optional<TypeId> existingFunctionTy = follow(lookup(scope, function->name->location, def));

if (existingFunctionTy && (sigFullyDefined || function->name->is<AstExprLocal>()) && get<BlockedType>(*existingFunctionTy))
asMutable(*existingFunctionTy)->ty.emplace<BoundType>(sig.signature);
if (get<BlockedType>(existingFunctionTy) && sigFullyDefined)
emplaceType<BoundType>(asMutable(*existingFunctionTy), sig.signature);

if (AstExprLocal* localName = function->name->as<AstExprLocal>())
{
Expand Down Expand Up @@ -1071,6 +1071,12 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f
blocked->setOwner(addConstraint(scope, std::move(c)));
}

if (BlockedType* bt = getMutable<BlockedType>(follow(existingFunctionTy)); bt && !bt->getOwner())
{
auto uc = addConstraint(scope, function->name->location, Unpack1Constraint{*existingFunctionTy, generalizedType});
bt->setOwner(uc);
}

return ControlFlow::None;
}

Expand Down Expand Up @@ -1113,13 +1119,13 @@ static void bindFreeType(TypeId a, TypeId b)
LUAU_ASSERT(af || bf);

if (!bf)
asMutable(a)->ty.emplace<BoundType>(b);
emplaceType<BoundType>(asMutable(a), b);
else if (!af)
asMutable(b)->ty.emplace<BoundType>(a);
emplaceType<BoundType>(asMutable(b), a);
else if (subsumes(bf->scope, af->scope))
asMutable(a)->ty.emplace<BoundType>(b);
emplaceType<BoundType>(asMutable(a), b);
else if (subsumes(af->scope, bf->scope))
asMutable(b)->ty.emplace<BoundType>(a);
emplaceType<BoundType>(asMutable(b), a);
}

ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatAssign* assign)
Expand Down Expand Up @@ -1153,6 +1159,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatAssign* ass
for (TypeId assignee : typeStates)
{
auto blocked = getMutable<BlockedType>(assignee);

if (blocked && !blocked->getOwner())
blocked->setOwner(uc);
}
Expand Down Expand Up @@ -1254,11 +1261,11 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeAlias*
LUAU_ASSERT(get<BlockedType>(aliasTy));
if (occursCheck(aliasTy, ty))
{
asMutable(aliasTy)->ty.emplace<BoundType>(builtinTypes->anyType);
emplaceType<BoundType>(asMutable(aliasTy), builtinTypes->anyType);
reportError(alias->nameLocation, OccursCheckFailed{});
}
else
asMutable(aliasTy)->ty.emplace<BoundType>(ty);
emplaceType<BoundType>(asMutable(aliasTy), ty);

std::vector<TypeId> typeParams;
for (auto tyParam : createGenerics(*defnScope, alias->generics, /* useCache */ true, /* addTypes */ false))
Expand Down Expand Up @@ -1856,12 +1863,12 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprLocal* local)

// if we have a refinement key, we can look up its type.
if (key)
maybeTy = lookup(scope, key->def);
maybeTy = lookup(scope, local->location, key->def);

// if the current def doesn't have a type, we might be doing a compound assignment
// and therefore might need to look at the rvalue def instead.
if (!maybeTy && rvalueDef)
maybeTy = lookup(scope, *rvalueDef);
maybeTy = lookup(scope, local->location, *rvalueDef);

if (maybeTy)
{
Expand All @@ -1887,7 +1894,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprGlobal* globa
/* prepopulateGlobalScope() has already added all global functions to the environment by this point, so any
* global that is not already in-scope is definitely an unknown symbol.
*/
if (auto ty = lookup(scope, def, /*prototype=*/false))
if (auto ty = lookup(scope, global->location, def, /*prototype=*/false))
{
rootScope->lvalueTypes[def] = *ty;
return Inference{*ty, refinementArena.proposition(key, builtinTypes->truthyType)};
Expand All @@ -1899,14 +1906,14 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprGlobal* globa
}
}

Inference ConstraintGenerator::checkIndexName(const ScopePtr& scope, const RefinementKey* key, AstExpr* indexee, std::string index)
Inference ConstraintGenerator::checkIndexName(const ScopePtr& scope, const RefinementKey* key, AstExpr* indexee, const std::string& index, Location indexLocation)
{
TypeId obj = check(scope, indexee).ty;
TypeId result = arena->addType(BlockedType{});

if (key)
{
if (auto ty = lookup(scope, key->def))
if (auto ty = lookup(scope, indexLocation, key->def))
return Inference{*ty, refinementArena.proposition(key, builtinTypes->truthyType)};

scope->rvalueRefinements[key->def] = result;
Expand All @@ -1925,15 +1932,15 @@ Inference ConstraintGenerator::checkIndexName(const ScopePtr& scope, const Refin
Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprIndexName* indexName)
{
const RefinementKey* key = dfg->getRefinementKey(indexName);
return checkIndexName(scope, key, indexName->expr, indexName->index.value);
return checkIndexName(scope, key, indexName->expr, indexName->index.value, indexName->indexLocation);
}

Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprIndexExpr* indexExpr)
{
if (auto constantString = indexExpr->index->as<AstExprConstantString>())
{
const RefinementKey* key = dfg->getRefinementKey(indexExpr);
return checkIndexName(scope, key, indexExpr->expr, constantString->value.data);
return checkIndexName(scope, key, indexExpr->expr, constantString->value.data, indexExpr->location);
}

TypeId obj = check(scope, indexExpr->expr).ty;
Expand All @@ -1944,7 +1951,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprIndexExpr* in
const RefinementKey* key = dfg->getRefinementKey(indexExpr);
if (key)
{
if (auto ty = lookup(scope, key->def))
if (auto ty = lookup(scope, indexExpr->location, key->def))
return Inference{*ty, refinementArena.proposition(key, builtinTypes->truthyType)};

scope->rvalueRefinements[key->def] = result;
Expand Down Expand Up @@ -2326,7 +2333,7 @@ std::tuple<TypeId, TypeId, RefinementId> ConstraintGenerator::checkBinary(
TypeId ty = follow(typeFun->type);

// We're only interested in the root class of any classes.
if (auto ctv = get<ClassType>(ty); !ctv || ctv->parent == builtinTypes->classType)
if (auto ctv = get<ClassType>(ty); ctv && ctv->parent == builtinTypes->classType)
discriminantTy = ty;
}

Expand Down Expand Up @@ -2427,9 +2434,7 @@ ConstraintGenerator::LValueBounds ConstraintGenerator::checkLValue(const ScopePt
// TODO: Need to clip this, but this requires more code to be reworked first before we can clip this.
std::optional<TypeId> assignedTy = arena->addType(BlockedType{});

auto unpackC = addConstraint(scope, local->location,
UnpackConstraint{arena->addTypePack({*ty}), arena->addTypePack({*assignedTy}),
/*resultIsLValue*/ true});
auto unpackC = addConstraint(scope, local->location, Unpack1Constraint{*ty, *assignedTy, /*resultIsLValue*/ true});

if (auto blocked = get<BlockedType>(*ty))
{
Expand Down Expand Up @@ -2834,11 +2839,11 @@ ConstraintGenerator::FunctionSignature ConstraintGenerator::checkFunctionSignatu
// generate constraints for return types, we have a guarantee that we
// know the annotated return type already, if one was provided.
LUAU_ASSERT(get<FreeTypePack>(returnType));
asMutable(returnType)->ty.emplace<BoundTypePack>(annotatedRetType);
emplaceTypePack<BoundTypePack>(asMutable(returnType), annotatedRetType);
}
else if (expectedFunction)
{
asMutable(returnType)->ty.emplace<BoundTypePack>(expectedFunction->retTypes);
emplaceTypePack<BoundTypePack>(asMutable(returnType), expectedFunction->retTypes);
}

// TODO: Preserve argument names in the function's type.
Expand Down Expand Up @@ -3386,7 +3391,7 @@ void ConstraintGenerator::fillInInferredBindings(const ScopePtr& globalScope, As
std::move(tys),
{},
},
globalScope, Location{});
globalScope, location);

scope->bindings[symbol] = Binding{ty, location};
}
Expand Down

0 comments on commit 259e509

Please sign in to comment.