Skip to content

Commit

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

- Variety of bugfixes in the new solver

## New Solver

- Fix an issue where we would hit a recursion limit when applying long
chains of type refinements.
- Weaken the types of `table.freeze` and `table.clone` in the new solver
so we can accept common code patterns like `local a = table.freeze({x=5,
x=0})` at the expense of accepting code like `table.freeze(true)`.
- Don't warn when the # operator is used on a value of type never

## VM
- Fix a bug in lua_resume where too many values might be removed from
stack when resume throws an error

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

---------

Co-authored-by: Aaron Weiss <[email protected]>
Co-authored-by: Alexander McCord <[email protected]>
Co-authored-by: Andy Friesen <[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]>
Co-authored-by: Junseo Yoo <[email protected]>
  • Loading branch information
9 people authored Aug 16, 2024
1 parent 1788e23 commit 25f91aa
Show file tree
Hide file tree
Showing 38 changed files with 1,462 additions and 898 deletions.
33 changes: 7 additions & 26 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(LuauDeclarationExtraPropData)

namespace Luau
{

Expand Down Expand Up @@ -898,19 +896,11 @@ struct AstJsonEncoder : public AstVisitor
{
// TODO: attributes
PROP(name);

if (FFlag::LuauDeclarationExtraPropData)
PROP(nameLocation);

PROP(nameLocation);
PROP(params);

if (FFlag::LuauDeclarationExtraPropData)
{
PROP(paramNames);
PROP(vararg);
PROP(varargLocation);
}

PROP(paramNames);
PROP(vararg);
PROP(varargLocation);
PROP(retTypes);
PROP(generics);
PROP(genericPacks);
Expand All @@ -926,10 +916,7 @@ struct AstJsonEncoder : public AstVisitor
[&]()
{
PROP(name);

if (FFlag::LuauDeclarationExtraPropData)
PROP(nameLocation);

PROP(nameLocation);
PROP(type);
}
);
Expand All @@ -940,16 +927,10 @@ struct AstJsonEncoder : public AstVisitor
writeRaw("{");
bool c = pushComma();
write("name", prop.name);

if (FFlag::LuauDeclarationExtraPropData)
write("nameLocation", prop.nameLocation);

write("nameLocation", prop.nameLocation);
writeType("AstDeclaredClassProp");
write("luauType", prop.ty);

if (FFlag::LuauDeclarationExtraPropData)
write("location", prop.location);

write("location", prop.location);
popComma(c);
writeRaw("}");
}
Expand Down
22 changes: 19 additions & 3 deletions Analysis/src/BuiltinDefinitions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -374,9 +374,25 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC

if (TableType* ttv = getMutable<TableType>(getGlobalBinding(globals, "table")))
{
// tabTy is a generic table type which we can't express via declaration syntax yet
ttv->props["freeze"] = makeProperty(makeFunction(arena, std::nullopt, {tabTy}, {tabTy}), "@luau/global/table.freeze");
ttv->props["clone"] = makeProperty(makeFunction(arena, std::nullopt, {tabTy}, {tabTy}), "@luau/global/table.clone");
if (FFlag::DebugLuauDeferredConstraintResolution)
{
// CLI-114044 - The new solver does not yet support generic tables,
// which act, in an odd way, like generics that are constrained to
// the top table type. We do the best we can by modelling these
// functions using unconstrained generics. It's not quite right,
// but it'll be ok for now.
TypeId genericTy = arena.addType(GenericType{"T"});
TypePackId thePack = arena.addTypePack({genericTy});
TypeId idTy = arena.addType(FunctionType{{genericTy}, {}, thePack, thePack});
ttv->props["freeze"] = makeProperty(idTy, "@luau/global/table.freeze");
ttv->props["clone"] = makeProperty(idTy, "@luau/global/table.clone");
}
else
{
// tabTy is a generic table type which we can't express via declaration syntax yet
ttv->props["freeze"] = makeProperty(makeFunction(arena, std::nullopt, {tabTy}, {tabTy}), "@luau/global/table.freeze");
ttv->props["clone"] = makeProperty(makeFunction(arena, std::nullopt, {tabTy}, {tabTy}), "@luau/global/table.clone");
}

ttv->props["getn"].deprecated = true;
ttv->props["getn"].deprecatedSuggestion = "#";
Expand Down
143 changes: 75 additions & 68 deletions Analysis/src/ConstraintGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
LUAU_FASTINT(LuauCheckRecursionLimit);
LUAU_FASTFLAG(DebugLuauLogSolverToJson);
LUAU_FASTFLAG(DebugLuauMagicTypes);
LUAU_FASTFLAG(LuauDeclarationExtraPropData);

namespace Luau
{
Expand Down Expand Up @@ -550,6 +549,13 @@ bool mustDeferIntersection(TypeId ty)
}
} // namespace

enum RefinementsOpKind
{
Intersect,
Refine,
None
};

void ConstraintGenerator::applyRefinements(const ScopePtr& scope, Location location, RefinementId refinement)
{
if (!refinement)
Expand All @@ -558,6 +564,23 @@ void ConstraintGenerator::applyRefinements(const ScopePtr& scope, Location locat
RefinementContext refinements;
std::vector<ConstraintV> constraints;
computeRefinement(scope, location, refinement, &refinements, /*sense*/ true, /*eq*/ false, &constraints);
auto flushConstraints = [this, &scope, &location](RefinementsOpKind kind, TypeId ty, std::vector<TypeId>& discriminants)
{
if (discriminants.empty())
return ty;
if (kind == RefinementsOpKind::None)
{
LUAU_ASSERT(false);
return ty;
}
std::vector<TypeId> args = {ty};
const TypeFunction& func = kind == RefinementsOpKind::Intersect ? builtinTypeFunctions().intersectFunc : builtinTypeFunctions().refineFunc;
LUAU_ASSERT(!func.name.empty());
args.insert(args.end(), discriminants.begin(), discriminants.end());
TypeId resultType = createTypeFunctionInstance(func, args, {}, scope, location);
discriminants.clear();
return resultType;
};

for (auto& [def, partition] : refinements)
{
Expand All @@ -566,41 +589,52 @@ void ConstraintGenerator::applyRefinements(const ScopePtr& scope, Location locat
TypeId ty = *defTy;
if (partition.shouldAppendNilType)
ty = arena->addType(UnionType{{ty, builtinTypes->nilType}});

// Intersect ty with every discriminant type. If either type is not
// sufficiently solved, we queue the intersection up via an
// IntersectConstraint.

// For each discriminant ty, we accumulated it onto ty, creating a longer and longer
// sequence of refine constraints. On every loop of this we called mustDeferIntersection.
// For sufficiently large types, we would blow the stack.
// Instead, we record all the discriminant types in sequence
// and then dispatch a single refine constraint with multiple arguments. This helps us avoid
// the potentially expensive check on mustDeferIntersection
std::vector<TypeId> discriminants;
RefinementsOpKind kind = RefinementsOpKind::None;
bool mustDefer = mustDeferIntersection(ty);
for (TypeId dt : partition.discriminantTypes)
{
if (mustDeferIntersection(ty) || mustDeferIntersection(dt))
mustDefer = mustDefer || mustDeferIntersection(dt);
if (mustDefer)
{
TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().refineFunc, {ty, dt}, {}, scope, location);
if (kind == RefinementsOpKind::Intersect)
ty = flushConstraints(kind, ty, discriminants);
kind = RefinementsOpKind::Refine;

ty = resultType;
discriminants.push_back(dt);
}
else
{
switch (shouldSuppressErrors(normalizer, ty))
{
case ErrorSuppression::DoNotSuppress:
ErrorSuppression status = shouldSuppressErrors(normalizer, ty);
if (status == ErrorSuppression::NormalizationFailed)
reportError(location, NormalizationTooComplex{});
if (kind == RefinementsOpKind::Refine)
ty = flushConstraints(kind, ty, discriminants);
kind = RefinementsOpKind::Intersect;

discriminants.push_back(dt);

if (status == ErrorSuppression::Suppress)
{
if (!get<NeverType>(follow(ty)))
ty = makeIntersect(scope, location, ty, dt);
break;
}
case ErrorSuppression::Suppress:
ty = makeIntersect(scope, location, ty, dt);
ty = flushConstraints(kind, ty, discriminants);
ty = makeUnion(scope, location, ty, builtinTypes->errorType);
break;
case ErrorSuppression::NormalizationFailed:
reportError(location, NormalizationTooComplex{});
ty = makeIntersect(scope, location, ty, dt);
break;
}
}
}

// Finalize - if there are any discriminants left, make one big constraint for refining them
if (kind != RefinementsOpKind::None)
ty = flushConstraints(kind, ty, discriminants);

scope->rvalueRefinements[def] = ty;
}
}
Expand Down Expand Up @@ -1532,30 +1566,24 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareClas

ftv->hasSelf = true;

if (FFlag::LuauDeclarationExtraPropData)
{
FunctionDefinition defn;
FunctionDefinition defn;

defn.definitionModuleName = module->name;
defn.definitionLocation = prop.location;
// No data is preserved for varargLocation
defn.originalNameLocation = prop.nameLocation;
defn.definitionModuleName = module->name;
defn.definitionLocation = prop.location;
// No data is preserved for varargLocation
defn.originalNameLocation = prop.nameLocation;

ftv->definition = defn;
}
ftv->definition = defn;
}
}

TableType::Props& props = assignToMetatable ? metatable->props : ctv->props;

if (props.count(propName) == 0)
{
if (FFlag::LuauDeclarationExtraPropData)
props[propName] = {propTy, /*deprecated*/ false, /*deprecatedSuggestion*/ "", prop.location};
else
props[propName] = {propTy};
props[propName] = {propTy, /*deprecated*/ false, /*deprecatedSuggestion*/ "", prop.location};
}
else if (FFlag::LuauDeclarationExtraPropData)
else
{
Luau::Property& prop = props[propName];
TypeId currentTy = prop.type();
Expand Down Expand Up @@ -1583,31 +1611,6 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareClas
reportError(declaredClass->location, GenericError{format("Cannot overload non-function class member '%s'", propName.c_str())});
}
}
else
{
TypeId currentTy = props[propName].type();

// We special-case this logic to keep the intersection flat; otherwise we
// would create a ton of nested intersection types.
if (const IntersectionType* itv = get<IntersectionType>(currentTy))
{
std::vector<TypeId> options = itv->parts;
options.push_back(propTy);
TypeId newItv = arena->addType(IntersectionType{std::move(options)});

props[propName] = {newItv};
}
else if (get<FunctionType>(currentTy))
{
TypeId intersection = arena->addType(IntersectionType{{currentTy, propTy}});

props[propName] = {intersection};
}
else
{
reportError(declaredClass->location, GenericError{format("Cannot overload non-function class member '%s'", propName.c_str())});
}
}
}

return ControlFlow::None;
Expand Down Expand Up @@ -1641,13 +1644,10 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareFunc

FunctionDefinition defn;

if (FFlag::LuauDeclarationExtraPropData)
{
defn.definitionModuleName = module->name;
defn.definitionLocation = global->location;
defn.varargLocation = global->vararg ? std::make_optional(global->varargLocation) : std::nullopt;
defn.originalNameLocation = global->nameLocation;
}
defn.definitionModuleName = module->name;
defn.definitionLocation = global->location;
defn.varargLocation = global->vararg ? std::make_optional(global->varargLocation) : std::nullopt;
defn.originalNameLocation = global->nameLocation;

TypeId fnType = arena->addType(FunctionType{TypeLevel{}, funScope.get(), std::move(genericTys), std::move(genericTps), paramPack, retPack, defn});
FunctionType* ftv = getMutable<FunctionType>(fnType);
Expand Down Expand Up @@ -1989,7 +1989,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExpr* expr, std::
Inference result;

if (auto group = expr->as<AstExprGroup>())
result = check(scope, group->expr, expectedType, forceSingleton);
result = check(scope, group->expr, expectedType, forceSingleton, generalize);
else if (auto stringExpr = expr->as<AstExprConstantString>())
result = check(scope, stringExpr, expectedType, forceSingleton);
else if (expr->is<AstExprConstantNumber>())
Expand Down Expand Up @@ -2188,6 +2188,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprIndexExpr* in
{
if (auto constantString = indexExpr->index->as<AstExprConstantString>())
{
module->astTypes[indexExpr->index] = builtinTypes->stringType;
const RefinementKey* key = dfg->getRefinementKey(indexExpr);
return checkIndexName(scope, key, indexExpr->expr, constantString->value.data, indexExpr->location);
}
Expand Down Expand Up @@ -3005,7 +3006,13 @@ TypeId ConstraintGenerator::resolveType(const ScopePtr& scope, AstType* ty, bool
}
else if (p.typePack)
{
packParameters.push_back(resolveTypePack(scope, p.typePack, /* inTypeArguments */ true));
TypePackId tp = resolveTypePack(scope, p.typePack, /*inTypeArguments*/ true);

// If we need more regular types, we can use single element type packs to fill those in
if (parameters.size() < alias->typeParams.size() && size(tp) == 1 && finite(tp) && first(tp))
parameters.push_back(*first(tp));
else
packParameters.push_back(tp);
}
else
{
Expand Down
15 changes: 14 additions & 1 deletion Analysis/src/ConstraintSolver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1271,6 +1271,8 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons

if (occursCheckPassed && c.callSite)
(*c.astOverloadResolvedTypes)[c.callSite] = inferredTy;
else if (!occursCheckPassed)
reportError(OccursCheckFailed{}, constraint->location);

InstantiationQueuer queuer{constraint->scope, constraint->location, this};
queuer.traverse(overloadToUse);
Expand All @@ -1281,6 +1283,14 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
return true;
}

static AstExpr* unwrapGroup(AstExpr* expr)
{
while (auto group = expr->as<AstExprGroup>())
expr = group->expr;

return expr;
}

bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<const Constraint> constraint)
{
TypeId fn = follow(c.fn);
Expand Down Expand Up @@ -1354,7 +1364,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<con
{
const TypeId expectedArgTy = follow(expectedArgs[i + typeOffset]);
const TypeId actualArgTy = follow(argPackHead[i + typeOffset]);
const AstExpr* expr = c.callSite->args.data[i];
const AstExpr* expr = unwrapGroup(c.callSite->args.data[i]);

(*c.astExpectedTypes)[expr] = expectedArgTy;

Expand Down Expand Up @@ -1697,7 +1707,10 @@ bool ConstraintSolver::tryDispatch(const AssignPropConstraint& c, NotNull<const
{
const Property* prop = lookupClassProp(lhsClass, propName);
if (!prop || !prop->writeTy.has_value())
{
bind(constraint, c.propType, builtinTypes->anyType);
return true;
}

bind(constraint, c.propType, *prop->writeTy);
unify(constraint, rhsType, *prop->writeTy);
Expand Down
Loading

0 comments on commit 25f91aa

Please sign in to comment.