Merge branch 'upstream' into merge

This commit is contained in:
Vighnesh 2024-08-16 10:15:02 -07:00
commit 3613ee9ddc
38 changed files with 1474 additions and 910 deletions

View File

@ -8,8 +8,6 @@
#include <math.h> #include <math.h>
LUAU_FASTFLAG(LuauDeclarationExtraPropData)
namespace Luau namespace Luau
{ {
@ -898,19 +896,11 @@ struct AstJsonEncoder : public AstVisitor
{ {
// TODO: attributes // TODO: attributes
PROP(name); PROP(name);
PROP(nameLocation);
if (FFlag::LuauDeclarationExtraPropData)
PROP(nameLocation);
PROP(params); PROP(params);
PROP(paramNames);
if (FFlag::LuauDeclarationExtraPropData) PROP(vararg);
{ PROP(varargLocation);
PROP(paramNames);
PROP(vararg);
PROP(varargLocation);
}
PROP(retTypes); PROP(retTypes);
PROP(generics); PROP(generics);
PROP(genericPacks); PROP(genericPacks);
@ -926,10 +916,7 @@ struct AstJsonEncoder : public AstVisitor
[&]() [&]()
{ {
PROP(name); PROP(name);
PROP(nameLocation);
if (FFlag::LuauDeclarationExtraPropData)
PROP(nameLocation);
PROP(type); PROP(type);
} }
); );
@ -940,16 +927,10 @@ struct AstJsonEncoder : public AstVisitor
writeRaw("{"); writeRaw("{");
bool c = pushComma(); bool c = pushComma();
write("name", prop.name); write("name", prop.name);
write("nameLocation", prop.nameLocation);
if (FFlag::LuauDeclarationExtraPropData)
write("nameLocation", prop.nameLocation);
writeType("AstDeclaredClassProp"); writeType("AstDeclaredClassProp");
write("luauType", prop.ty); write("luauType", prop.ty);
write("location", prop.location);
if (FFlag::LuauDeclarationExtraPropData)
write("location", prop.location);
popComma(c); popComma(c);
writeRaw("}"); writeRaw("}");
} }

View File

@ -374,9 +374,25 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
if (TableType* ttv = getMutable<TableType>(getGlobalBinding(globals, "table"))) if (TableType* ttv = getMutable<TableType>(getGlobalBinding(globals, "table")))
{ {
// tabTy is a generic table type which we can't express via declaration syntax yet if (FFlag::DebugLuauDeferredConstraintResolution)
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"); // 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"].deprecated = true;
ttv->props["getn"].deprecatedSuggestion = "#"; ttv->props["getn"].deprecatedSuggestion = "#";

View File

@ -29,7 +29,6 @@
LUAU_FASTINT(LuauCheckRecursionLimit); LUAU_FASTINT(LuauCheckRecursionLimit);
LUAU_FASTFLAG(DebugLuauLogSolverToJson); LUAU_FASTFLAG(DebugLuauLogSolverToJson);
LUAU_FASTFLAG(DebugLuauMagicTypes); LUAU_FASTFLAG(DebugLuauMagicTypes);
LUAU_FASTFLAG(LuauDeclarationExtraPropData);
namespace Luau namespace Luau
{ {
@ -550,6 +549,13 @@ bool mustDeferIntersection(TypeId ty)
} }
} // namespace } // namespace
enum RefinementsOpKind
{
Intersect,
Refine,
None
};
void ConstraintGenerator::applyRefinements(const ScopePtr& scope, Location location, RefinementId refinement) void ConstraintGenerator::applyRefinements(const ScopePtr& scope, Location location, RefinementId refinement)
{ {
if (!refinement) if (!refinement)
@ -558,6 +564,23 @@ void ConstraintGenerator::applyRefinements(const ScopePtr& scope, Location locat
RefinementContext refinements; RefinementContext refinements;
std::vector<ConstraintV> constraints; std::vector<ConstraintV> constraints;
computeRefinement(scope, location, refinement, &refinements, /*sense*/ true, /*eq*/ false, &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) for (auto& [def, partition] : refinements)
{ {
@ -566,41 +589,52 @@ void ConstraintGenerator::applyRefinements(const ScopePtr& scope, Location locat
TypeId ty = *defTy; TypeId ty = *defTy;
if (partition.shouldAppendNilType) if (partition.shouldAppendNilType)
ty = arena->addType(UnionType{{ty, builtinTypes->nilType}}); ty = arena->addType(UnionType{{ty, builtinTypes->nilType}});
// Intersect ty with every discriminant type. If either type is not // Intersect ty with every discriminant type. If either type is not
// sufficiently solved, we queue the intersection up via an // sufficiently solved, we queue the intersection up via an
// IntersectConstraint. // 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) 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 else
{ {
switch (shouldSuppressErrors(normalizer, ty)) ErrorSuppression status = shouldSuppressErrors(normalizer, ty);
{ if (status == ErrorSuppression::NormalizationFailed)
case ErrorSuppression::DoNotSuppress:
{
if (!get<NeverType>(follow(ty)))
ty = makeIntersect(scope, location, ty, dt);
break;
}
case ErrorSuppression::Suppress:
ty = makeIntersect(scope, location, ty, dt);
ty = makeUnion(scope, location, ty, builtinTypes->errorType);
break;
case ErrorSuppression::NormalizationFailed:
reportError(location, NormalizationTooComplex{}); reportError(location, NormalizationTooComplex{});
ty = makeIntersect(scope, location, ty, dt); if (kind == RefinementsOpKind::Refine)
break; ty = flushConstraints(kind, ty, discriminants);
kind = RefinementsOpKind::Intersect;
discriminants.push_back(dt);
if (status == ErrorSuppression::Suppress)
{
ty = flushConstraints(kind, ty, discriminants);
ty = makeUnion(scope, location, ty, builtinTypes->errorType);
} }
} }
} }
// 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; scope->rvalueRefinements[def] = ty;
} }
} }
@ -1532,17 +1566,14 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareClas
ftv->hasSelf = true; ftv->hasSelf = true;
if (FFlag::LuauDeclarationExtraPropData) FunctionDefinition defn;
{
FunctionDefinition defn;
defn.definitionModuleName = module->name; defn.definitionModuleName = module->name;
defn.definitionLocation = prop.location; defn.definitionLocation = prop.location;
// No data is preserved for varargLocation // No data is preserved for varargLocation
defn.originalNameLocation = prop.nameLocation; defn.originalNameLocation = prop.nameLocation;
ftv->definition = defn; ftv->definition = defn;
}
} }
} }
@ -1550,12 +1581,9 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareClas
if (props.count(propName) == 0) if (props.count(propName) == 0)
{ {
if (FFlag::LuauDeclarationExtraPropData) props[propName] = {propTy, /*deprecated*/ false, /*deprecatedSuggestion*/ "", prop.location};
props[propName] = {propTy, /*deprecated*/ false, /*deprecatedSuggestion*/ "", prop.location};
else
props[propName] = {propTy};
} }
else if (FFlag::LuauDeclarationExtraPropData) else
{ {
Luau::Property& prop = props[propName]; Luau::Property& prop = props[propName];
TypeId currentTy = prop.type(); TypeId currentTy = prop.type();
@ -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())}); 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; return ControlFlow::None;
@ -1641,13 +1644,10 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareFunc
FunctionDefinition defn; FunctionDefinition defn;
if (FFlag::LuauDeclarationExtraPropData) defn.definitionModuleName = module->name;
{ defn.definitionLocation = global->location;
defn.definitionModuleName = module->name; defn.varargLocation = global->vararg ? std::make_optional(global->varargLocation) : std::nullopt;
defn.definitionLocation = global->location; defn.originalNameLocation = global->nameLocation;
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}); TypeId fnType = arena->addType(FunctionType{TypeLevel{}, funScope.get(), std::move(genericTys), std::move(genericTps), paramPack, retPack, defn});
FunctionType* ftv = getMutable<FunctionType>(fnType); FunctionType* ftv = getMutable<FunctionType>(fnType);
@ -1989,7 +1989,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExpr* expr, std::
Inference result; Inference result;
if (auto group = expr->as<AstExprGroup>()) 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>()) else if (auto stringExpr = expr->as<AstExprConstantString>())
result = check(scope, stringExpr, expectedType, forceSingleton); result = check(scope, stringExpr, expectedType, forceSingleton);
else if (expr->is<AstExprConstantNumber>()) else if (expr->is<AstExprConstantNumber>())
@ -2188,6 +2188,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprIndexExpr* in
{ {
if (auto constantString = indexExpr->index->as<AstExprConstantString>()) if (auto constantString = indexExpr->index->as<AstExprConstantString>())
{ {
module->astTypes[indexExpr->index] = builtinTypes->stringType;
const RefinementKey* key = dfg->getRefinementKey(indexExpr); const RefinementKey* key = dfg->getRefinementKey(indexExpr);
return checkIndexName(scope, key, indexExpr->expr, constantString->value.data, indexExpr->location); return checkIndexName(scope, key, indexExpr->expr, constantString->value.data, indexExpr->location);
} }
@ -3005,7 +3006,13 @@ TypeId ConstraintGenerator::resolveType(const ScopePtr& scope, AstType* ty, bool
} }
else if (p.typePack) 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 else
{ {

View File

@ -1271,6 +1271,8 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
if (occursCheckPassed && c.callSite) if (occursCheckPassed && c.callSite)
(*c.astOverloadResolvedTypes)[c.callSite] = inferredTy; (*c.astOverloadResolvedTypes)[c.callSite] = inferredTy;
else if (!occursCheckPassed)
reportError(OccursCheckFailed{}, constraint->location);
InstantiationQueuer queuer{constraint->scope, constraint->location, this}; InstantiationQueuer queuer{constraint->scope, constraint->location, this};
queuer.traverse(overloadToUse); queuer.traverse(overloadToUse);
@ -1281,6 +1283,14 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
return true; 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) bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<const Constraint> constraint)
{ {
TypeId fn = follow(c.fn); TypeId fn = follow(c.fn);
@ -1354,7 +1364,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<con
{ {
const TypeId expectedArgTy = follow(expectedArgs[i + typeOffset]); const TypeId expectedArgTy = follow(expectedArgs[i + typeOffset]);
const TypeId actualArgTy = follow(argPackHead[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; (*c.astExpectedTypes)[expr] = expectedArgTy;
@ -1697,7 +1707,10 @@ bool ConstraintSolver::tryDispatch(const AssignPropConstraint& c, NotNull<const
{ {
const Property* prop = lookupClassProp(lhsClass, propName); const Property* prop = lookupClassProp(lhsClass, propName);
if (!prop || !prop->writeTy.has_value()) if (!prop || !prop->writeTy.has_value())
{
bind(constraint, c.propType, builtinTypes->anyType);
return true; return true;
}
bind(constraint, c.propType, *prop->writeTy); bind(constraint, c.propType, *prop->writeTy);
unify(constraint, rhsType, *prop->writeTy); unify(constraint, rhsType, *prop->writeTy);

View File

@ -37,7 +37,6 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTINT(LuauTarjanChildLimit) LUAU_FASTINT(LuauTarjanChildLimit)
LUAU_FASTFLAG(LuauInferInNoCheckMode) LUAU_FASTFLAG(LuauInferInNoCheckMode)
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false) LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false)
LUAU_FASTFLAGVARIABLE(LuauCancelFromProgress, false)
LUAU_FASTFLAGVARIABLE(LuauStoreCommentsForDefinitionFiles, false) LUAU_FASTFLAGVARIABLE(LuauStoreCommentsForDefinitionFiles, false)
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution) LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false) LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false)
@ -745,15 +744,8 @@ std::vector<ModuleName> Frontend::checkQueuedModules(
if (progress) if (progress)
{ {
if (FFlag::LuauCancelFromProgress) if (!progress(buildQueueItems.size() - remaining, buildQueueItems.size()))
{ cancelled = true;
if (!progress(buildQueueItems.size() - remaining, buildQueueItems.size()))
cancelled = true;
}
else
{
progress(buildQueueItems.size() - remaining, buildQueueItems.size());
}
} }
// Items cannot be submitted while holding the lock // Items cannot be submitted while holding the lock

View File

@ -11,15 +11,12 @@
#include <algorithm> #include <algorithm>
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution) LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
LUAU_FASTFLAG(LuauReusableSubstitutions)
namespace Luau namespace Luau
{ {
void Instantiation::resetState(const TxnLog* log, TypeArena* arena, NotNull<BuiltinTypes> builtinTypes, TypeLevel level, Scope* scope) void Instantiation::resetState(const TxnLog* log, TypeArena* arena, NotNull<BuiltinTypes> builtinTypes, TypeLevel level, Scope* scope)
{ {
LUAU_ASSERT(FFlag::LuauReusableSubstitutions);
Substitution::resetState(log, arena); Substitution::resetState(log, arena);
this->builtinTypes = builtinTypes; this->builtinTypes = builtinTypes;
@ -71,26 +68,13 @@ TypeId Instantiation::clean(TypeId ty)
clone.argNames = ftv->argNames; clone.argNames = ftv->argNames;
TypeId result = addType(std::move(clone)); TypeId result = addType(std::move(clone));
if (FFlag::LuauReusableSubstitutions) // Annoyingly, we have to do this even if there are no generics,
{ // to replace any generic tables.
// Annoyingly, we have to do this even if there are no generics, reusableReplaceGenerics.resetState(log, arena, builtinTypes, level, scope, ftv->generics, ftv->genericPacks);
// to replace any generic tables.
reusableReplaceGenerics.resetState(log, arena, builtinTypes, level, scope, ftv->generics, ftv->genericPacks);
// TODO: What to do if this returns nullopt? // TODO: What to do if this returns nullopt?
// We don't have access to the error-reporting machinery // We don't have access to the error-reporting machinery
result = reusableReplaceGenerics.substitute(result).value_or(result); result = reusableReplaceGenerics.substitute(result).value_or(result);
}
else
{
// Annoyingly, we have to do this even if there are no generics,
// to replace any generic tables.
ReplaceGenerics replaceGenerics{log, arena, builtinTypes, level, scope, ftv->generics, ftv->genericPacks};
// TODO: What to do if this returns nullopt?
// We don't have access to the error-reporting machinery
result = replaceGenerics.substitute(result).value_or(result);
}
asMutable(result)->documentationSymbol = ty->documentationSymbol; asMutable(result)->documentationSymbol = ty->documentationSymbol;
return result; return result;
@ -112,8 +96,6 @@ void ReplaceGenerics::resetState(
const std::vector<TypePackId>& genericPacks const std::vector<TypePackId>& genericPacks
) )
{ {
LUAU_ASSERT(FFlag::LuauReusableSubstitutions);
Substitution::resetState(log, arena); Substitution::resetState(log, arena);
this->builtinTypes = builtinTypes; this->builtinTypes = builtinTypes;

View File

@ -231,14 +231,17 @@ std::pair<OverloadResolver::Analysis, ErrorVec> OverloadResolver::checkOverload_
// is ok. // is ok.
const size_t firstUnsatisfiedArgument = args->head.size(); const size_t firstUnsatisfiedArgument = args->head.size();
const auto [requiredHead, _requiredTail] = flatten(fn->argTypes); const auto [requiredHead, requiredTail] = flatten(fn->argTypes);
bool isVariadic = requiredTail && Luau::isVariadic(*requiredTail);
// If too many arguments were supplied, this overload // If too many arguments were supplied, this overload
// definitely does not match. // definitely does not match.
if (args->head.size() > requiredHead.size()) if (args->head.size() > requiredHead.size())
{ {
auto [minParams, optMaxParams] = getParameterExtents(TxnLog::empty(), fn->argTypes); auto [minParams, optMaxParams] = getParameterExtents(TxnLog::empty(), fn->argTypes);
TypeError error{fnExpr->location, CountMismatch{minParams, optMaxParams, args->head.size(), CountMismatch::Arg, false}};
TypeError error{fnExpr->location, CountMismatch{minParams, optMaxParams, args->head.size(), CountMismatch::Arg, isVariadic}};
return {Analysis::ArityMismatch, {error}}; return {Analysis::ArityMismatch, {error}};
} }
@ -250,7 +253,7 @@ std::pair<OverloadResolver::Analysis, ErrorVec> OverloadResolver::checkOverload_
if (!subtyping.isSubtype(builtinTypes->nilType, requiredHead[i]).isSubtype) if (!subtyping.isSubtype(builtinTypes->nilType, requiredHead[i]).isSubtype)
{ {
auto [minParams, optMaxParams] = getParameterExtents(TxnLog::empty(), fn->argTypes); auto [minParams, optMaxParams] = getParameterExtents(TxnLog::empty(), fn->argTypes);
TypeError error{fnExpr->location, CountMismatch{minParams, optMaxParams, args->head.size(), CountMismatch::Arg, false}}; TypeError error{fnExpr->location, CountMismatch{minParams, optMaxParams, args->head.size(), CountMismatch::Arg, isVariadic}};
return {Analysis::ArityMismatch, {error}}; return {Analysis::ArityMismatch, {error}};
} }

View File

@ -11,7 +11,6 @@
LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000) LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000)
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
LUAU_FASTINTVARIABLE(LuauTarjanPreallocationSize, 256); LUAU_FASTINTVARIABLE(LuauTarjanPreallocationSize, 256);
LUAU_FASTFLAG(LuauReusableSubstitutions)
namespace Luau namespace Luau
{ {
@ -148,8 +147,8 @@ static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log, bool a
} }
Tarjan::Tarjan() Tarjan::Tarjan()
: typeToIndex(nullptr, FFlag::LuauReusableSubstitutions ? FInt::LuauTarjanPreallocationSize : 0) : typeToIndex(nullptr, FInt::LuauTarjanPreallocationSize)
, packToIndex(nullptr, FFlag::LuauReusableSubstitutions ? FInt::LuauTarjanPreallocationSize : 0) , packToIndex(nullptr, FInt::LuauTarjanPreallocationSize)
{ {
nodes.reserve(FInt::LuauTarjanPreallocationSize); nodes.reserve(FInt::LuauTarjanPreallocationSize);
stack.reserve(FInt::LuauTarjanPreallocationSize); stack.reserve(FInt::LuauTarjanPreallocationSize);
@ -452,28 +451,17 @@ TarjanResult Tarjan::visitRoot(TypePackId tp)
void Tarjan::clearTarjan(const TxnLog* log) void Tarjan::clearTarjan(const TxnLog* log)
{ {
if (FFlag::LuauReusableSubstitutions) typeToIndex.clear(~0u);
{ packToIndex.clear(~0u);
typeToIndex.clear(~0u);
packToIndex.clear(~0u);
}
else
{
typeToIndex.clear();
packToIndex.clear();
}
nodes.clear(); nodes.clear();
stack.clear(); stack.clear();
if (FFlag::LuauReusableSubstitutions) childCount = 0;
{ // childLimit setting stays the same
childCount = 0;
// childLimit setting stays the same
this->log = log; this->log = log;
}
edgesTy.clear(); edgesTy.clear();
edgesTp.clear(); edgesTp.clear();
@ -629,8 +617,6 @@ std::optional<TypePackId> Substitution::substitute(TypePackId tp)
void Substitution::resetState(const TxnLog* log, TypeArena* arena) void Substitution::resetState(const TxnLog* log, TypeArena* arena)
{ {
LUAU_ASSERT(FFlag::LuauReusableSubstitutions);
clearTarjan(log); clearTarjan(log);
this->arena = arena; this->arena = arena;

View File

@ -627,6 +627,11 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
result = isCovariantWith(env, p); result = isCovariantWith(env, p);
else if (auto p = get2<SingletonType, SingletonType>(subTy, superTy)) else if (auto p = get2<SingletonType, SingletonType>(subTy, superTy))
result = isCovariantWith(env, p); result = isCovariantWith(env, p);
else if (auto p = get2<FunctionType, PrimitiveType>(subTy, superTy))
{
auto [subFunction, superPrimitive] = p;
result.isSubtype = superPrimitive->type == PrimitiveType::Function;
}
else if (auto p = get2<FunctionType, FunctionType>(subTy, superTy)) else if (auto p = get2<FunctionType, FunctionType>(subTy, superTy))
result = isCovariantWith(env, p); result = isCovariantWith(env, p);
else if (auto p = get2<TableType, TableType>(subTy, superTy)) else if (auto p = get2<TableType, TableType>(subTy, superTy))

View File

@ -389,6 +389,10 @@ TypeId matchLiteralType(
TypeId tProp = follow(*propTy); TypeId tProp = follow(*propTy);
if (get<BlockedType>(tProp)) if (get<BlockedType>(tProp))
toBlock.push_back(tProp); toBlock.push_back(tProp);
// Populate expected types for non-string keys declared with [] (the code below will handle the case where they are strings)
if (!item.key->as<AstExprConstantString>() && expectedTableTy->indexer)
(*astExpectedTypes)[item.key] = expectedTableTy->indexer->indexType;
} }
else else
LUAU_ASSERT(!"Unexpected"); LUAU_ASSERT(!"Unexpected");

View File

@ -1445,37 +1445,40 @@ struct TypeChecker2
else else
LUAU_ASSERT(!"Generating the best possible error from this function call resolution was inexhaustive?"); LUAU_ASSERT(!"Generating the best possible error from this function call resolution was inexhaustive?");
if (resolver.arityMismatches.size() > 1 || resolver.nonviableOverloads.size() > 1) if (resolver.nonviableOverloads.size() <= 1 && resolver.arityMismatches.size() <= 1)
return;
std::string s = "Available overloads: ";
std::vector<TypeId> overloads;
if (resolver.nonviableOverloads.empty())
{ {
std::string s = "Available overloads: "; for (const auto& [ty, p] : resolver.resolution)
std::vector<TypeId> overloads;
if (resolver.nonviableOverloads.empty())
{ {
for (const auto& [ty, p] : resolver.resolution) if (p.first == OverloadResolver::TypeIsNotAFunction)
{ continue;
if (p.first == OverloadResolver::TypeIsNotAFunction)
continue;
overloads.push_back(ty); overloads.push_back(ty);
}
} }
else
{
for (const auto& [ty, _] : resolver.nonviableOverloads)
overloads.push_back(ty);
}
for (size_t i = 0; i < overloads.size(); ++i)
{
if (i > 0)
s += (i == overloads.size() - 1) ? "; and " : "; ";
s += toString(overloads[i]);
}
reportError(ExtraInformation{std::move(s)}, call->func->location);
} }
else
{
for (const auto& [ty, _] : resolver.nonviableOverloads)
overloads.push_back(ty);
}
if (overloads.size() <= 1)
return;
for (size_t i = 0; i < overloads.size(); ++i)
{
if (i > 0)
s += (i == overloads.size() - 1) ? "; and " : "; ";
s += toString(overloads[i]);
}
reportError(ExtraInformation{std::move(s)}, call->func->location);
} }
void visit(AstExprCall* call) void visit(AstExprCall* call)
@ -1756,7 +1759,7 @@ struct TypeChecker2
if (get<TypeFunctionInstanceType>(follow(retTy))) if (get<TypeFunctionInstanceType>(follow(retTy)))
{ {
TypeFunctionReductionGuessResult result = guesser.guessTypeFunctionReductionForFunctionExpr(*fn, inferredFtv, retTy); TypeFunctionReductionGuessResult result = guesser.guessTypeFunctionReductionForFunctionExpr(*fn, inferredFtv, retTy);
if (result.shouldRecommendAnnotation) if (result.shouldRecommendAnnotation && !get<UnknownType>(result.guessedReturnType))
reportError( reportError(
ExplicitFunctionAnnotationRecommended{std::move(result.guessedFunctionAnnotations), result.guessedReturnType}, ExplicitFunctionAnnotationRecommended{std::move(result.guessedFunctionAnnotations), result.guessedReturnType},
fn->location fn->location
@ -1838,6 +1841,17 @@ struct TypeChecker2
if (nty && nty->shouldSuppressErrors()) if (nty && nty->shouldSuppressErrors())
return; return;
switch (normalizer.isInhabited(nty.get()))
{
case NormalizationResult::True:
break;
case NormalizationResult::False:
return;
case NormalizationResult::HitLimits:
reportError(NormalizationTooComplex{}, expr->location);
return;
}
if (!hasLength(operandType, seen, &recursionCount)) if (!hasLength(operandType, seen, &recursionCount))
{ {
if (isOptional(operandType)) if (isOptional(operandType))

View File

@ -659,12 +659,9 @@ TypeFunctionReductionResult<TypeId> lenTypeFunction(
if (normTy->shouldSuppressErrors()) if (normTy->shouldSuppressErrors())
return {ctx->builtins->numberType, false, {}, {}}; return {ctx->builtins->numberType, false, {}, {}};
// if we have an uninhabited type (like `never`), we can never observe that the operator didn't work. // # always returns a number, even if its operand is never.
if (inhabited == NormalizationResult::False)
return {ctx->builtins->neverType, false, {}, {}};
// if we're checking the length of a string, that works! // if we're checking the length of a string, that works!
if (normTy->isSubtypeOfString()) if (inhabited == NormalizationResult::False || normTy->isSubtypeOfString())
return {ctx->builtins->numberType, false, {}, {}}; return {ctx->builtins->numberType, false, {}, {}};
// we use the normalized operand here in case there was an intersection or union. // we use the normalized operand here in case there was an intersection or union.
@ -1576,86 +1573,116 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
NotNull<TypeFunctionContext> ctx NotNull<TypeFunctionContext> ctx
) )
{ {
if (typeParams.size() != 2 || !packParams.empty()) if (typeParams.size() < 2 || !packParams.empty())
{ {
ctx->ice->ice("refine type function: encountered a type function instance without the required argument structure"); ctx->ice->ice("refine type function: encountered a type function instance without the required argument structure");
LUAU_ASSERT(false); LUAU_ASSERT(false);
} }
TypeId targetTy = follow(typeParams.at(0)); TypeId targetTy = follow(typeParams.at(0));
TypeId discriminantTy = follow(typeParams.at(1)); std::vector<TypeId> discriminantTypes;
for (size_t i = 1; i < typeParams.size(); i++)
discriminantTypes.push_back(follow(typeParams.at(i)));
// check to see if both operand types are resolved enough, and wait to reduce if not // check to see if both operand types are resolved enough, and wait to reduce if not
if (isPending(targetTy, ctx->solver)) if (isPending(targetTy, ctx->solver))
return {std::nullopt, false, {targetTy}, {}}; return {std::nullopt, false, {targetTy}, {}};
else if (isPending(discriminantTy, ctx->solver)) else
return {std::nullopt, false, {discriminantTy}, {}};
// if either type is free but has only one remaining reference, we can generalize it to its upper bound here.
if (ctx->solver)
{ {
std::optional<TypeId> targetMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, targetTy); for (auto t : discriminantTypes)
std::optional<TypeId> discriminantMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, discriminantTy); {
if (isPending(t, ctx->solver))
if (!targetMaybeGeneralized) return {std::nullopt, false, {t}, {}};
return {std::nullopt, false, {targetTy}, {}}; }
else if (!discriminantMaybeGeneralized)
return {std::nullopt, false, {discriminantTy}, {}};
targetTy = *targetMaybeGeneralized;
discriminantTy = *discriminantMaybeGeneralized;
} }
// Refine a target type and a discriminant one at a time.
// we need a more complex check for blocking on the discriminant in particular // Returns result : TypeId, toBlockOn : vector<TypeId>
FindRefinementBlockers frb; auto stepRefine = [&ctx](TypeId target, TypeId discriminant) -> std::pair<TypeId, std::vector<TypeId>>
frb.traverse(discriminantTy);
if (!frb.found.empty())
return {std::nullopt, false, {frb.found.begin(), frb.found.end()}, {}};
/* 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 (auto nt = get<NegationType>(discriminantTy))
if (get<AnyType>(follow(nt->ty)))
return {targetTy, false, {}, {}};
// If the target type is a table, then simplification already implements the logic to deal with refinements properly since the
// type of the discriminant is guaranteed to only ever be an (arbitrarily-nested) table of a single property type.
if (get<TableType>(targetTy))
{ {
SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, targetTy, discriminantTy); std::vector<TypeId> toBlock;
if (!result.blockedTypes.empty()) if (ctx->solver)
return {std::nullopt, false, {result.blockedTypes.begin(), result.blockedTypes.end()}, {}}; {
std::optional<TypeId> targetMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, target);
std::optional<TypeId> discriminantMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, discriminant);
return {result.result, false, {}, {}}; if (!targetMaybeGeneralized)
return std::pair<TypeId, std::vector<TypeId>>{nullptr, {target}};
else if (!discriminantMaybeGeneralized)
return std::pair<TypeId, std::vector<TypeId>>{nullptr, {discriminant}};
target = *targetMaybeGeneralized;
discriminant = *discriminantMaybeGeneralized;
}
// we need a more complex check for blocking on the discriminant in particular
FindRefinementBlockers frb;
frb.traverse(discriminant);
if (!frb.found.empty())
return {nullptr, {frb.found.begin(), frb.found.end()}};
/* 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 (auto nt = get<NegationType>(discriminant))
if (get<AnyType>(follow(nt->ty)))
return {target, {}};
// If the target type is a table, then simplification already implements the logic to deal with refinements properly since the
// type of the discriminant is guaranteed to only ever be an (arbitrarily-nested) table of a single property type.
if (get<TableType>(target))
{
SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, target, discriminant);
if (!result.blockedTypes.empty())
return {nullptr, {result.blockedTypes.begin(), result.blockedTypes.end()}};
return {result.result, {}};
}
// In the general case, we'll still use normalization though.
TypeId intersection = ctx->arena->addType(IntersectionType{{target, discriminant}});
std::shared_ptr<const NormalizedType> normIntersection = ctx->normalizer->normalize(intersection);
std::shared_ptr<const NormalizedType> normType = ctx->normalizer->normalize(target);
// if the intersection failed to normalize, we can't reduce, but know nothing about inhabitance.
if (!normIntersection || !normType)
return {nullptr, {}};
TypeId resultTy = ctx->normalizer->typeFromNormal(*normIntersection);
// include the error type if the target type is error-suppressing and the intersection we computed is not
if (normType->shouldSuppressErrors() && !normIntersection->shouldSuppressErrors())
resultTy = ctx->arena->addType(UnionType{{resultTy, ctx->builtins->errorType}});
return {resultTy, {}};
};
// refine target with each discriminant type in sequence (reverse of insertion order)
// If we cannot proceed, block. If all discriminant types refine successfully, return
// the result
TypeId target = targetTy;
while (!discriminantTypes.empty())
{
TypeId discriminant = discriminantTypes.back();
auto [refined, blocked] = stepRefine(target, discriminant);
if (blocked.empty() && refined == nullptr)
return {std::nullopt, false, {}, {}};
if (!blocked.empty())
return {std::nullopt, false, blocked, {}};
target = refined;
discriminantTypes.pop_back();
} }
return {target, false, {}, {}};
// In the general case, we'll still use normalization though.
TypeId intersection = ctx->arena->addType(IntersectionType{{targetTy, discriminantTy}});
std::shared_ptr<const NormalizedType> normIntersection = ctx->normalizer->normalize(intersection);
std::shared_ptr<const NormalizedType> normType = ctx->normalizer->normalize(targetTy);
// if the intersection failed to normalize, we can't reduce, but know nothing about inhabitance.
if (!normIntersection || !normType)
return {std::nullopt, false, {}, {}};
TypeId resultTy = ctx->normalizer->typeFromNormal(*normIntersection);
// include the error type if the target type is error-suppressing and the intersection we computed is not
if (normType->shouldSuppressErrors() && !normIntersection->shouldSuppressErrors())
resultTy = ctx->arena->addType(UnionType{{resultTy, ctx->builtins->errorType}});
return {resultTy, false, {}, {}};
} }
TypeFunctionReductionResult<TypeId> singletonTypeFunction( TypeFunctionReductionResult<TypeId> singletonTypeFunction(

View File

@ -34,8 +34,6 @@ LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAGVARIABLE(LuauRemoveBadRelationalOperatorWarning, false) LUAU_FASTFLAGVARIABLE(LuauRemoveBadRelationalOperatorWarning, false)
LUAU_FASTFLAGVARIABLE(LuauOkWithIteratingOverTableProperties, false) LUAU_FASTFLAGVARIABLE(LuauOkWithIteratingOverTableProperties, false)
LUAU_FASTFLAGVARIABLE(LuauReusableSubstitutions, false)
LUAU_FASTFLAG(LuauDeclarationExtraPropData)
namespace Luau namespace Luau
{ {
@ -1756,28 +1754,22 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareClass&
ftv->argTypes = addTypePack(TypePack{{classTy}, ftv->argTypes}); ftv->argTypes = addTypePack(TypePack{{classTy}, ftv->argTypes});
ftv->hasSelf = true; ftv->hasSelf = true;
if (FFlag::LuauDeclarationExtraPropData) FunctionDefinition defn;
{
FunctionDefinition defn;
defn.definitionModuleName = currentModule->name; defn.definitionModuleName = currentModule->name;
defn.definitionLocation = prop.location; defn.definitionLocation = prop.location;
// No data is preserved for varargLocation // No data is preserved for varargLocation
defn.originalNameLocation = prop.nameLocation; defn.originalNameLocation = prop.nameLocation;
ftv->definition = defn; ftv->definition = defn;
}
} }
} }
if (assignTo.count(propName) == 0) if (assignTo.count(propName) == 0)
{ {
if (FFlag::LuauDeclarationExtraPropData) assignTo[propName] = {propTy, /*deprecated*/ false, /*deprecatedSuggestion*/ "", prop.location};
assignTo[propName] = {propTy, /*deprecated*/ false, /*deprecatedSuggestion*/ "", prop.location};
else
assignTo[propName] = {propTy};
} }
else if (FFlag::LuauDeclarationExtraPropData) else
{ {
Luau::Property& prop = assignTo[propName]; Luau::Property& prop = assignTo[propName];
TypeId currentTy = prop.type(); TypeId currentTy = prop.type();
@ -1805,31 +1797,6 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareClass&
reportError(declaredClass.location, GenericError{format("Cannot overload non-function class member '%s'", propName.c_str())}); reportError(declaredClass.location, GenericError{format("Cannot overload non-function class member '%s'", propName.c_str())});
} }
} }
else
{
TypeId currentTy = assignTo[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 = addType(IntersectionType{std::move(options)});
assignTo[propName] = {newItv};
}
else if (get<FunctionType>(currentTy))
{
TypeId intersection = addType(IntersectionType{{currentTy, propTy}});
assignTo[propName] = {intersection};
}
else
{
reportError(declaredClass.location, GenericError{format("Cannot overload non-function class member '%s'", propName.c_str())});
}
}
} }
return ControlFlow::None; return ControlFlow::None;
@ -1870,13 +1837,10 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareFuncti
FunctionDefinition defn; FunctionDefinition defn;
if (FFlag::LuauDeclarationExtraPropData) defn.definitionModuleName = currentModule->name;
{ defn.definitionLocation = global.location;
defn.definitionModuleName = currentModule->name; defn.varargLocation = global.vararg ? std::make_optional(global.varargLocation) : std::nullopt;
defn.definitionLocation = global.location; defn.originalNameLocation = global.nameLocation;
defn.varargLocation = global.vararg ? std::make_optional(global.varargLocation) : std::nullopt;
defn.originalNameLocation = global.nameLocation;
}
TypeId fnType = addType(FunctionType{funScope->level, std::move(genericTys), std::move(genericTps), argPack, retPack, defn}); TypeId fnType = addType(FunctionType{funScope->level, std::move(genericTys), std::move(genericTps), argPack, retPack, defn});
FunctionType* ftv = getMutable<FunctionType>(fnType); FunctionType* ftv = getMutable<FunctionType>(fnType);
@ -4991,24 +4955,12 @@ TypeId TypeChecker::instantiate(const ScopePtr& scope, TypeId ty, Location locat
std::optional<TypeId> instantiated; std::optional<TypeId> instantiated;
if (FFlag::LuauReusableSubstitutions) reusableInstantiation.resetState(log, &currentModule->internalTypes, builtinTypes, scope->level, /*scope*/ nullptr);
{
reusableInstantiation.resetState(log, &currentModule->internalTypes, builtinTypes, scope->level, /*scope*/ nullptr);
if (instantiationChildLimit) if (instantiationChildLimit)
reusableInstantiation.childLimit = *instantiationChildLimit; reusableInstantiation.childLimit = *instantiationChildLimit;
instantiated = reusableInstantiation.substitute(ty); instantiated = reusableInstantiation.substitute(ty);
}
else
{
Instantiation instantiation{log, &currentModule->internalTypes, builtinTypes, scope->level, /*scope*/ nullptr};
if (instantiationChildLimit)
instantiation.childLimit = *instantiationChildLimit;
instantiated = instantiation.substitute(ty);
}
if (instantiated.has_value()) if (instantiated.has_value())
return *instantiated; return *instantiated;

View File

@ -19,7 +19,6 @@ LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
LUAU_FASTFLAGVARIABLE(DebugLuauDeferredConstraintResolution, false) LUAU_FASTFLAGVARIABLE(DebugLuauDeferredConstraintResolution, false)
LUAU_FASTFLAGVARIABLE(LuauNativeAttribute, false) LUAU_FASTFLAGVARIABLE(LuauNativeAttribute, false)
LUAU_FASTFLAGVARIABLE(LuauAttributeSyntaxFunExpr, false) LUAU_FASTFLAGVARIABLE(LuauAttributeSyntaxFunExpr, false)
LUAU_FASTFLAGVARIABLE(LuauDeclarationExtraPropData, false)
LUAU_FASTFLAGVARIABLE(LuauUserDefinedTypeFunctions, false) LUAU_FASTFLAGVARIABLE(LuauUserDefinedTypeFunctions, false)
namespace Luau namespace Luau
@ -938,16 +937,10 @@ AstStat* Parser::parseTypeFunction(const Location& start)
AstDeclaredClassProp Parser::parseDeclaredClassMethod() AstDeclaredClassProp Parser::parseDeclaredClassMethod()
{ {
Location start; Location start = lexer.current().location;
if (FFlag::LuauDeclarationExtraPropData)
start = lexer.current().location;
nextLexeme(); nextLexeme();
if (!FFlag::LuauDeclarationExtraPropData)
start = lexer.current().location;
Name fnName = parseName("function name"); Name fnName = parseName("function name");
// TODO: generic method declarations CLI-39909 // TODO: generic method declarations CLI-39909
@ -972,7 +965,7 @@ AstDeclaredClassProp Parser::parseDeclaredClassMethod()
expectMatchAndConsume(')', matchParen); expectMatchAndConsume(')', matchParen);
AstTypeList retTypes = parseOptionalReturnType().value_or(AstTypeList{copy<AstType*>(nullptr, 0), nullptr}); AstTypeList retTypes = parseOptionalReturnType().value_or(AstTypeList{copy<AstType*>(nullptr, 0), nullptr});
Location end = FFlag::LuauDeclarationExtraPropData ? lexer.previousLocation() : lexer.current().location; Location end = lexer.previousLocation();
TempVector<AstType*> vars(scratchType); TempVector<AstType*> vars(scratchType);
TempVector<std::optional<AstArgumentName>> varNames(scratchOptArgName); TempVector<std::optional<AstArgumentName>> varNames(scratchOptArgName);
@ -980,10 +973,7 @@ AstDeclaredClassProp Parser::parseDeclaredClassMethod()
if (args.size() == 0 || args[0].name.name != "self" || args[0].annotation != nullptr) if (args.size() == 0 || args[0].name.name != "self" || args[0].annotation != nullptr)
{ {
return AstDeclaredClassProp{ return AstDeclaredClassProp{
fnName.name, fnName.name, fnName.location, reportTypeError(Location(start, end), {}, "'self' must be present as the unannotated first parameter"), true
FFlag::LuauDeclarationExtraPropData ? fnName.location : Location{},
reportTypeError(Location(start, end), {}, "'self' must be present as the unannotated first parameter"),
true
}; };
} }
@ -1005,13 +995,7 @@ AstDeclaredClassProp Parser::parseDeclaredClassMethod()
Location(start, end), generics, genericPacks, AstTypeList{copy(vars), varargAnnotation}, copy(varNames), retTypes Location(start, end), generics, genericPacks, AstTypeList{copy(vars), varargAnnotation}, copy(varNames), retTypes
); );
return AstDeclaredClassProp{ return AstDeclaredClassProp{fnName.name, fnName.location, fnType, true, Location(start, end)};
fnName.name,
FFlag::LuauDeclarationExtraPropData ? fnName.location : Location{},
fnType,
true,
FFlag::LuauDeclarationExtraPropData ? Location(start, end) : Location{}
};
} }
AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*>& attributes) AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*>& attributes)
@ -1067,34 +1051,19 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
if (vararg && !varargAnnotation) if (vararg && !varargAnnotation)
return reportStatError(Location(start, end), {}, {}, "All declaration parameters must be annotated"); return reportStatError(Location(start, end), {}, {}, "All declaration parameters must be annotated");
if (FFlag::LuauDeclarationExtraPropData) return allocator.alloc<AstStatDeclareFunction>(
return allocator.alloc<AstStatDeclareFunction>( Location(start, end),
Location(start, end), attributes,
attributes, globalName.name,
globalName.name, globalName.location,
globalName.location, generics,
generics, genericPacks,
genericPacks, AstTypeList{copy(vars), varargAnnotation},
AstTypeList{copy(vars), varargAnnotation}, copy(varNames),
copy(varNames), vararg,
vararg, varargLocation,
varargLocation, retTypes
retTypes );
);
else
return allocator.alloc<AstStatDeclareFunction>(
Location(start, end),
attributes,
globalName.name,
Location{},
generics,
genericPacks,
AstTypeList{copy(vars), varargAnnotation},
copy(varNames),
false,
Location{},
retTypes
);
} }
else if (AstName(lexer.current().name) == "class") else if (AstName(lexer.current().name) == "class")
{ {
@ -1124,42 +1093,27 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
const Lexeme begin = lexer.current(); const Lexeme begin = lexer.current();
nextLexeme(); // [ nextLexeme(); // [
if (FFlag::LuauDeclarationExtraPropData) const Location nameBegin = lexer.current().location;
std::optional<AstArray<char>> chars = parseCharArray();
const Location nameEnd = lexer.previousLocation();
expectMatchAndConsume(']', begin);
expectAndConsume(':', "property type annotation");
AstType* type = parseType();
// since AstName contains a char*, it can't contain null
bool containsNull = chars && (strnlen(chars->data, chars->size) < chars->size);
if (chars && !containsNull)
{ {
const Location nameBegin = lexer.current().location; props.push_back(AstDeclaredClassProp{
std::optional<AstArray<char>> chars = parseCharArray(); AstName(chars->data), Location(nameBegin, nameEnd), type, false, Location(begin.location, lexer.previousLocation())
});
const Location nameEnd = lexer.previousLocation();
expectMatchAndConsume(']', begin);
expectAndConsume(':', "property type annotation");
AstType* type = parseType();
// since AstName contains a char*, it can't contain null
bool containsNull = chars && (strnlen(chars->data, chars->size) < chars->size);
if (chars && !containsNull)
props.push_back(AstDeclaredClassProp{
AstName(chars->data), Location(nameBegin, nameEnd), type, false, Location(begin.location, lexer.previousLocation())
});
else
report(begin.location, "String literal contains malformed escape sequence or \\0");
} }
else else
{ {
std::optional<AstArray<char>> chars = parseCharArray(); report(begin.location, "String literal contains malformed escape sequence or \\0");
expectMatchAndConsume(']', begin);
expectAndConsume(':', "property type annotation");
AstType* type = parseType();
// since AstName contains a char*, it can't contain null
bool containsNull = chars && (strnlen(chars->data, chars->size) < chars->size);
if (chars && !containsNull)
props.push_back(AstDeclaredClassProp{AstName(chars->data), Location{}, type, false});
else
report(begin.location, "String literal contains malformed escape sequence or \\0");
} }
} }
else if (lexer.current().type == '[') else if (lexer.current().type == '[')
@ -1178,7 +1132,7 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
indexer = parseTableIndexer(AstTableAccess::ReadWrite, std::nullopt); indexer = parseTableIndexer(AstTableAccess::ReadWrite, std::nullopt);
} }
} }
else if (FFlag::LuauDeclarationExtraPropData) else
{ {
Location propStart = lexer.current().location; Location propStart = lexer.current().location;
Name propName = parseName("property name"); Name propName = parseName("property name");
@ -1187,13 +1141,6 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
props.push_back(AstDeclaredClassProp{propName.name, propName.location, propType, false, Location(propStart, lexer.previousLocation())} props.push_back(AstDeclaredClassProp{propName.name, propName.location, propType, false, Location(propStart, lexer.previousLocation())}
); );
} }
else
{
Name propName = parseName("property name");
expectAndConsume(':', "property type annotation");
AstType* propType = parseType();
props.push_back(AstDeclaredClassProp{propName.name, Location{}, propType, false});
}
} }
Location classEnd = lexer.current().location; Location classEnd = lexer.current().location;
@ -1206,9 +1153,7 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
expectAndConsume(':', "global variable declaration"); expectAndConsume(':', "global variable declaration");
AstType* type = parseType(/* in declaration context */ true); AstType* type = parseType(/* in declaration context */ true);
return allocator.alloc<AstStatDeclareGlobal>( return allocator.alloc<AstStatDeclareGlobal>(Location(start, type->location), globalName->name, globalName->location, type);
Location(start, type->location), globalName->name, FFlag::LuauDeclarationExtraPropData ? globalName->location : Location{}, type
);
} }
else else
{ {

View File

@ -17,6 +17,8 @@
#include <string.h> #include <string.h>
LUAU_FASTFLAGVARIABLE(LuauErrorResumeCleanupArgs, false)
/* /*
** {====================================================== ** {======================================================
** Error-recovery functions ** Error-recovery functions
@ -426,9 +428,13 @@ static void resume_handle(lua_State* L, void* ud)
resume_continue(L); resume_continue(L);
} }
static int resume_error(lua_State* L, const char* msg) static int resume_error(lua_State* L, const char* msg, int narg)
{ {
L->top = L->ci->base; if (FFlag::LuauErrorResumeCleanupArgs)
L->top -= narg;
else
L->top = L->ci->base;
setsvalue(L, L->top, luaS_new(L, msg)); setsvalue(L, L->top, luaS_new(L, msg));
incr_top(L); incr_top(L);
return LUA_ERRRUN; return LUA_ERRRUN;
@ -455,11 +461,11 @@ int lua_resume(lua_State* L, lua_State* from, int nargs)
{ {
int status; int status;
if (L->status != LUA_YIELD && L->status != LUA_BREAK && (L->status != 0 || L->ci != L->base_ci)) if (L->status != LUA_YIELD && L->status != LUA_BREAK && (L->status != 0 || L->ci != L->base_ci))
return resume_error(L, "cannot resume non-suspended coroutine"); return resume_error(L, "cannot resume non-suspended coroutine", nargs);
L->nCcalls = from ? from->nCcalls : 0; L->nCcalls = from ? from->nCcalls : 0;
if (L->nCcalls >= LUAI_MAXCCALLS) if (L->nCcalls >= LUAI_MAXCCALLS)
return resume_error(L, "C stack overflow"); return resume_error(L, "C stack overflow", nargs);
L->baseCcalls = ++L->nCcalls; L->baseCcalls = ++L->nCcalls;
L->isactive = true; L->isactive = true;
@ -484,11 +490,11 @@ int lua_resumeerror(lua_State* L, lua_State* from)
{ {
int status; int status;
if (L->status != LUA_YIELD && L->status != LUA_BREAK && (L->status != 0 || L->ci != L->base_ci)) if (L->status != LUA_YIELD && L->status != LUA_BREAK && (L->status != 0 || L->ci != L->base_ci))
return resume_error(L, "cannot resume non-suspended coroutine"); return resume_error(L, "cannot resume non-suspended coroutine", 1);
L->nCcalls = from ? from->nCcalls : 0; L->nCcalls = from ? from->nCcalls : 0;
if (L->nCcalls >= LUAI_MAXCCALLS) if (L->nCcalls >= LUAI_MAXCCALLS)
return resume_error(L, "C stack overflow"); return resume_error(L, "C stack overflow", 1);
L->baseCcalls = ++L->nCcalls; L->baseCcalls = ++L->nCcalls;
L->isactive = true; L->isactive = true;

View File

@ -9,8 +9,6 @@
#include <math.h> #include <math.h>
#include <ostream> #include <ostream>
LUAU_FASTFLAG(LuauDeclarationExtraPropData)
using namespace Luau; using namespace Luau;
struct JsonEncoderFixture struct JsonEncoderFixture
@ -417,8 +415,6 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatTypeAlias")
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatDeclareFunction") TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatDeclareFunction")
{ {
ScopedFastFlag luauDeclarationExtraPropData{FFlag::LuauDeclarationExtraPropData, true};
AstStat* statement = expectParseStatement("declare function foo(x: number): string"); AstStat* statement = expectParseStatement("declare function foo(x: number): string");
std::string_view expected = std::string_view expected =
@ -429,8 +425,6 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatDeclareFunction")
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatDeclareFunction2") TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatDeclareFunction2")
{ {
ScopedFastFlag luauDeclarationExtraPropData{FFlag::LuauDeclarationExtraPropData, true};
AstStat* statement = expectParseStatement("declare function foo(x: number, ...: string): string"); AstStat* statement = expectParseStatement("declare function foo(x: number, ...: string): string");
std::string_view expected = std::string_view expected =
@ -441,8 +435,6 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatDeclareFunction2")
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatDeclareClass") TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatDeclareClass")
{ {
ScopedFastFlag luauDeclarationExtraPropData{FFlag::LuauDeclarationExtraPropData, true};
AstStatBlock* root = expectParse(R"( AstStatBlock* root = expectParse(R"(
declare class Foo declare class Foo
prop: number prop: number

View File

@ -171,6 +171,8 @@ TEST_SUITE_BEGIN("AstQuery");
TEST_CASE_FIXTURE(Fixture, "last_argument_function_call_type") TEST_CASE_FIXTURE(Fixture, "last_argument_function_call_type")
{ {
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
check(R"( check(R"(
local function foo() return 2 end local function foo() return 2 end
local function bar(a: number) return -a end local function bar(a: number) return -a end

View File

@ -1658,6 +1658,10 @@ table.create(42, {} :: {})
TEST_CASE_FIXTURE(BuiltinsFixture, "TableOperationsIndexer") TEST_CASE_FIXTURE(BuiltinsFixture, "TableOperationsIndexer")
{ {
// CLI-116824 Linter incorrectly issues false positive when taking the length of a unannotated string function argument
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
LintResult result = lint(R"( LintResult result = lint(R"(
local t1 = {} -- ok: empty local t1 = {} -- ok: empty
local t2 = {1, 2} -- ok: array local t2 = {1, 2} -- ok: array

View File

@ -440,11 +440,11 @@ struct NormalizeFixture : Fixture
registerHiddenTypes(&frontend); registerHiddenTypes(&frontend);
} }
std::shared_ptr<const NormalizedType> toNormalizedType(const std::string& annotation) std::shared_ptr<const NormalizedType> toNormalizedType(const std::string& annotation, int expectedErrors = 0)
{ {
normalizer.clearCaches(); normalizer.clearCaches();
CheckResult result = check("type _Res = " + annotation); CheckResult result = check("type _Res = " + annotation);
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_ERROR_COUNT(expectedErrors, result);
if (FFlag::DebugLuauDeferredConstraintResolution) if (FFlag::DebugLuauDeferredConstraintResolution)
{ {
@ -662,7 +662,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "negated_function_is_anything_except_a_funct
TEST_CASE_FIXTURE(NormalizeFixture, "specific_functions_cannot_be_negated") TEST_CASE_FIXTURE(NormalizeFixture, "specific_functions_cannot_be_negated")
{ {
CHECK(nullptr == toNormalizedType("Not<(boolean) -> boolean>")); CHECK(nullptr == toNormalizedType("Not<(boolean) -> boolean>", FFlag::DebugLuauDeferredConstraintResolution ? 1 : 0));
} }
TEST_CASE_FIXTURE(NormalizeFixture, "trivial_intersection_inhabited") TEST_CASE_FIXTURE(NormalizeFixture, "trivial_intersection_inhabited")
@ -875,7 +875,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "top_table_type")
TEST_CASE_FIXTURE(NormalizeFixture, "negations_of_tables") TEST_CASE_FIXTURE(NormalizeFixture, "negations_of_tables")
{ {
CHECK(nullptr == toNormalizedType("Not<{}>")); CHECK(nullptr == toNormalizedType("Not<{}>", FFlag::DebugLuauDeferredConstraintResolution ? 1 : 0));
CHECK("(boolean | buffer | class | function | number | string | thread)?" == toString(normal("Not<tbl>"))); CHECK("(boolean | buffer | class | function | number | string | thread)?" == toString(normal("Not<tbl>")));
CHECK("table" == toString(normal("Not<Not<tbl>>"))); CHECK("table" == toString(normal("Not<Not<tbl>>")));
} }

View File

@ -17,7 +17,6 @@ LUAU_FASTINT(LuauTypeLengthLimit);
LUAU_FASTINT(LuauParseErrorLimit); LUAU_FASTINT(LuauParseErrorLimit);
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
LUAU_FASTFLAG(LuauAttributeSyntaxFunExpr); LUAU_FASTFLAG(LuauAttributeSyntaxFunExpr);
LUAU_FASTFLAG(LuauDeclarationExtraPropData);
LUAU_FASTFLAG(LuauUserDefinedTypeFunctions); LUAU_FASTFLAG(LuauUserDefinedTypeFunctions);
namespace namespace
@ -1918,8 +1917,6 @@ function func():end
TEST_CASE_FIXTURE(Fixture, "parse_declarations") TEST_CASE_FIXTURE(Fixture, "parse_declarations")
{ {
ScopedFastFlag luauDeclarationExtraPropData{FFlag::LuauDeclarationExtraPropData, true};
AstStatBlock* stat = parseEx(R"( AstStatBlock* stat = parseEx(R"(
declare foo: number declare foo: number
declare function bar(x: number): string declare function bar(x: number): string
@ -1957,8 +1954,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_declarations")
TEST_CASE_FIXTURE(Fixture, "parse_class_declarations") TEST_CASE_FIXTURE(Fixture, "parse_class_declarations")
{ {
ScopedFastFlag luauDeclarationExtraPropData{FFlag::LuauDeclarationExtraPropData, true};
AstStatBlock* stat = parseEx(R"( AstStatBlock* stat = parseEx(R"(
declare class Foo declare class Foo
prop: number prop: number

View File

@ -104,6 +104,9 @@ TEST_CASE_FIXTURE(Fixture, "cannot_steal_hoisted_type_alias")
TEST_CASE_FIXTURE(Fixture, "mismatched_generic_type_param") TEST_CASE_FIXTURE(Fixture, "mismatched_generic_type_param")
{ {
// We erroneously report an extra error in this case when the new solver is enabled.
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
type T<A> = (A...) -> () type T<A> = (A...) -> ()
)"); )");
@ -240,6 +243,9 @@ TEST_CASE_FIXTURE(Fixture, "dependent_generic_aliases")
TEST_CASE_FIXTURE(Fixture, "mutually_recursive_generic_aliases") TEST_CASE_FIXTURE(Fixture, "mutually_recursive_generic_aliases")
{ {
// CLI-116108
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
--!strict --!strict
type T<a> = { f: a, g: U<a> } type T<a> = { f: a, g: U<a> }
@ -411,6 +417,8 @@ TEST_CASE_FIXTURE(Fixture, "corecursive_function_types")
TEST_CASE_FIXTURE(Fixture, "generic_param_remap") TEST_CASE_FIXTURE(Fixture, "generic_param_remap")
{ {
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
const std::string code = R"( const std::string code = R"(
-- An example of a forwarded use of a type that has different type arguments than parameters -- An example of a forwarded use of a type that has different type arguments than parameters
type A<T,U> = {t:T, u:U, next:A<U,T>?} type A<T,U> = {t:T, u:U, next:A<U,T>?}
@ -535,11 +543,13 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_import_mutation")
TEST_CASE_FIXTURE(Fixture, "type_alias_local_mutation") TEST_CASE_FIXTURE(Fixture, "type_alias_local_mutation")
{ {
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
type Cool = { a: number, b: string } type Cool = { a: number, b: string }
local c: Cool = { a = 1, b = "s" } local c: Cool = { a = 1, b = "s" }
type NotCool<x> = Cool type NotCool<x> = Cool
)"); )");
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
std::optional<TypeId> ty = requireType("c"); std::optional<TypeId> ty = requireType("c");
@ -554,6 +564,8 @@ type NotCool<x> = Cool
TEST_CASE_FIXTURE(Fixture, "type_alias_local_rename") TEST_CASE_FIXTURE(Fixture, "type_alias_local_rename")
{ {
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
type Cool = { a: number, b: string } type Cool = { a: number, b: string }
type NotCool = Cool type NotCool = Cool
@ -615,16 +627,16 @@ type X = Import.X
TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_of_an_imported_recursive_generic_type") TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_of_an_imported_recursive_generic_type")
{ {
fileResolver.source["game/A"] = R"( fileResolver.source["game/A"] = R"(
export type X<T, U> = { a: T, b: U, C: X<T, U>? } export type X<T, U> = { a: T, b: U, C: X<T, U>? }
return {} return {}
)"; )";
CheckResult aResult = frontend.check("game/A"); CheckResult aResult = frontend.check("game/A");
LUAU_REQUIRE_NO_ERRORS(aResult); LUAU_REQUIRE_NO_ERRORS(aResult);
CheckResult bResult = check(R"( CheckResult bResult = check(R"(
local Import = require(game.A) local Import = require(game.A)
type X<T, U> = Import.X<T, U> type X<T, U> = Import.X<T, U>
)"); )");
LUAU_REQUIRE_NO_ERRORS(bResult); LUAU_REQUIRE_NO_ERRORS(bResult);
@ -637,8 +649,8 @@ type X<T, U> = Import.X<T, U>
CHECK_EQ(toString(*ty1, {true}), toString(*ty2, {true})); CHECK_EQ(toString(*ty1, {true}), toString(*ty2, {true}));
bResult = check(R"( bResult = check(R"(
local Import = require(game.A) local Import = require(game.A)
type X<T, U> = Import.X<U, T> type X<T, U> = Import.X<U, T>
)"); )");
LUAU_REQUIRE_NO_ERRORS(bResult); LUAU_REQUIRE_NO_ERRORS(bResult);
@ -648,8 +660,16 @@ type X<T, U> = Import.X<U, T>
ty2 = lookupType("X"); ty2 = lookupType("X");
REQUIRE(ty2); REQUIRE(ty2);
CHECK_EQ(toString(*ty1, {true}), "t1 where t1 = {| C: t1?, a: T, b: U |}"); if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ(toString(*ty2, {true}), "{| C: t1, a: U, b: T |} where t1 = {| C: t1, a: U, b: T |}?"); {
CHECK(toString(*ty1, {true}) == "t1 where t1 = { C: t1?, a: T, b: U }");
CHECK(toString(*ty2, {true}) == "t1 where t1 = { C: t1?, a: U, b: T }");
}
else
{
CHECK_EQ(toString(*ty1, {true}), "t1 where t1 = {| C: t1?, a: T, b: U |}");
CHECK_EQ(toString(*ty2, {true}), "{| C: t1, a: U, b: T |} where t1 = {| C: t1, a: U, b: T |}?");
}
} }
TEST_CASE_FIXTURE(Fixture, "module_export_free_type_leak") TEST_CASE_FIXTURE(Fixture, "module_export_free_type_leak")
@ -691,6 +711,9 @@ TEST_CASE_FIXTURE(Fixture, "mutually_recursive_types_restriction_ok")
TEST_CASE_FIXTURE(Fixture, "mutually_recursive_types_restriction_not_ok_1") TEST_CASE_FIXTURE(Fixture, "mutually_recursive_types_restriction_not_ok_1")
{ {
// CLI-116108
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
-- OK because forwarded types are used with their parameters. -- OK because forwarded types are used with their parameters.
type Tree<T> = { data: T, children: Forest<T> } type Tree<T> = { data: T, children: Forest<T> }
@ -702,6 +725,9 @@ TEST_CASE_FIXTURE(Fixture, "mutually_recursive_types_restriction_not_ok_1")
TEST_CASE_FIXTURE(Fixture, "mutually_recursive_types_restriction_not_ok_2") TEST_CASE_FIXTURE(Fixture, "mutually_recursive_types_restriction_not_ok_2")
{ {
// CLI-116108
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
-- Not OK because forwarded types are used with different types than their parameters. -- Not OK because forwarded types are used with different types than their parameters.
type Forest<T> = {Tree<{T}>} type Forest<T> = {Tree<{T}>}
@ -723,6 +749,9 @@ TEST_CASE_FIXTURE(Fixture, "mutually_recursive_types_swapsies_ok")
TEST_CASE_FIXTURE(Fixture, "mutually_recursive_types_swapsies_not_ok") TEST_CASE_FIXTURE(Fixture, "mutually_recursive_types_swapsies_not_ok")
{ {
// CLI-116108
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
type Tree1<T,U> = { data: T, children: {Tree2<U,T>} } type Tree1<T,U> = { data: T, children: {Tree2<U,T>} }
type Tree2<T,U> = { data: U, children: {Tree1<T,U>} } type Tree2<T,U> = { data: U, children: {Tree1<T,U>} }
@ -845,6 +874,9 @@ TEST_CASE_FIXTURE(Fixture, "recursive_types_restriction_ok")
TEST_CASE_FIXTURE(Fixture, "recursive_types_restriction_not_ok") TEST_CASE_FIXTURE(Fixture, "recursive_types_restriction_not_ok")
{ {
// CLI-116108
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
-- this would be an infinite type if we allowed it -- this would be an infinite type if we allowed it
type Tree<T> = { data: T, children: {Tree<{T}>} } type Tree<T> = { data: T, children: {Tree<{T}>} }
@ -855,6 +887,9 @@ TEST_CASE_FIXTURE(Fixture, "recursive_types_restriction_not_ok")
TEST_CASE_FIXTURE(Fixture, "report_shadowed_aliases") TEST_CASE_FIXTURE(Fixture, "report_shadowed_aliases")
{ {
// CLI-116110
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
// We allow a previous type alias to depend on a future type alias. That exact feature enables a confusing example, like the following snippet, // We allow a previous type alias to depend on a future type alias. That exact feature enables a confusing example, like the following snippet,
// which has the type alias FakeString point to the type alias `string` that which points to `number`. // which has the type alias FakeString point to the type alias `string` that which points to `number`.
CheckResult result = check(R"( CheckResult result = check(R"(
@ -936,6 +971,9 @@ TEST_CASE_FIXTURE(Fixture, "type_alias_locations")
*/ */
TEST_CASE_FIXTURE(BuiltinsFixture, "dont_lose_track_of_PendingExpansionTypes_after_substitution") TEST_CASE_FIXTURE(BuiltinsFixture, "dont_lose_track_of_PendingExpansionTypes_after_substitution")
{ {
// CLI-114134 - We need egraphs to properly simplify these types.
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
fileResolver.source["game/ReactCurrentDispatcher"] = R"( fileResolver.source["game/ReactCurrentDispatcher"] = R"(
export type BasicStateAction<S> = ((S) -> S) | S export type BasicStateAction<S> = ((S) -> S) | S
export type Dispatch<A> = (A) -> () export type Dispatch<A> = (A) -> ()

View File

@ -132,6 +132,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "sort_with_predicate")
TEST_CASE_FIXTURE(BuiltinsFixture, "sort_with_bad_predicate") TEST_CASE_FIXTURE(BuiltinsFixture, "sort_with_bad_predicate")
{ {
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
--!strict --!strict
local t = {'one', 'two', 'three'} local t = {'one', 'two', 'three'}
@ -510,6 +512,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "buffer_is_a_type")
TEST_CASE_FIXTURE(BuiltinsFixture, "coroutine_resume_anything_goes") TEST_CASE_FIXTURE(BuiltinsFixture, "coroutine_resume_anything_goes")
{ {
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
local function nifty(x, y) local function nifty(x, y)
print(x, y) print(x, y)
@ -547,6 +551,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "coroutine_wrap_anything_goes")
TEST_CASE_FIXTURE(BuiltinsFixture, "setmetatable_should_not_mutate_persisted_types") TEST_CASE_FIXTURE(BuiltinsFixture, "setmetatable_should_not_mutate_persisted_types")
{ {
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check(R"( CheckResult result = check(R"(
local string = string local string = string
@ -591,6 +598,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_arg_count_mismatch")
TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_correctly_ordered_types") TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_correctly_ordered_types")
{ {
// CLI-115690
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check(R"( CheckResult result = check(R"(
--!strict --!strict
string.format("%s", 123) string.format("%s", 123)
@ -694,10 +705,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "bad_select_should_not_crash")
if (FFlag::DebugLuauDeferredConstraintResolution) if (FFlag::DebugLuauDeferredConstraintResolution)
{ {
// Counterintuitively, the parametr l0 is unconstrained and therefore it is valid to pass nil. // Counterintuitively, the parameter l0 is unconstrained and therefore it is valid to pass nil.
// The new solver therefore considers that parameter to be optional. // The new solver therefore considers that parameter to be optional.
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK("Argument count mismatch. Function expects 1 argument, but none are specified" == toString(result.errors[0])); CHECK("Argument count mismatch. Function expects at least 1 argument, but none are specified" == toString(result.errors[0]));
} }
else else
{ {
@ -709,6 +720,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "bad_select_should_not_crash")
TEST_CASE_FIXTURE(BuiltinsFixture, "select_way_out_of_range") TEST_CASE_FIXTURE(BuiltinsFixture, "select_way_out_of_range")
{ {
// CLI-115720
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check(R"( CheckResult result = check(R"(
select(5432598430953240958) select(5432598430953240958)
)"); )");
@ -720,6 +735,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "select_way_out_of_range")
TEST_CASE_FIXTURE(BuiltinsFixture, "select_slightly_out_of_range") TEST_CASE_FIXTURE(BuiltinsFixture, "select_slightly_out_of_range")
{ {
// CLI-115720
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check(R"( CheckResult result = check(R"(
select(3, "a", 1) select(3, "a", 1)
)"); )");
@ -750,6 +769,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "select_with_variadic_typepack_tail")
TEST_CASE_FIXTURE(BuiltinsFixture, "select_with_variadic_typepack_tail_and_string_head") TEST_CASE_FIXTURE(BuiltinsFixture, "select_with_variadic_typepack_tail_and_string_head")
{ {
// CLI-115720
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check(R"( CheckResult result = check(R"(
--!nonstrict --!nonstrict
local function f(...) local function f(...)
@ -769,6 +792,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "select_with_variadic_typepack_tail_and_strin
TEST_CASE_FIXTURE(Fixture, "string_format_as_method") TEST_CASE_FIXTURE(Fixture, "string_format_as_method")
{ {
// CLI-115690
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check("local _ = ('%s'):format(5)"); CheckResult result = check("local _ = ('%s'):format(5)");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
@ -792,6 +819,10 @@ TEST_CASE_FIXTURE(Fixture, "string_format_use_correct_argument")
TEST_CASE_FIXTURE(Fixture, "string_format_use_correct_argument2") TEST_CASE_FIXTURE(Fixture, "string_format_use_correct_argument2")
{ {
// CLI-115690
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check(R"( CheckResult result = check(R"(
local _ = ("%s %d").format("%d %s", "A type error", 2) local _ = ("%s %d").format("%d %s", "A type error", 2)
)"); )");
@ -850,6 +881,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "debug_info_is_crazy")
TEST_CASE_FIXTURE(BuiltinsFixture, "aliased_string_format") TEST_CASE_FIXTURE(BuiltinsFixture, "aliased_string_format")
{ {
// CLI-115690
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check(R"( CheckResult result = check(R"(
local fmt = string.format local fmt = string.format
local s = fmt("%d", "oops") local s = fmt("%d", "oops")
@ -909,6 +944,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "select_on_variadic")
TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_report_all_type_errors_at_correct_positions") TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_report_all_type_errors_at_correct_positions")
{ {
// CLI-115690
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check(R"( CheckResult result = check(R"(
("%s%d%s"):format(1, "hello", true) ("%s%d%s"):format(1, "hello", true)
string.format("%s%d%s", 1, "hello", true) string.format("%s%d%s", 1, "hello", true)
@ -1039,6 +1078,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types3")
TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types_even_from_type_pack_tail_but_only_for_the_first_type") TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types_even_from_type_pack_tail_but_only_for_the_first_type")
{ {
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check(R"( CheckResult result = check(R"(
local function f(...: number?) local function f(...: number?)
return assert(...) return assert(...)
@ -1051,6 +1093,12 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types_even_from_type_pa
TEST_CASE_FIXTURE(BuiltinsFixture, "assert_returns_false_and_string_iff_it_knows_the_first_argument_cannot_be_truthy") TEST_CASE_FIXTURE(BuiltinsFixture, "assert_returns_false_and_string_iff_it_knows_the_first_argument_cannot_be_truthy")
{ {
if (FFlag::DebugLuauDeferredConstraintResolution)
{
// CLI-114134 - egraph simplification
return;
}
CheckResult result = check(R"( CheckResult result = check(R"(
local function f(x: nil) local function f(x: nil)
return assert(x, "hmm") return assert(x, "hmm")
@ -1080,7 +1128,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_is_generic")
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ("Key 'b' not found in table '{| a: number |}'", toString(result.errors[0])); if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK("Key 'b' not found in table '{ a: number }'" == toString(result.errors[0]));
else
CHECK_EQ("Key 'b' not found in table '{| a: number |}'", toString(result.errors[0]));
CHECK(Location({13, 18}, {13, 23}) == result.errors[0].location); CHECK(Location({13, 18}, {13, 23}) == result.errors[0].location);
CHECK_EQ("number", toString(requireType("a"))); CHECK_EQ("number", toString(requireType("a")));
@ -1095,11 +1146,14 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_is_generic")
TEST_CASE_FIXTURE(BuiltinsFixture, "set_metatable_needs_arguments") TEST_CASE_FIXTURE(BuiltinsFixture, "set_metatable_needs_arguments")
{ {
// In the new solver, nil can certainly be used where a generic is required, so all generic parameters are optional.
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
local a = {b=setmetatable} local a = {b=setmetatable}
a.b() a.b()
a:b() a:b()
a:b({}) a:b({})
)"); )");
LUAU_REQUIRE_ERROR_COUNT(2, result); LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK_EQ(toString(result.errors[0]), "Argument count mismatch. Function 'a.b' expects 2 arguments, but none are specified"); CHECK_EQ(toString(result.errors[0]), "Argument count mismatch. Function 'a.b' expects 2 arguments, but none are specified");
@ -1109,8 +1163,8 @@ a:b({})
TEST_CASE_FIXTURE(Fixture, "typeof_unresolved_function") TEST_CASE_FIXTURE(Fixture, "typeof_unresolved_function")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
local function f(a: typeof(f)) end local function f(a: typeof(f)) end
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ("Unknown global 'f'", toString(result.errors[0])); CHECK_EQ("Unknown global 'f'", toString(result.errors[0]));
} }

View File

@ -128,6 +128,8 @@ TEST_CASE_FIXTURE(ClassFixture, "we_can_infer_that_a_parameter_must_be_a_particu
TEST_CASE_FIXTURE(ClassFixture, "we_can_report_when_someone_is_trying_to_use_a_table_rather_than_a_class") TEST_CASE_FIXTURE(ClassFixture, "we_can_report_when_someone_is_trying_to_use_a_table_rather_than_a_class")
{ {
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
function makeClone(o) function makeClone(o)
return BaseClass.Clone(o) return BaseClass.Clone(o)
@ -152,6 +154,34 @@ TEST_CASE_FIXTURE(ClassFixture, "we_can_report_when_someone_is_trying_to_use_a_t
CHECK_EQ("BaseClass", toString(tm->wantedType)); CHECK_EQ("BaseClass", toString(tm->wantedType));
} }
TEST_CASE_FIXTURE(ClassFixture, "we_can_report_when_someone_is_trying_to_use_a_table_rather_than_a_class_using_new_solver")
{
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
CheckResult result = check(R"(
function makeClone(o)
return BaseClass.Clone(o)
end
type Oopsies = { read BaseMethod: (Oopsies, number) -> ()}
local oopsies: Oopsies = {
BaseMethod = function (self: Oopsies, i: number)
print('gadzooks!')
end
}
makeClone(oopsies)
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
TypeMismatch* tm = get<TypeMismatch>(result.errors.at(0));
REQUIRE(tm != nullptr);
CHECK_EQ("Oopsies", toString(tm->givenType));
CHECK_EQ("BaseClass", toString(tm->wantedType));
}
TEST_CASE_FIXTURE(ClassFixture, "assign_to_prop_of_class") TEST_CASE_FIXTURE(ClassFixture, "assign_to_prop_of_class")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
@ -204,6 +234,9 @@ TEST_CASE_FIXTURE(ClassFixture, "can_assign_to_prop_of_base_class_using_string")
TEST_CASE_FIXTURE(ClassFixture, "cannot_unify_class_instance_with_primitive") TEST_CASE_FIXTURE(ClassFixture, "cannot_unify_class_instance_with_primitive")
{ {
// This is allowed in the new solver
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
local v = Vector2.New(0, 5) local v = Vector2.New(0, 5)
v = 444 v = 444
@ -364,9 +397,17 @@ TEST_CASE_FIXTURE(ClassFixture, "table_class_unification_reports_sane_errors_for
foo(a) foo(a)
)"); )");
LUAU_REQUIRE_ERROR_COUNT(2, result); if (FFlag::DebugLuauDeferredConstraintResolution)
REQUIRE_EQ("Key 'w' not found in class 'Vector2'", toString(result.errors.at(0))); {
REQUIRE_EQ("Key 'x' not found in class 'Vector2'. Did you mean 'X'?", toString(result.errors[1])); LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK("Type 'Vector2' could not be converted into '{ Y: number, w: number, x: number }'" == toString(result.errors[0]));
}
else
{
LUAU_REQUIRE_ERROR_COUNT(2, result);
REQUIRE_EQ("Key 'w' not found in class 'Vector2'", toString(result.errors.at(0)));
REQUIRE_EQ("Key 'x' not found in class 'Vector2'. Did you mean 'X'?", toString(result.errors[1]));
}
} }
TEST_CASE_FIXTURE(ClassFixture, "class_unification_type_mismatch_is_correct_order") TEST_CASE_FIXTURE(ClassFixture, "class_unification_type_mismatch_is_correct_order")
@ -412,15 +453,27 @@ b(a)
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type 'Vector2' could not be converted into '{- X: number, Y: string -}'
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK("Type 'number' could not be converted into 'string'" == toString(result.errors.at(0)));
}
else
{
const std::string expected = R"(Type 'Vector2' could not be converted into '{- X: number, Y: string -}'
caused by: caused by:
Property 'Y' is not compatible. Property 'Y' is not compatible.
Type 'number' could not be converted into 'string')"; Type 'number' could not be converted into 'string')";
CHECK_EQ(expected, toString(result.errors.at(0)));
CHECK_EQ(expected, toString(result.errors.at(0)));
}
} }
TEST_CASE_FIXTURE(ClassFixture, "class_type_mismatch_with_name_conflict") TEST_CASE_FIXTURE(ClassFixture, "class_type_mismatch_with_name_conflict")
{ {
// CLI-116433
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
local i = ChildClass.New() local i = ChildClass.New()
type ChildClass = { x: number } type ChildClass = { x: number }
@ -611,10 +664,14 @@ TEST_CASE_FIXTURE(ClassFixture, "indexable_classes")
local y = x[true] local y = x[true]
)"); )");
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ( CHECK(
toString(result.errors.at(0)), "Type 'boolean' could not be converted into 'number | string'; none of the union options are compatible" "Type 'boolean' could not be converted into 'number | string'" == toString(result.errors.at(0))
); );
else
CHECK_EQ(
toString(result.errors.at(0)), "Type 'boolean' could not be converted into 'number | string'; none of the union options are compatible"
);
} }
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
@ -622,9 +679,14 @@ TEST_CASE_FIXTURE(ClassFixture, "indexable_classes")
x[true] = 42 x[true] = 42
)"); )");
CHECK_EQ( if (FFlag::DebugLuauDeferredConstraintResolution)
toString(result.errors.at(0)), "Type 'boolean' could not be converted into 'number | string'; none of the union options are compatible" CHECK(
); "Type 'boolean' could not be converted into 'number | string'" == toString(result.errors.at(0))
);
else
CHECK_EQ(
toString(result.errors.at(0)), "Type 'boolean' could not be converted into 'number | string'; none of the union options are compatible"
);
} }
// Test type checking for the return type of the indexer (i.e. a number) // Test type checking for the return type of the indexer (i.e. a number)
@ -633,7 +695,13 @@ TEST_CASE_FIXTURE(ClassFixture, "indexable_classes")
local x : IndexableClass local x : IndexableClass
x.key = "string value" x.key = "string value"
)"); )");
CHECK_EQ(toString(result.errors.at(0)), "Type 'string' could not be converted into 'number'");
if (FFlag::DebugLuauDeferredConstraintResolution)
{
// Disabled for now. CLI-115686
}
else
CHECK_EQ(toString(result.errors.at(0)), "Type 'string' could not be converted into 'number'");
} }
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
@ -682,7 +750,7 @@ TEST_CASE_FIXTURE(ClassFixture, "indexable_classes")
local y = x["key"] local y = x["key"]
)"); )");
if (FFlag::DebugLuauDeferredConstraintResolution) if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ(toString(result.errors.at(0)), "Key 'key' not found in class 'IndexableNumericKeyClass'"); CHECK(toString(result.errors.at(0)) == "Key 'key' not found in class 'IndexableNumericKeyClass'");
else else
CHECK_EQ(toString(result.errors.at(0)), "Type 'string' could not be converted into 'number'"); CHECK_EQ(toString(result.errors.at(0)), "Type 'string' could not be converted into 'number'");
} }

View File

@ -7,8 +7,6 @@
#include "doctest.h" #include "doctest.h"
LUAU_FASTFLAG(LuauDeclarationExtraPropData)
using namespace Luau; using namespace Luau;
TEST_SUITE_BEGIN("DefinitionTests"); TEST_SUITE_BEGIN("DefinitionTests");
@ -346,8 +344,6 @@ TEST_CASE_FIXTURE(Fixture, "definitions_documentation_symbols")
TEST_CASE_FIXTURE(Fixture, "definitions_symbols_are_generated_for_recursively_referenced_types") TEST_CASE_FIXTURE(Fixture, "definitions_symbols_are_generated_for_recursively_referenced_types")
{ {
ScopedFastFlag luauDeclarationExtraPropData{FFlag::LuauDeclarationExtraPropData, true};
loadDefinition(R"( loadDefinition(R"(
declare class MyClass declare class MyClass
function myMethod(self) function myMethod(self)

View File

@ -11,6 +11,7 @@
#include "ClassFixture.h" #include "ClassFixture.h"
#include "Fixture.h" #include "Fixture.h"
#include "ScopedFlags.h"
#include "doctest.h" #include "doctest.h"
using namespace Luau; using namespace Luau;
@ -255,7 +256,7 @@ TEST_CASE_FIXTURE(Fixture, "list_only_alternative_overloads_that_match_argument_
REQUIRE(ei); REQUIRE(ei);
if (FFlag::DebugLuauDeferredConstraintResolution) if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK("Available overloads: (number) -> number; and (number) -> string" == ei->message); CHECK("Available overloads: (number) -> number; (number) -> string; and (number, number) -> number" == ei->message);
else else
CHECK_EQ("Other overloads are also not viable: (number) -> string", ei->message); CHECK_EQ("Other overloads are also not viable: (number) -> string", ei->message);
} }
@ -310,6 +311,9 @@ TEST_CASE_FIXTURE(Fixture, "infer_return_type_from_selected_overload")
TEST_CASE_FIXTURE(Fixture, "too_many_arguments") TEST_CASE_FIXTURE(Fixture, "too_many_arguments")
{ {
// This is not part of the new non-strict specification currently.
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
--!nonstrict --!nonstrict
@ -476,13 +480,28 @@ TEST_CASE_FIXTURE(Fixture, "another_higher_order_function")
TEST_CASE_FIXTURE(Fixture, "another_other_higher_order_function") TEST_CASE_FIXTURE(Fixture, "another_other_higher_order_function")
{ {
CheckResult result = check(R"( if (FFlag::DebugLuauDeferredConstraintResolution)
local d {
d:foo() CheckResult result = check(R"(
d:foo() local function f(d)
)"); d:foo()
d:foo()
end
)");
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
}
else
{
CheckResult result = check(R"(
local d
d:foo()
d:foo()
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
} }
TEST_CASE_FIXTURE(Fixture, "local_function") TEST_CASE_FIXTURE(Fixture, "local_function")
@ -585,6 +604,9 @@ TEST_CASE_FIXTURE(Fixture, "duplicate_functions_allowed_in_nonstrict")
TEST_CASE_FIXTURE(Fixture, "duplicate_functions_with_different_signatures_not_allowed_in_nonstrict") TEST_CASE_FIXTURE(Fixture, "duplicate_functions_with_different_signatures_not_allowed_in_nonstrict")
{ {
// This is not part of the spec for the new non-strict mode currently.
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
--!nonstrict --!nonstrict
function foo(): number function foo(): number
@ -612,7 +634,7 @@ TEST_CASE_FIXTURE(Fixture, "complicated_return_types_require_an_explicit_annotat
local i = 0 local i = 0
function most_of_the_natural_numbers(): number? function most_of_the_natural_numbers(): number?
if i < 10 then if i < 10 then
i = i + 1 i += 1
return i return i
else else
return nil return nil
@ -737,7 +759,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "higher_order_function_4")
end end
end end
function mergesort(arr, comp) function mergesort<T>(arr: {T}, comp: (T, T) -> boolean)
local work = {} local work = {}
for i = 1, #arr do for i = 1, #arr do
work[i] = arr[i] work[i] = arr[i]
@ -756,7 +778,11 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "higher_order_function_4")
end end
)"); )");
LUAU_REQUIRE_NO_ERRORS(result); // This function currently has a bug in the new solver reporting `{T} | {T}` is not a table.
if (FFlag::DebugLuauDeferredConstraintResolution)
LUAU_REQUIRE_ERRORS(result);
else
LUAU_REQUIRE_NO_ERRORS(result);
/* /*
* mergesort takes two arguments: an array of some type T and a function that takes two Ts. * mergesort takes two arguments: an array of some type T and a function that takes two Ts.
@ -861,6 +887,9 @@ TEST_CASE_FIXTURE(Fixture, "another_indirect_function_case_where_it_is_ok_to_pro
TEST_CASE_FIXTURE(Fixture, "report_exiting_without_return_nonstrict") TEST_CASE_FIXTURE(Fixture, "report_exiting_without_return_nonstrict")
{ {
// new non-strict mode spec does not include this error yet.
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
--!nonstrict --!nonstrict
@ -985,11 +1014,17 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "calling_function_with_anytypepack_doesnt_lea
opts.exhaustive = true; opts.exhaustive = true;
opts.maxTableLength = 0; opts.maxTableLength = 0;
CHECK_EQ("{any}", toString(requireType("tab"), opts)); if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ("{string}", toString(requireType("tab"), opts));
else
CHECK_EQ("{any}", toString(requireType("tab"), opts));
} }
TEST_CASE_FIXTURE(Fixture, "too_many_return_values") TEST_CASE_FIXTURE(Fixture, "too_many_return_values")
{ {
// FIXME: CLI-116157 variadic and generic type packs seem to be interacting incorrectly.
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
--!strict --!strict
@ -1011,6 +1046,9 @@ TEST_CASE_FIXTURE(Fixture, "too_many_return_values")
TEST_CASE_FIXTURE(Fixture, "too_many_return_values_in_parentheses") TEST_CASE_FIXTURE(Fixture, "too_many_return_values_in_parentheses")
{ {
// FIXME: CLI-116157 variadic and generic type packs seem to be interacting incorrectly.
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
--!strict --!strict
@ -1032,6 +1070,9 @@ TEST_CASE_FIXTURE(Fixture, "too_many_return_values_in_parentheses")
TEST_CASE_FIXTURE(Fixture, "too_many_return_values_no_function") TEST_CASE_FIXTURE(Fixture, "too_many_return_values_no_function")
{ {
// FIXME: CLI-116157 variadic and generic type packs seem to be interacting incorrectly.
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
--!strict --!strict
@ -1072,13 +1113,25 @@ TEST_CASE_FIXTURE(Fixture, "function_does_not_return_enough_values")
end end
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); if (FFlag::DebugLuauDeferredConstraintResolution)
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
CountMismatch* acm = get<CountMismatch>(result.errors[0]); auto tpm = get<TypePackMismatch>(result.errors[0]);
REQUIRE(acm); REQUIRE(tpm);
CHECK_EQ(acm->context, CountMismatch::Return); CHECK("number, string" == toString(tpm->wantedTp));
CHECK_EQ(acm->expected, 2); CHECK("number" == toString(tpm->givenTp));
CHECK_EQ(acm->actual, 1); }
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
CountMismatch* acm = get<CountMismatch>(result.errors[0]);
REQUIRE(acm);
CHECK_EQ(acm->context, CountMismatch::Return);
CHECK_EQ(acm->expected, 2);
CHECK_EQ(acm->actual, 1);
}
} }
TEST_CASE_FIXTURE(Fixture, "function_cast_error_uses_correct_language") TEST_CASE_FIXTURE(Fixture, "function_cast_error_uses_correct_language")
@ -1100,13 +1153,19 @@ TEST_CASE_FIXTURE(Fixture, "function_cast_error_uses_correct_language")
REQUIRE(tm1); REQUIRE(tm1);
CHECK_EQ("(string) -> number", toString(tm1->wantedType)); CHECK_EQ("(string) -> number", toString(tm1->wantedType));
CHECK_EQ("(string, *error-type*) -> number", toString(tm1->givenType)); if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ("(unknown, unknown) -> number", toString(tm1->givenType));
else
CHECK_EQ("(string, *error-type*) -> number", toString(tm1->givenType));
auto tm2 = get<TypeMismatch>(result.errors[1]); auto tm2 = get<TypeMismatch>(result.errors[1]);
REQUIRE(tm2); REQUIRE(tm2);
CHECK_EQ("(number, number) -> (number, number)", toString(tm2->wantedType)); CHECK_EQ("(number, number) -> (number, number)", toString(tm2->wantedType));
CHECK_EQ("(string, *error-type*) -> number", toString(tm2->givenType)); if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ("(unknown, unknown) -> number", toString(tm1->givenType));
else
CHECK_EQ("(string, *error-type*) -> number", toString(tm2->givenType));
} }
TEST_CASE_FIXTURE(Fixture, "no_lossy_function_type") TEST_CASE_FIXTURE(Fixture, "no_lossy_function_type")
@ -1123,7 +1182,10 @@ TEST_CASE_FIXTURE(Fixture, "no_lossy_function_type")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
TypeId type = requireTypeAtPosition(Position(6, 14)); TypeId type = requireTypeAtPosition(Position(6, 14));
CHECK_EQ("(tbl, number, number) -> number", toString(type)); if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ("(unknown, number, number) -> number", toString(type));
else
CHECK_EQ("(tbl, number, number) -> number", toString(type));
auto ftv = get<FunctionType>(follow(type)); auto ftv = get<FunctionType>(follow(type));
REQUIRE(ftv); REQUIRE(ftv);
CHECK(ftv->hasSelf); CHECK(ftv->hasSelf);
@ -1166,13 +1228,20 @@ TEST_CASE_FIXTURE(Fixture, "return_type_by_overload")
LUAU_REQUIRE_ERRORS(result); LUAU_REQUIRE_ERRORS(result);
CHECK_EQ("string", toString(requireType("x"))); CHECK_EQ("string", toString(requireType("x")));
CHECK_EQ("number", toString(requireType("y"))); // the new solver does not currently "favor" arity-matching overloads when the call itself is ill-typed.
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ("string", toString(requireType("y")));
else
CHECK_EQ("number", toString(requireType("y")));
// Should this be string|number? // Should this be string|number?
CHECK_EQ("string", toString(requireType("z"))); CHECK_EQ("string", toString(requireType("z")));
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "infer_anonymous_function_arguments") TEST_CASE_FIXTURE(BuiltinsFixture, "infer_anonymous_function_arguments")
{ {
// FIXME: CLI-116133 bidirectional type inference needs to push expected types in for higher-order function calls
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
// Simple direct arg to arg propagation // Simple direct arg to arg propagation
CheckResult result = check(R"( CheckResult result = check(R"(
type Table = { x: number, y: number } type Table = { x: number, y: number }
@ -1289,6 +1358,9 @@ f(function(x) return x * 2 end)
TEST_CASE_FIXTURE(BuiltinsFixture, "infer_generic_function_function_argument") TEST_CASE_FIXTURE(BuiltinsFixture, "infer_generic_function_function_argument")
{ {
// FIXME: CLI-116133 bidirectional type inference needs to push expected types in for higher-order function calls
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
local function sum<a>(x: a, y: a, f: (a, a) -> a) return f(x, y) end local function sum<a>(x: a, y: a, f: (a, a) -> a) return f(x, y) end
return sum(2, 3, function(a, b) return a + b end) return sum(2, 3, function(a, b) return a + b end)
@ -1317,6 +1389,9 @@ local r = foldl(a, {s=0,c=0}, function(a, b) return {s = a.s + b, c = a.c + 1} e
TEST_CASE_FIXTURE(Fixture, "infer_generic_function_function_argument_overloaded") TEST_CASE_FIXTURE(Fixture, "infer_generic_function_function_argument_overloaded")
{ {
// FIXME: CLI-116133 bidirectional type inference needs to push expected types in for higher-order function calls
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
local function g1<T>(a: T, f: (T) -> T) return f(a) end local function g1<T>(a: T, f: (T) -> T) return f(a) end
local function g2<T>(a: T, b: T, f: (T, T) -> T) return f(a, b) end local function g2<T>(a: T, b: T, f: (T, T) -> T) return f(a, b) end
@ -1381,6 +1456,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "variadic_any_is_compatible_with_a_generic_Ty
TEST_CASE_FIXTURE(Fixture, "infer_anonymous_function_arguments_outside_call") TEST_CASE_FIXTURE(Fixture, "infer_anonymous_function_arguments_outside_call")
{ {
// FIXME: CLI-116133 bidirectional type inference needs to push expected types in for higher-order function calls
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
type Table = { x: number, y: number } type Table = { x: number, y: number }
local f: (Table) -> number = function(t) return t.x + t.y end local f: (Table) -> number = function(t) return t.x + t.y end
@ -1412,11 +1490,18 @@ local function i(): ...{string|number}
end end
)"); )");
LUAU_REQUIRE_NO_ERRORS(result); // `h` regresses in the new solver, the return type is not being pushed into the body.
if (FFlag::DebugLuauDeferredConstraintResolution)
LUAU_REQUIRE_ERROR_COUNT(1, result);
else
LUAU_REQUIRE_NO_ERRORS(result);
} }
TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_arg_count") TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_arg_count")
{ {
// FIXME: CLI-116111 test disabled until type path stringification is improved
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
type A = (number, number) -> string type A = (number, number) -> string
type B = (number) -> string type B = (number) -> string
@ -1437,6 +1522,9 @@ caused by:
TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_arg") TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_arg")
{ {
// FIXME: CLI-116111 test disabled until type path stringification is improved
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
type A = (number, number) -> string type A = (number, number) -> string
type B = (number, string) -> string type B = (number, string) -> string
@ -1458,6 +1546,9 @@ Type 'string' could not be converted into 'number')";
TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_ret_count") TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_ret_count")
{ {
// FIXME: CLI-116111 test disabled until type path stringification is improved
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
type A = (number, number) -> (number) type A = (number, number) -> (number)
type B = (number, number) -> (number, boolean) type B = (number, number) -> (number, boolean)
@ -1478,6 +1569,9 @@ caused by:
TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_ret") TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_ret")
{ {
// FIXME: CLI-116111 test disabled until type path stringification is improved
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
type A = (number, number) -> string type A = (number, number) -> string
type B = (number, number) -> number type B = (number, number) -> number
@ -1499,6 +1593,9 @@ Type 'string' could not be converted into 'number')";
TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_ret_mult") TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_ret_mult")
{ {
// FIXME: CLI-116111 test disabled until type path stringification is improved
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
type A = (number, number) -> (number, string) type A = (number, number) -> (number, string)
type B = (number, number) -> (number, boolean) type B = (number, number) -> (number, boolean)
@ -1578,9 +1675,18 @@ t.f = function(x)
end end
)"); )");
LUAU_REQUIRE_ERROR_COUNT(2, result); if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ(toString(result.errors[0]), R"(Type 'string' could not be converted into 'number')"); {
CHECK_EQ(toString(result.errors[1]), R"(Type 'string' could not be converted into 'number')"); LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK_EQ(toString(result.errors[0]), R"(Type function instance add<a, number> depends on generic function parameters but does not appear in the function signature; this construct cannot be type-checked at this time)");
CHECK_EQ(toString(result.errors[1]), R"(Type function instance add<a, number> depends on generic function parameters but does not appear in the function signature; this construct cannot be type-checked at this time)");
}
else
{
LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK_EQ(toString(result.errors[0]), R"(Type 'string' could not be converted into 'number')");
CHECK_EQ(toString(result.errors[1]), R"(Type 'string' could not be converted into 'number')");
}
} }
TEST_CASE_FIXTURE(Fixture, "inferred_higher_order_functions_are_quantified_at_the_right_time2") TEST_CASE_FIXTURE(Fixture, "inferred_higher_order_functions_are_quantified_at_the_right_time2")
@ -1611,6 +1717,9 @@ TEST_CASE_FIXTURE(Fixture, "inferred_higher_order_functions_are_quantified_at_th
TEST_CASE_FIXTURE(Fixture, "inferred_higher_order_functions_are_quantified_at_the_right_time3") TEST_CASE_FIXTURE(Fixture, "inferred_higher_order_functions_are_quantified_at_the_right_time3")
{ {
// This test regresses in the new solver, but is sort of nonsensical insofar as `foo` is known to be `nil`, so it's "right" to not be able to call it.
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
local foo local foo
@ -1637,8 +1746,15 @@ t.f = function(x)
end end
)"); )");
LUAU_REQUIRE_ERROR_COUNT(2, result); if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ(toString(result.errors[0]), R"(Type {
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(toString(result.errors[0]), R"(Type function instance add<a, number> depends on generic function parameters but does not appear in the function signature; this construct cannot be type-checked at this time)");
}
else
{
LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK_EQ(toString(result.errors[0]), R"(Type
'(string) -> string' '(string) -> string'
could not be converted into could not be converted into
'((number) -> number)?' '((number) -> number)?'
@ -1651,7 +1767,8 @@ could not be converted into
caused by: caused by:
Argument #1 type is not compatible. Argument #1 type is not compatible.
Type 'number' could not be converted into 'string')"); Type 'number' could not be converted into 'string')");
CHECK_EQ(toString(result.errors[1]), R"(Type 'string' could not be converted into 'number')"); CHECK_EQ(toString(result.errors[1]), R"(Type 'string' could not be converted into 'number')");
}
} }
TEST_CASE_FIXTURE(Fixture, "strict_mode_ok_with_missing_arguments") TEST_CASE_FIXTURE(Fixture, "strict_mode_ok_with_missing_arguments")
@ -1666,6 +1783,9 @@ TEST_CASE_FIXTURE(Fixture, "strict_mode_ok_with_missing_arguments")
TEST_CASE_FIXTURE(Fixture, "function_statement_sealed_table_assignment_through_indexer") TEST_CASE_FIXTURE(Fixture, "function_statement_sealed_table_assignment_through_indexer")
{ {
// FIXME: CLI-116122 bug where `t:b` does not check against the type from the indexer annotation on `t`.
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
local t: {[string]: () -> number} = {} local t: {[string]: () -> number} = {}
@ -1708,6 +1828,9 @@ TEST_CASE_FIXTURE(Fixture, "too_few_arguments_variadic")
TEST_CASE_FIXTURE(Fixture, "too_few_arguments_variadic_generic") TEST_CASE_FIXTURE(Fixture, "too_few_arguments_variadic_generic")
{ {
// FIXME: CLI-116157 variadic and generic type packs seem to be interacting incorrectly.
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
function test(a: number, b: string, ...) function test(a: number, b: string, ...)
return 1 return 1
@ -1733,6 +1856,9 @@ wrapper(test)
TEST_CASE_FIXTURE(BuiltinsFixture, "too_few_arguments_variadic_generic2") TEST_CASE_FIXTURE(BuiltinsFixture, "too_few_arguments_variadic_generic2")
{ {
// FIXME: CLI-116157 variadic and generic type packs seem to be interacting incorrectly.
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
function test(a: number, b: string, ...) function test(a: number, b: string, ...)
return 1 return 1
@ -1807,7 +1933,7 @@ TEST_CASE_FIXTURE(Fixture, "dont_infer_parameter_types_for_functions_from_their_
CHECK_EQ("<a>(a) -> a", toString(requireType("f"))); CHECK_EQ("<a>(a) -> a", toString(requireType("f")));
if (FFlag::DebugLuauDeferredConstraintResolution) if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ("<a>({+ p: {+ q: a +} +}) -> a & ~false", toString(requireType("g"))); CHECK_EQ("({ read p: { read q: unknown } }) -> ~(false?)?", toString(requireType("g")));
else else
CHECK_EQ("({+ p: {+ q: nil +} +}) -> nil", toString(requireType("g"))); CHECK_EQ("({+ p: {+ q: nil +} +}) -> nil", toString(requireType("g")));
} }
@ -1854,24 +1980,43 @@ u.b().foo()
)"); )");
LUAU_REQUIRE_ERROR_COUNT(9, result); LUAU_REQUIRE_ERROR_COUNT(9, result);
CHECK_EQ(toString(result.errors[0]), "Argument count mismatch. Function 'foo1' expects 1 argument, but none are specified"); if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ(toString(result.errors[1]), "Argument count mismatch. Function 'foo2' expects 1 to 2 arguments, but none are specified"); {
CHECK_EQ(toString(result.errors[2]), "Argument count mismatch. Function 'foo3' expects 1 to 3 arguments, but none are specified"); // These improvements to the error messages are currently regressed in the new type solver.
CHECK_EQ(toString(result.errors[3]), "Argument count mismatch. Function 'string.find' expects 2 to 4 arguments, but none are specified"); CHECK_EQ(toString(result.errors[0]), "Argument count mismatch. Function expects 1 argument, but none are specified");
CHECK_EQ(toString(result.errors[4]), "Argument count mismatch. Function 't.foo' expects at least 1 argument, but none are specified"); CHECK_EQ(toString(result.errors[1]), "Argument count mismatch. Function expects 1 to 2 arguments, but none are specified");
CHECK_EQ(toString(result.errors[5]), "Argument count mismatch. Function 't.bar' expects 2 to 3 arguments, but only 1 is specified"); CHECK_EQ(toString(result.errors[2]), "Argument count mismatch. Function expects 1 to 3 arguments, but none are specified");
CHECK_EQ(toString(result.errors[6]), "Argument count mismatch. Function 'u.a.foo' expects at least 1 argument, but none are specified"); CHECK_EQ(toString(result.errors[3]), "Argument count mismatch. Function expects 2 to 4 arguments, but none are specified");
CHECK_EQ(toString(result.errors[7]), "Argument count mismatch. Function 'u.a.foo' expects at least 1 argument, but none are specified"); CHECK_EQ(toString(result.errors[4]), "Argument count mismatch. Function expects at least 1 argument, but none are specified");
CHECK_EQ(toString(result.errors[8]), "Argument count mismatch. Function expects at least 1 argument, but none are specified"); CHECK_EQ(toString(result.errors[5]), "Argument count mismatch. Function expects 2 to 3 arguments, but only 1 is specified");
CHECK_EQ(toString(result.errors[6]), "Argument count mismatch. Function expects at least 1 argument, but none are specified");
CHECK_EQ(toString(result.errors[7]), "Argument count mismatch. Function expects at least 1 argument, but none are specified");
CHECK_EQ(toString(result.errors[8]), "Argument count mismatch. Function expects at least 1 argument, but none are specified");
}
else
{
CHECK_EQ(toString(result.errors[0]), "Argument count mismatch. Function 'foo1' expects 1 argument, but none are specified");
CHECK_EQ(toString(result.errors[1]), "Argument count mismatch. Function 'foo2' expects 1 to 2 arguments, but none are specified");
CHECK_EQ(toString(result.errors[2]), "Argument count mismatch. Function 'foo3' expects 1 to 3 arguments, but none are specified");
CHECK_EQ(toString(result.errors[3]), "Argument count mismatch. Function 'string.find' expects 2 to 4 arguments, but none are specified");
CHECK_EQ(toString(result.errors[4]), "Argument count mismatch. Function 't.foo' expects at least 1 argument, but none are specified");
CHECK_EQ(toString(result.errors[5]), "Argument count mismatch. Function 't.bar' expects 2 to 3 arguments, but only 1 is specified");
CHECK_EQ(toString(result.errors[6]), "Argument count mismatch. Function 'u.a.foo' expects at least 1 argument, but none are specified");
CHECK_EQ(toString(result.errors[7]), "Argument count mismatch. Function 'u.a.foo' expects at least 1 argument, but none are specified");
CHECK_EQ(toString(result.errors[8]), "Argument count mismatch. Function expects at least 1 argument, but none are specified");
}
} }
// This might be surprising, but since 'any' became optional, unannotated functions in non-strict 'expect' 0 arguments // This might be surprising, but since 'any' became optional, unannotated functions in non-strict 'expect' 0 arguments
TEST_CASE_FIXTURE(BuiltinsFixture, "improved_function_arg_mismatch_error_nonstrict") TEST_CASE_FIXTURE(BuiltinsFixture, "improved_function_arg_mismatch_error_nonstrict")
{ {
// This behavior is not part of the current specification of the new type solver.
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
--!nonstrict --!nonstrict
local function foo(a, b) end local function foo(a, b) end
foo(string.find("hello", "e")) foo(string.find("hello", "e"))
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
@ -1880,6 +2025,9 @@ foo(string.find("hello", "e"))
TEST_CASE_FIXTURE(Fixture, "luau_subtyping_is_np_hard") TEST_CASE_FIXTURE(Fixture, "luau_subtyping_is_np_hard")
{ {
// The case that _should_ succeed here (`z = x`) does not currently in the new solver.
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
--!strict --!strict
@ -1954,7 +2102,16 @@ TEST_CASE_FIXTURE(Fixture, "concrete_functions_are_not_supertypes_of_function")
LUAU_REQUIRE_ERROR_COUNT(2, result); LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK(6 == result.errors[0].location.begin.line); CHECK(6 == result.errors[0].location.begin.line);
auto tm1 = get<TypeMismatch>(result.errors[0]);
REQUIRE(tm1);
CHECK("() -> ()" == toString(tm1->wantedType));
CHECK("function" == toString(tm1->givenType));
CHECK(7 == result.errors[1].location.begin.line); CHECK(7 == result.errors[1].location.begin.line);
auto tm2 = get<TypeMismatch>(result.errors[1]);
REQUIRE(tm2);
CHECK("<T>(T) -> T" == toString(tm2->wantedType));
CHECK("function" == toString(tm2->givenType));
} }
TEST_CASE_FIXTURE(Fixture, "other_things_are_not_related_to_function") TEST_CASE_FIXTURE(Fixture, "other_things_are_not_related_to_function")
@ -1991,9 +2148,8 @@ end
TEST_CASE_FIXTURE(BuiltinsFixture, "dont_assert_when_the_tarjan_limit_is_exceeded_during_generalization") TEST_CASE_FIXTURE(BuiltinsFixture, "dont_assert_when_the_tarjan_limit_is_exceeded_during_generalization")
{ {
ScopedFastInt sfi{FInt::LuauTarjanChildLimit, 2}; ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
if (!FFlag::DebugLuauDeferredConstraintResolution) ScopedFastInt sfi{FInt::LuauTarjanChildLimit, 1};
return;
CheckResult result = check(R"( CheckResult result = check(R"(
function f(t) function f(t)
@ -2003,8 +2159,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "dont_assert_when_the_tarjan_limit_is_exceede
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_MESSAGE(get<CodeTooComplex>(result.errors[0]), "Expected CodeTooComplex but got: " << toString(result.errors[0])); CHECK_MESSAGE(get<UnificationTooComplex>(result.errors[0]), "Expected UnificationTooComplex but got: " << toString(result.errors[0]));
CHECK(Location({1, 17}, {1, 18}) == result.errors[0].location);
} }
/* We had a bug under DCR where instantiated type packs had a nullptr scope. /* We had a bug under DCR where instantiated type packs had a nullptr scope.
@ -2014,7 +2169,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "dont_assert_when_the_tarjan_limit_is_exceede
TEST_CASE_FIXTURE(Fixture, "instantiated_type_packs_must_have_a_non_null_scope") TEST_CASE_FIXTURE(Fixture, "instantiated_type_packs_must_have_a_non_null_scope")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
function pcall<A..., R...>(...: A...): R... function pcall<A..., R...>(...: (A...) -> R...): (boolean, R...)
return nil :: any
end end
type Dispatch<A> = (A) -> () type Dispatch<A> = (A) -> ()
@ -2068,8 +2224,13 @@ TEST_CASE_FIXTURE(Fixture, "function_exprs_are_generalized_at_signature_scope_no
)"); )");
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
// note that b is not in the generic list; it is free, the unconstrained type of `bar`. if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK(toString(requireType("foo")) == "<a>(a) -> b"); CHECK(toString(requireType("foo")) == "((unknown) -> nil)?");
else
{
// note that b is not in the generic list; it is free, the unconstrained type of `bar`.
CHECK(toString(requireType("foo")) == "<a>(a) -> b");
}
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "param_1_and_2_both_takes_the_same_generic_but_their_arguments_are_incompatible") TEST_CASE_FIXTURE(BuiltinsFixture, "param_1_and_2_both_takes_the_same_generic_but_their_arguments_are_incompatible")
@ -2082,46 +2243,58 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "param_1_and_2_both_takes_the_same_generic_bu
local ret: number = foo(vec2, { x = 5 }) local ret: number = foo(vec2, { x = 5 })
)"); )");
// In the old solver, this produces a very strange result: if (FFlag::DebugLuauDeferredConstraintResolution)
// {
// Here, we instantiate `<a>(x: a, y: a?) -> a` with a fresh type `'a` for `a`. LUAU_REQUIRE_ERROR_COUNT(1, result);
// In argument #1, we unify `vec2` with `'a`.
// This is ok, so we record an equality constraint `'a` with `vec2`.
// In argument #2, we unify `{ x: number }` with `'a?`.
// This fails because `'a` has equality constraint with `vec2`,
// so `{ x: number } <: vec2?`, which is false.
//
// If the unifications were to be committed, then it'd result in the following type error:
//
// Type '{ x: number }' could not be converted into 'vec2?'
// caused by:
// [...] Table type '{ x: number }' not compatible with type 'vec2' because the former is missing field 'y'
//
// However, whenever we check the argument list, if there's an error, we don't commit the unifications, so it actually looks like this:
//
// Type '{ x: number }' could not be converted into 'a?'
// caused by:
// [...] Table type '{ x: number }' not compatible with type 'vec2' because the former is missing field 'y'
//
// Then finally, that generic is left floating free, and since the function returns that generic,
// that free type is then later bound to `number`, which succeeds and mutates the type graph.
// This again changes the type error where `a` becomes bound to `number`.
//
// Type '{ x: number }' could not be converted into 'number?'
// caused by:
// [...] Table type '{ x: number }' not compatible with type 'vec2' because the former is missing field 'y'
//
// Uh oh, that type error is extremely confusing for people who doesn't know how that went down.
// Really, what should happen is we roll each argument incompatibility into a union type, but that needs local type inference.
LUAU_REQUIRE_ERROR_COUNT(2, result); auto tm = get<TypeMismatch>(result.errors[0]);
REQUIRE(tm);
CHECK("number" == toString(tm->wantedType));
CHECK("{ x: number }" == toString(tm->givenType));
}
else
{
// In the old solver, this produces a very strange result:
//
// Here, we instantiate `<a>(x: a, y: a?) -> a` with a fresh type `'a` for `a`.
// In argument #1, we unify `vec2` with `'a`.
// This is ok, so we record an equality constraint `'a` with `vec2`.
// In argument #2, we unify `{ x: number }` with `'a?`.
// This fails because `'a` has equality constraint with `vec2`,
// so `{ x: number } <: vec2?`, which is false.
//
// If the unifications were to be committed, then it'd result in the following type error:
//
// Type '{ x: number }' could not be converted into 'vec2?'
// caused by:
// [...] Table type '{ x: number }' not compatible with type 'vec2' because the former is missing field 'y'
//
// However, whenever we check the argument list, if there's an error, we don't commit the unifications, so it actually looks like this:
//
// Type '{ x: number }' could not be converted into 'a?'
// caused by:
// [...] Table type '{ x: number }' not compatible with type 'vec2' because the former is missing field 'y'
//
// Then finally, that generic is left floating free, and since the function returns that generic,
// that free type is then later bound to `number`, which succeeds and mutates the type graph.
// This again changes the type error where `a` becomes bound to `number`.
//
// Type '{ x: number }' could not be converted into 'number?'
// caused by:
// [...] Table type '{ x: number }' not compatible with type 'vec2' because the former is missing field 'y'
//
// Uh oh, that type error is extremely confusing for people who doesn't know how that went down.
// Really, what should happen is we roll each argument incompatibility into a union type, but that needs local type inference.
const std::string expected = R"(Type '{ x: number }' could not be converted into 'vec2?' LUAU_REQUIRE_ERROR_COUNT(2, result);
const std::string expected = R"(Type '{ x: number }' could not be converted into 'vec2?'
caused by: caused by:
None of the union options are compatible. For example: None of the union options are compatible. For example:
Table type '{ x: number }' not compatible with type 'vec2' because the former is missing field 'y')"; Table type '{ x: number }' not compatible with type 'vec2' because the former is missing field 'y')";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
CHECK_EQ("Type 'vec2' could not be converted into 'number'", toString(result.errors[1])); CHECK_EQ("Type 'vec2' could not be converted into 'number'", toString(result.errors[1]));
}
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "param_1_and_2_both_takes_the_same_generic_but_their_arguments_are_incompatible_2") TEST_CASE_FIXTURE(BuiltinsFixture, "param_1_and_2_both_takes_the_same_generic_but_their_arguments_are_incompatible_2")
@ -2134,10 +2307,22 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "param_1_and_2_both_takes_the_same_generic_bu
local z: boolean = f(5, "five") local z: boolean = f(5, "five")
)"); )");
LUAU_REQUIRE_ERROR_COUNT(2, result); if (FFlag::DebugLuauDeferredConstraintResolution)
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(toString(result.errors[0]), "Type 'string' could not be converted into 'number'"); auto tm = get<TypeMismatch>(result.errors[0]);
CHECK_EQ(toString(result.errors[1]), "Type 'number' could not be converted into 'boolean'"); REQUIRE(tm);
CHECK("boolean" == toString(tm->wantedType));
CHECK("number | string" == toString(tm->givenType));
}
else
{
LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK_EQ(toString(result.errors[0]), "Type 'string' could not be converted into 'number'");
CHECK_EQ(toString(result.errors[1]), "Type 'number' could not be converted into 'boolean'");
}
} }
TEST_CASE_FIXTURE(Fixture, "attempt_to_call_an_intersection_of_tables") TEST_CASE_FIXTURE(Fixture, "attempt_to_call_an_intersection_of_tables")
@ -2183,10 +2368,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "attempt_to_call_an_intersection_of_tables_wi
TEST_CASE_FIXTURE(Fixture, "generic_packs_are_not_variadic") TEST_CASE_FIXTURE(Fixture, "generic_packs_are_not_variadic")
{ {
// This test is blocking CI until subtyping is complete.
if (!FFlag::DebugLuauDeferredConstraintResolution)
return;
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true}; ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
CheckResult result = check(R"( CheckResult result = check(R"(
@ -2201,7 +2382,9 @@ TEST_CASE_FIXTURE(Fixture, "generic_packs_are_not_variadic")
apply(add, 5) apply(add, 5)
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); // FIXME: this errored at some point, but doesn't anymore.
// the desired behavior here is erroring.
LUAU_REQUIRE_NO_ERRORS(result);
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "num_is_solved_before_num_or_str") TEST_CASE_FIXTURE(BuiltinsFixture, "num_is_solved_before_num_or_str")
@ -2316,14 +2499,11 @@ end
if (FFlag::DebugLuauDeferredConstraintResolution) if (FFlag::DebugLuauDeferredConstraintResolution)
{ {
LUAU_REQUIRE_ERROR_COUNT(4, result); LUAU_REQUIRE_ERROR_COUNT(5, result);
CHECK( CHECK(get<ConstraintSolvingIncompleteError>(result.errors[0]));
toString(result.errors[0]) ==
"Operator '-' could not be applied to operands of types unknown and number; there is no corresponding overload for __sub"
);
CHECK( CHECK(
toString(result.errors[1]) == toString(result.errors[1]) ==
"Operator '-' could not be applied to operands of types unknown and number; there is no corresponding overload for __sub" "Type pack '*blocked-tp-1*' could not be converted into 'boolean'; type *blocked-tp-1*.tail() (*blocked-tp-1*) is not a subtype of boolean (boolean)"
); );
CHECK( CHECK(
toString(result.errors[2]) == toString(result.errors[2]) ==
@ -2333,6 +2513,10 @@ end
toString(result.errors[3]) == toString(result.errors[3]) ==
"Operator '-' could not be applied to operands of types unknown and number; there is no corresponding overload for __sub" "Operator '-' could not be applied to operands of types unknown and number; there is no corresponding overload for __sub"
); );
CHECK(
toString(result.errors[4]) ==
"Operator '-' could not be applied to operands of types unknown and number; there is no corresponding overload for __sub"
);
} }
else else
{ {
@ -2415,17 +2599,15 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tf_suggest_arg_type_2")
frontend.options.retainFullTypeGraphs = false; frontend.options.retainFullTypeGraphs = false;
CheckResult result = check(R"( CheckResult result = check(R"(
local function escape_fslash(pre) local function escape_fslash(pre)
return (#pre % 2 == 0 and '\\' or '') .. pre .. '.' return (#pre % 2 == 0 and '\\' or '') .. pre .. '.'
end end
)"); )");
LUAU_REQUIRE_ERRORS(result); LUAU_REQUIRE_ERRORS(result);
auto err = get<ExplicitFunctionAnnotationRecommended>(result.errors.back()); auto err = get<NotATable>(result.errors.back());
LUAU_ASSERT(err); REQUIRE(err);
CHECK("unknown" == toString(err->recommendedReturn)); CHECK("a" == toString(err->ty));
REQUIRE(err->recommendedArgs.size() == 1);
CHECK("a" == toString(err->recommendedArgs[0].second));
} }
TEST_CASE_FIXTURE(Fixture, "local_function_fwd_decl_doesnt_crash") TEST_CASE_FIXTURE(Fixture, "local_function_fwd_decl_doesnt_crash")
@ -2463,11 +2645,26 @@ TEST_CASE_FIXTURE(Fixture, "bidirectional_checking_of_callback_property")
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_MESSAGE(get<UnknownProperty>(result.errors[0]), "Expected UnknownProperty but got " << result.errors[0]); if (FFlag::DebugLuauDeferredConstraintResolution)
{
auto tm = get<TypeMismatch>(result.errors[0]);
REQUIRE(tm);
Location location = result.errors[0].location; CHECK("((Point) -> ())?" == toString(tm->wantedType));
CHECK(location.begin.line == 7); CHECK("({ read z: number }) -> ()" == toString(tm->givenType));
CHECK(location.end.line == 7);
Location location = result.errors[0].location;
CHECK(location.begin.line == 6);
CHECK(location.end.line == 8);
}
else
{
CHECK_MESSAGE(get<UnknownProperty>(result.errors[0]), "Expected UnknownProperty but got " << result.errors[0]);
Location location = result.errors[0].location;
CHECK(location.begin.line == 7);
CHECK(location.end.line == 7);
}
} }
TEST_CASE_FIXTURE(ClassFixture, "bidirectional_inference_of_class_methods") TEST_CASE_FIXTURE(ClassFixture, "bidirectional_inference_of_class_methods")
@ -2698,7 +2895,7 @@ TEST_CASE_FIXTURE(Fixture, "unifier_should_not_bind_free_types")
auto tm2 = get<TypePackMismatch>(result.errors[1]); auto tm2 = get<TypePackMismatch>(result.errors[1]);
REQUIRE(tm2); REQUIRE(tm2);
CHECK(toString(tm2->wantedTp) == "string"); CHECK(toString(tm2->wantedTp) == "string");
CHECK(toString(tm2->givenTp) == "~(false?)"); CHECK(toString(tm2->givenTp) == "buffer | class | function | number | string | table | thread | true");
} }
else else
{ {

View File

@ -4,6 +4,7 @@
#include "Fixture.h" #include "Fixture.h"
#include "ScopedFlags.h"
#include "doctest.h" #include "doctest.h"
using namespace Luau; using namespace Luau;
@ -331,6 +332,9 @@ TEST_CASE_FIXTURE(Fixture, "table_intersection_write_sealed")
TEST_CASE_FIXTURE(Fixture, "table_intersection_write_sealed_indirect") TEST_CASE_FIXTURE(Fixture, "table_intersection_write_sealed_indirect")
{ {
ScopedFastFlag dcr{
FFlag::DebugLuauDeferredConstraintResolution, false
}; // CLI-116476 Subtyping between type alias and an equivalent but not named type isn't working.
CheckResult result = check(R"( CheckResult result = check(R"(
type X = { x: (number) -> number } type X = { x: (number) -> number }
type Y = { y: (string) -> string } type Y = { y: (string) -> string }
@ -368,6 +372,7 @@ caused by:
TEST_CASE_FIXTURE(Fixture, "table_write_sealed_indirect") TEST_CASE_FIXTURE(Fixture, "table_write_sealed_indirect")
{ {
ScopedFastFlag dcr{FFlag::DebugLuauDeferredConstraintResolution, false}; // CLI-
// After normalization, previous 'table_intersection_write_sealed_indirect' is identical to this one // After normalization, previous 'table_intersection_write_sealed_indirect' is identical to this one
CheckResult result = check(R"( CheckResult result = check(R"(
type XY = { x: (number) -> number, y: (string) -> string } type XY = { x: (number) -> number, y: (string) -> string }
@ -576,6 +581,9 @@ could not be converted into
TEST_CASE_FIXTURE(Fixture, "union_saturate_overloaded_functions") TEST_CASE_FIXTURE(Fixture, "union_saturate_overloaded_functions")
{ {
ScopedFastFlag dcr{
FFlag::DebugLuauDeferredConstraintResolution, false
}; // CLI-116474 Semantic subtyping of assignments needs to decide how to interpret intersections of functions
CheckResult result = check(R"( CheckResult result = check(R"(
function f(x: ((number) -> number) & ((string) -> string)) function f(x: ((number) -> number) & ((string) -> string))
local y : ((number | string) -> (number | string)) = x -- OK local y : ((number | string) -> (number | string)) = x -- OK
@ -754,6 +762,9 @@ could not be converted into
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_result") TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_result")
{ {
ScopedFastFlag dcr{
FFlag::DebugLuauDeferredConstraintResolution, false
}; // CLI-116474 Semantic subtyping of assignments needs to decide how to interpret intersections of functions
CheckResult result = check(R"( CheckResult result = check(R"(
function f<a...,b...>() function f<a...,b...>()
function g(x : ((number) -> number) & ((nil) -> unknown)) function g(x : ((number) -> number) & ((nil) -> unknown))
@ -773,6 +784,9 @@ could not be converted into
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_arguments") TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_arguments")
{ {
ScopedFastFlag dcr{
FFlag::DebugLuauDeferredConstraintResolution, false
}; // CLI-116474 Semantic subtyping of assignments needs to decide how to interpret intersections of functions
CheckResult result = check(R"( CheckResult result = check(R"(
function f<a...,b...>() function f<a...,b...>()
function g(x : ((number) -> number?) & ((unknown) -> string?)) function g(x : ((number) -> number?) & ((unknown) -> string?))
@ -801,12 +815,36 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_result")
end end
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); if (FFlag::DebugLuauDeferredConstraintResolution)
const std::string expected = R"(Type {
LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK_EQ(
R"(Type
'((nil) -> never) & ((number) -> number)'
could not be converted into
'(number?) -> number'; type ((nil) -> never) & ((number) -> number)[0].arguments()[0] (number) is not a supertype of (number?) -> number.arguments()[0][1] (nil)
type ((nil) -> never) & ((number) -> number)[1].arguments()[0] (nil) is not a supertype of (number?) -> number.arguments()[0][0] (number))",
toString(result.errors[0])
);
CHECK_EQ(
R"(Type
'((nil) -> never) & ((number) -> number)'
could not be converted into
'(number?) -> never'; type ((nil) -> never) & ((number) -> number)[0].arguments()[0] (number) is not a supertype of (number?) -> never.arguments()[0][1] (nil)
type ((nil) -> never) & ((number) -> number)[0].returns()[0] (number) is not a subtype of (number?) -> never.returns()[0] (never)
type ((nil) -> never) & ((number) -> number)[1].arguments()[0] (nil) is not a supertype of (number?) -> never.arguments()[0][0] (number))",
toString(result.errors[1])
);
}
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type
'((nil) -> never) & ((number) -> number)' '((nil) -> never) & ((number) -> number)'
could not be converted into could not be converted into
'(number?) -> never'; none of the intersection parts are compatible)"; '(number?) -> never'; none of the intersection parts are compatible)";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
}
} }
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_arguments") TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_arguments")
@ -830,6 +868,9 @@ could not be converted into
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_overlapping_results_and_variadics") TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_overlapping_results_and_variadics")
{ {
ScopedFastFlag dcr{
FFlag::DebugLuauDeferredConstraintResolution, false
}; // CLI-116474 Semantic subtyping of assignments needs to decide how to interpret intersections of functions
CheckResult result = check(R"( CheckResult result = check(R"(
function f(x : ((string?) -> (string | number)) & ((number?) -> ...number)) function f(x : ((string?) -> (string | number)) & ((number?) -> ...number))
local y : ((nil) -> (number, number?)) = x -- OK local y : ((nil) -> (number, number?)) = x -- OK
@ -856,11 +897,18 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_1")
end end
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ( {
toString(result.errors[0]), LUAU_REQUIRE_NO_ERRORS(result);
"Type '(() -> (a...)) & (() -> (b...))' could not be converted into '() -> ()'; none of the intersection parts are compatible" }
); else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(
toString(result.errors[0]),
"Type '(() -> (a...)) & (() -> (b...))' could not be converted into '() -> ()'; none of the intersection parts are compatible"
);
}
} }
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_2") TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_2")
@ -874,11 +922,18 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_2")
end end
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ( {
toString(result.errors[0]), LUAU_REQUIRE_NO_ERRORS(result);
"Type '((a...) -> ()) & ((b...) -> ())' could not be converted into '() -> ()'; none of the intersection parts are compatible" }
); else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(
toString(result.errors[0]),
"Type '((a...) -> ()) & ((b...) -> ())' could not be converted into '() -> ()'; none of the intersection parts are compatible"
);
}
} }
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_3") TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_3")
@ -892,12 +947,19 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_3")
end end
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); if (FFlag::DebugLuauDeferredConstraintResolution)
const std::string expected = R"(Type {
LUAU_REQUIRE_NO_ERRORS(result);
}
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type
'(() -> (a...)) & (() -> (number?, a...))' '(() -> (a...)) & (() -> (number?, a...))'
could not be converted into could not be converted into
'() -> number'; none of the intersection parts are compatible)"; '() -> number'; none of the intersection parts are compatible)";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
}
} }
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_4") TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_4")
@ -912,11 +974,27 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_4")
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ(
R"(Type
'((a...) -> ()) & ((number, a...) -> number)' '((a...) -> ()) & ((number, a...) -> number)'
could not be converted into could not be converted into
'(number?) -> ()'; none of the intersection parts are compatible)"; '((a...) -> ()) & ((number, a...) -> number)'; at [0].returns(), is not a subtype of number
CHECK_EQ(expected, toString(result.errors[0])); type ((a...) -> ()) & ((number, a...) -> number)[1].arguments().tail() (a...) is not a supertype of ((a...) -> ()) & ((number, a...) -> number)[0].arguments().tail() (a...))",
toString(result.errors[0])
);
}
else
{
CHECK_EQ(
R"(Type
'((a...) -> ()) & ((number, a...) -> number)'
could not be converted into
'(number?) -> ()'; none of the intersection parts are compatible)",
toString(result.errors[0])
);
}
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatables") TEST_CASE_FIXTURE(BuiltinsFixture, "intersect_metatables")

View File

@ -32,7 +32,14 @@ TEST_CASE_FIXTURE(Fixture, "for_loop")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(*builtinTypes->numberType, *requireType("q")); if (FFlag::DebugLuauDeferredConstraintResolution)
{
// Luau cannot see that the loop must always run at least once, so we
// think that q could be nil.
CHECK("number?" == toString(requireType("q")));
}
else
CHECK_EQ(*builtinTypes->numberType, *requireType("q"));
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "iteration_no_table_passed") TEST_CASE_FIXTURE(BuiltinsFixture, "iteration_no_table_passed")
@ -107,8 +114,17 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "iteration_regression_issue_69967_alt")
)"); )");
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ("number", toString(requireType("x"))); if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ("string", toString(requireType("y"))); {
// It's possible for the loop body to execute 0 times.
CHECK("number?" == toString(requireType("x")));
CHECK("string?" == toString(requireType("y")));
}
else
{
CHECK_EQ("number", toString(requireType("x")));
CHECK_EQ("string", toString(requireType("y")));
}
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop") TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop")
@ -124,12 +140,23 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(*builtinTypes->numberType, *requireType("n")); if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ(*builtinTypes->stringType, *requireType("s")); {
CHECK("number?" == toString(requireType("n")));
CHECK("string?" == toString(requireType("s")));
}
else
{
CHECK_EQ(*builtinTypes->numberType, *requireType("n"));
CHECK_EQ(*builtinTypes->stringType, *requireType("s"));
}
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop_with_next") TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop_with_next")
{ {
// CLI-116494 The generics K and V are leaking out of the next() function somehow.
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
local n local n
local s local s
@ -240,11 +267,17 @@ TEST_CASE_FIXTURE(Fixture, "for_in_loop_on_error")
LUAU_REQUIRE_ERROR_COUNT(2, result); LUAU_REQUIRE_ERROR_COUNT(2, result);
TypeId p = requireType("p"); TypeId p = requireType("p");
CHECK_EQ("*error-type*", toString(p)); if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ("*error-type*?", toString(p));
else
CHECK_EQ("*error-type*", toString(p));
} }
TEST_CASE_FIXTURE(Fixture, "for_in_loop_on_non_function") TEST_CASE_FIXTURE(Fixture, "for_in_loop_on_non_function")
{ {
// We report a spuriouus duplicate error here.
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
local bad_iter = 5 local bad_iter = 5
@ -259,6 +292,9 @@ TEST_CASE_FIXTURE(Fixture, "for_in_loop_on_non_function")
TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop_error_on_factory_not_returning_the_right_amount_of_values") TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop_error_on_factory_not_returning_the_right_amount_of_values")
{ {
// Spurious duplicate errors
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
local function hasDivisors(value: number, table) local function hasDivisors(value: number, table)
return false return false
@ -308,6 +344,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop_error_on_factory_not_returning_t
TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop_error_on_iterator_requiring_args_but_none_given") TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop_error_on_iterator_requiring_args_but_none_given")
{ {
// CLI-116496
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
function prime_iter(state, index) function prime_iter(state, index)
return 1 return 1
@ -380,7 +419,10 @@ TEST_CASE_FIXTURE(Fixture, "while_loop")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(*builtinTypes->numberType, *requireType("i")); if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK("number?" == toString(requireType("i")));
else
CHECK_EQ(*builtinTypes->numberType, *requireType("i"));
} }
TEST_CASE_FIXTURE(Fixture, "repeat_loop") TEST_CASE_FIXTURE(Fixture, "repeat_loop")
@ -394,7 +436,10 @@ TEST_CASE_FIXTURE(Fixture, "repeat_loop")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(*builtinTypes->stringType, *requireType("i")); if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK("string?" == toString(requireType("i")));
else
CHECK_EQ(*builtinTypes->stringType, *requireType("i"));
} }
TEST_CASE_FIXTURE(Fixture, "repeat_loop_condition_binds_to_its_block") TEST_CASE_FIXTURE(Fixture, "repeat_loop_condition_binds_to_its_block")
@ -490,7 +535,15 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "properly_infer_iteratee_is_a_free_table")
end end
)"); )");
LUAU_REQUIRE_NO_ERRORS(result); if (FFlag::DebugLuauDeferredConstraintResolution)
{
// In the new solver, we infer iter: unknown and so we warn on use of its properties.
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK(get<UnknownProperty>(result.errors[0]));
CHECK(Location{{2, 12}, {2, 18}} == result.errors[0].location);
}
else
LUAU_REQUIRE_NO_ERRORS(result);
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "correctly_scope_locals_while") TEST_CASE_FIXTURE(BuiltinsFixture, "correctly_scope_locals_while")
@ -532,7 +585,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "ipairs_produces_integral_indices")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
REQUIRE_EQ("number", toString(requireType("key"))); if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK("number?" == toString(requireType("key")));
else
REQUIRE_EQ("number", toString(requireType("key")));
} }
TEST_CASE_FIXTURE(Fixture, "for_in_loop_where_iteratee_is_free") TEST_CASE_FIXTURE(Fixture, "for_in_loop_where_iteratee_is_free")
@ -639,6 +695,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "unreachable_code_after_infinite_loop")
TEST_CASE_FIXTURE(BuiltinsFixture, "loop_typecheck_crash_on_empty_optional") TEST_CASE_FIXTURE(BuiltinsFixture, "loop_typecheck_crash_on_empty_optional")
{ {
// CLI-116498 Sometimes you can iterate over tables with no indexers.
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
ScopedFastFlag sff{FFlag::LuauOkWithIteratingOverTableProperties, true}; ScopedFastFlag sff{FFlag::LuauOkWithIteratingOverTableProperties, true};
CheckResult result = check(R"( CheckResult result = check(R"(
@ -704,6 +764,9 @@ TEST_CASE_FIXTURE(Fixture, "loop_iter_basic")
TEST_CASE_FIXTURE(Fixture, "loop_iter_trailing_nil") TEST_CASE_FIXTURE(Fixture, "loop_iter_trailing_nil")
{ {
// CLI-116498 Sometimes you can iterate over tables with no indexers.
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
local t: {string} = {} local t: {string} = {}
local extra local extra
@ -718,7 +781,11 @@ TEST_CASE_FIXTURE(Fixture, "loop_iter_trailing_nil")
TEST_CASE_FIXTURE(Fixture, "loop_iter_no_indexer_strict") TEST_CASE_FIXTURE(Fixture, "loop_iter_no_indexer_strict")
{ {
ScopedFastFlag sff{FFlag::LuauOkWithIteratingOverTableProperties, true}; // CLI-116498 Sometimes you can iterate over tables with no indexers.
ScopedFastFlag sff[] = {
{FFlag::DebugLuauDeferredConstraintResolution, false},
{FFlag::LuauOkWithIteratingOverTableProperties, true}
};
CheckResult result = check(R"( CheckResult result = check(R"(
local t = {} local t = {}
@ -742,7 +809,8 @@ TEST_CASE_FIXTURE(Fixture, "loop_iter_no_indexer_nonstrict")
TEST_CASE_FIXTURE(BuiltinsFixture, "loop_iter_metamethod_nil") TEST_CASE_FIXTURE(BuiltinsFixture, "loop_iter_metamethod_nil")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) // CLI-116499 Free types persisting until typechecking time.
if (1 || !FFlag::DebugLuauDeferredConstraintResolution)
return; return;
CheckResult result = check(R"( CheckResult result = check(R"(
@ -757,7 +825,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "loop_iter_metamethod_nil")
TEST_CASE_FIXTURE(BuiltinsFixture, "loop_iter_metamethod_not_enough_returns") TEST_CASE_FIXTURE(BuiltinsFixture, "loop_iter_metamethod_not_enough_returns")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) // CLI-116500
if (1 || !FFlag::DebugLuauDeferredConstraintResolution)
return; return;
CheckResult result = check(R"( CheckResult result = check(R"(
@ -778,7 +847,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "loop_iter_metamethod_not_enough_returns")
TEST_CASE_FIXTURE(BuiltinsFixture, "loop_iter_metamethod_ok") TEST_CASE_FIXTURE(BuiltinsFixture, "loop_iter_metamethod_ok")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) // CLI-116500
if (1 || !FFlag::DebugLuauDeferredConstraintResolution)
return; return;
CheckResult result = check(R"( CheckResult result = check(R"(
@ -794,7 +864,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "loop_iter_metamethod_ok")
TEST_CASE_FIXTURE(BuiltinsFixture, "loop_iter_metamethod_ok_with_inference") TEST_CASE_FIXTURE(BuiltinsFixture, "loop_iter_metamethod_ok_with_inference")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) // CLI-116500
if (1 || !FFlag::DebugLuauDeferredConstraintResolution)
return; return;
CheckResult result = check(R"( CheckResult result = check(R"(
@ -844,6 +915,10 @@ TEST_CASE_FIXTURE(Fixture, "for_loop_lower_bound_is_string_3")
TEST_CASE_FIXTURE(BuiltinsFixture, "cli_68448_iterators_need_not_accept_nil") TEST_CASE_FIXTURE(BuiltinsFixture, "cli_68448_iterators_need_not_accept_nil")
{ {
// CLI-116500
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check(R"( CheckResult result = check(R"(
local function makeEnum(members) local function makeEnum(members)
local enum = {} local enum = {}
@ -975,6 +1050,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "dcr_iteration_fragmented_keys")
TEST_CASE_FIXTURE(BuiltinsFixture, "dcr_xpath_candidates") TEST_CASE_FIXTURE(BuiltinsFixture, "dcr_xpath_candidates")
{ {
// CLI-116500
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check(R"( CheckResult result = check(R"(
type Instance = {} type Instance = {}
local function findCandidates(instances: { Instance }, path: { string }) local function findCandidates(instances: { Instance }, path: { string })
@ -1004,11 +1083,18 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "dcr_iteration_on_never_gives_never")
)"); )");
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
CHECK(toString(requireType("ans")) == "never");
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK("never?" == toString(requireType("ans"))); // CLI-114134 egraph simplification. Should just be nil.
else
CHECK(toString(requireType("ans")) == "never");
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "iterate_over_properties") TEST_CASE_FIXTURE(BuiltinsFixture, "iterate_over_properties")
{ {
// CLI-116498 - Sometimes you can iterate over tables with no indexer.
ScopedFastFlag sff0{FFlag::DebugLuauDeferredConstraintResolution, false};
ScopedFastFlag sff{FFlag::LuauOkWithIteratingOverTableProperties, true}; ScopedFastFlag sff{FFlag::LuauOkWithIteratingOverTableProperties, true};
CheckResult result = check(R"( CheckResult result = check(R"(

View File

@ -56,12 +56,24 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "require")
return {hooty=hooty} return {hooty=hooty}
)"; )";
fileResolver.source["game/B"] = R"( if (FFlag::DebugLuauDeferredConstraintResolution)
local Hooty = require(game.A) {
fileResolver.source["game/B"] = R"(
local Hooty = require(game.A)
local h -- free! local h = 4
local i = Hooty.hooty(h) local i = Hooty.hooty(h)
)"; )";
}
else
{
fileResolver.source["game/B"] = R"(
local Hooty = require(game.A)
local h -- free!
local i = Hooty.hooty(h)
)";
}
CheckResult aResult = frontend.check("game/A"); CheckResult aResult = frontend.check("game/A");
dumpErrors(aResult); dumpErrors(aResult);

View File

@ -18,6 +18,9 @@ TEST_SUITE_BEGIN("TypeInferOOP");
TEST_CASE_FIXTURE(Fixture, "dont_suggest_using_colon_rather_than_dot_if_not_defined_with_colon") TEST_CASE_FIXTURE(Fixture, "dont_suggest_using_colon_rather_than_dot_if_not_defined_with_colon")
{ {
// CLI-116571 method calls are missing arity checking?
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
local someTable = {} local someTable = {}
@ -33,6 +36,9 @@ TEST_CASE_FIXTURE(Fixture, "dont_suggest_using_colon_rather_than_dot_if_not_defi
TEST_CASE_FIXTURE(Fixture, "dont_suggest_using_colon_rather_than_dot_if_it_wont_help_2") TEST_CASE_FIXTURE(Fixture, "dont_suggest_using_colon_rather_than_dot_if_it_wont_help_2")
{ {
// CLI-116571 method calls are missing arity checking?
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
local someTable = {} local someTable = {}
@ -138,7 +144,10 @@ TEST_CASE_FIXTURE(Fixture, "inferring_hundreds_of_self_calls_should_not_suffocat
)"); )");
ModulePtr module = getMainModule(); ModulePtr module = getMainModule();
CHECK_GE(50, module->internalTypes.types.size()); if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_GE(80, module->internalTypes.types.size());
else
CHECK_GE(50, module->internalTypes.types.size());
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "object_constructor_can_refer_to_method_of_self") TEST_CASE_FIXTURE(BuiltinsFixture, "object_constructor_can_refer_to_method_of_self")
@ -410,10 +419,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cycle_between_object_constructor_and_alias")
TEST_CASE_FIXTURE(BuiltinsFixture, "promise_type_error_too_complex" * doctest::timeout(2)) TEST_CASE_FIXTURE(BuiltinsFixture, "promise_type_error_too_complex" * doctest::timeout(2))
{ {
// TODO: LTI changes to function call resolution have rendered this test impossibly slow
// shared self should fix it, but there may be other mitigations possible as well
REQUIRE(!FFlag::DebugLuauDeferredConstraintResolution);
frontend.options.retainFullTypeGraphs = false; frontend.options.retainFullTypeGraphs = false;
// Used `luau-reduce` tool to extract a minimal reproduction. // Used `luau-reduce` tool to extract a minimal reproduction.

View File

@ -189,11 +189,15 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typecheck_overloaded_multiply_that_is_an_int
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ("Vec3", toString(requireType("a"))); CHECK("Vec3" == toString(requireType("a")));
CHECK_EQ("Vec3", toString(requireType("b"))); CHECK("Vec3" == toString(requireType("b")));
CHECK_EQ("Vec3", toString(requireType("c"))); CHECK("Vec3" == toString(requireType("c")));
CHECK_EQ("Vec3", toString(requireType("d"))); CHECK("Vec3" == toString(requireType("d")));
CHECK_EQ("Vec3", toString(requireType("e")));
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK("mul<Vec3, string>" == toString(requireType("e")));
else
CHECK_EQ("Vec3", toString(requireType("e")));
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "typecheck_overloaded_multiply_that_is_an_intersection_on_rhs") TEST_CASE_FIXTURE(BuiltinsFixture, "typecheck_overloaded_multiply_that_is_an_intersection_on_rhs")
@ -223,11 +227,15 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typecheck_overloaded_multiply_that_is_an_int
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ("Vec3", toString(requireType("a"))); CHECK("Vec3" == toString(requireType("a")));
CHECK_EQ("Vec3", toString(requireType("b"))); CHECK("Vec3" == toString(requireType("b")));
CHECK_EQ("Vec3", toString(requireType("c"))); CHECK("Vec3" == toString(requireType("c")));
CHECK_EQ("Vec3", toString(requireType("d"))); CHECK("Vec3" == toString(requireType("d")));
CHECK_EQ("Vec3", toString(requireType("e")));
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK("mul<string, Vec3>" == toString(requireType("e")));
else
CHECK_EQ("Vec3", toString(requireType("e")));
} }
TEST_CASE_FIXTURE(Fixture, "compare_numbers") TEST_CASE_FIXTURE(Fixture, "compare_numbers")
@ -477,7 +485,13 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "compound_assign_result_must_be_compatible_wi
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK(result.errors[0] == TypeError{Location{{13, 8}, {13, 14}}, TypeMismatch{requireType("x"), builtinTypes->numberType}});
CHECK(Location{{13, 8}, {13, 14}} == result.errors[0].location);
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
REQUIRE(tm);
CHECK("x" == toString(tm->wantedType));
CHECK("number" == toString(tm->givenType));
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "compound_assign_mismatch_metatable") TEST_CASE_FIXTURE(BuiltinsFixture, "compound_assign_mismatch_metatable")
@ -593,17 +607,32 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typecheck_unary_minus_error")
local a = -foo local a = -foo
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); if (FFlag::DebugLuauDeferredConstraintResolution)
{
LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK_EQ("string", toString(requireType("a"))); CHECK(get<UninhabitedTypeFunction>(result.errors[0]));
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]); // This second error is spurious. We should not be reporting it.
REQUIRE_EQ(*tm->wantedType, *builtinTypes->booleanType); CHECK(get<TypeMismatch>(result.errors[1]));
// given type is the typeof(foo) which is complex to compare against }
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ("string", toString(requireType("a")));
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
REQUIRE_EQ(*tm->wantedType, *builtinTypes->booleanType);
// given type is the typeof(foo) which is complex to compare against
}
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "typecheck_unary_len_error") TEST_CASE_FIXTURE(BuiltinsFixture, "typecheck_unary_len_error")
{ {
// CLI-116463
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
--!strict --!strict
local mt = {} local mt = {}
@ -673,22 +702,34 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "disallow_string_and_types_without_metatables
LUAU_REQUIRE_ERROR_COUNT(3, result); LUAU_REQUIRE_ERROR_COUNT(3, result);
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
REQUIRE(tm);
CHECK_EQ(*tm->wantedType, *builtinTypes->numberType);
CHECK_EQ(*tm->givenType, *builtinTypes->stringType);
GenericError* gen1 = get<GenericError>(result.errors[1]);
REQUIRE(gen1);
if (FFlag::DebugLuauDeferredConstraintResolution) if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ(gen1->message, "Operator + is not applicable for '{ value: number }' and 'number' because neither type has a metatable"); {
CHECK(get<UninhabitedTypeFunction>(result.errors[0]));
CHECK(Location{{2, 18}, {2, 30}} == result.errors[0].location);
CHECK(get<UninhabitedTypeFunction>(result.errors[1]));
CHECK(Location{{8, 18}, {8, 25}} == result.errors[1].location);
CHECK(get<UninhabitedTypeFunction>(result.errors[2]));
CHECK(Location{{24, 18}, {24, 27}} == result.errors[2].location);
}
else else
CHECK_EQ(gen1->message, "Binary operator '+' not supported by types 'foo' and 'number'"); {
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
REQUIRE_MESSAGE(tm, "Expected a TypeMismatch but got " << result.errors[0]);
CHECK_EQ(*tm->wantedType, *builtinTypes->numberType);
CHECK_EQ(*tm->givenType, *builtinTypes->stringType);
TypeMismatch* tm2 = get<TypeMismatch>(result.errors[2]); GenericError* gen1 = get<GenericError>(result.errors[1]);
REQUIRE(tm2); REQUIRE(gen1);
CHECK_EQ(*tm2->wantedType, *builtinTypes->numberType); if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ(*tm2->givenType, *requireType("foo")); CHECK_EQ(gen1->message, "Operator + is not applicable for '{ value: number }' and 'number' because neither type has a metatable");
else
CHECK_EQ(gen1->message, "Binary operator '+' not supported by types 'foo' and 'number'");
TypeMismatch* tm2 = get<TypeMismatch>(result.errors[2]);
REQUIRE(tm2);
CHECK_EQ(*tm2->wantedType, *builtinTypes->numberType);
CHECK_EQ(*tm2->givenType, *requireType("foo"));
}
} }
// CLI-29033 // CLI-29033
@ -712,8 +753,16 @@ TEST_CASE_FIXTURE(Fixture, "concat_op_on_free_lhs_and_string_rhs")
end end
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); if (FFlag::DebugLuauDeferredConstraintResolution)
REQUIRE(get<CannotInferBinaryOperation>(result.errors[0])); {
LUAU_REQUIRE_NO_ERRORS(result);
CHECK("<a>(a) -> concat<a, string>" == toString(requireType("f")));
}
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
REQUIRE(get<CannotInferBinaryOperation>(result.errors[0]));
}
} }
TEST_CASE_FIXTURE(Fixture, "concat_op_on_string_lhs_and_free_rhs") TEST_CASE_FIXTURE(Fixture, "concat_op_on_string_lhs_and_free_rhs")
@ -726,7 +775,10 @@ TEST_CASE_FIXTURE(Fixture, "concat_op_on_string_lhs_and_free_rhs")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ("(string) -> string", toString(requireType("f"))); if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK("<a>(a) -> concat<string, a>" == toString(requireType("f")));
else
CHECK_EQ("(string) -> string", toString(requireType("f")));
} }
TEST_CASE_FIXTURE(Fixture, "strict_binary_op_where_lhs_unknown") TEST_CASE_FIXTURE(Fixture, "strict_binary_op_where_lhs_unknown")
@ -746,11 +798,10 @@ TEST_CASE_FIXTURE(Fixture, "strict_binary_op_where_lhs_unknown")
{ {
LUAU_REQUIRE_ERROR_COUNT(ops.size(), result); LUAU_REQUIRE_ERROR_COUNT(ops.size(), result);
CHECK_EQ( CHECK_EQ(
"Type function instance Add<a, b> depends on generic function parameters but does not appear in the function signature; this " "Operator '+' could not be applied to operands of types unknown and unknown; there is no corresponding overload for __add",
"construct cannot be type-checked at this time",
toString(result.errors[0]) toString(result.errors[0])
); );
CHECK_EQ("Unknown type used in - operation; consider adding a type annotation to 'a'", toString(result.errors[1])); CHECK_EQ("Operator '-' could not be applied to operands of types unknown and unknown; there is no corresponding overload for __sub", toString(result.errors[1]));
} }
else else
{ {
@ -833,6 +884,9 @@ TEST_CASE_FIXTURE(Fixture, "error_on_invalid_operand_types_to_relational_operato
TEST_CASE_FIXTURE(Fixture, "cli_38355_recursive_union") TEST_CASE_FIXTURE(Fixture, "cli_38355_recursive_union")
{ {
// There's an extra spurious warning here when the new solver is enabled.
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
--!strict --!strict
local _ local _
@ -847,14 +901,17 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "UnknownGlobalCompoundAssign")
{ {
// In non-strict mode, global definition is still allowed // In non-strict mode, global definition is still allowed
{ {
CheckResult result = check(R"( if (!FFlag::DebugLuauDeferredConstraintResolution)
--!nonstrict {
a = a + 1 CheckResult result = check(R"(
print(a) --!nonstrict
)"); a = a + 1
print(a)
)");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(toString(result.errors[0]), "Unknown global 'a'"); CHECK_EQ(toString(result.errors[0]), "Unknown global 'a'");
}
} }
// In strict mode we no longer generate two errors from lhs // In strict mode we no longer generate two errors from lhs
@ -871,14 +928,17 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "UnknownGlobalCompoundAssign")
// In non-strict mode, compound assignment is not a definition, it's a modification // In non-strict mode, compound assignment is not a definition, it's a modification
{ {
CheckResult result = check(R"( if (!FFlag::DebugLuauDeferredConstraintResolution)
--!nonstrict {
a += 1 CheckResult result = check(R"(
print(a) --!nonstrict
)"); a += 1
print(a)
)");
LUAU_REQUIRE_ERROR_COUNT(2, result); LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK_EQ(toString(result.errors[0]), "Unknown global 'a'"); CHECK_EQ(toString(result.errors[0]), "Unknown global 'a'");
}
} }
} }
@ -1233,7 +1293,8 @@ TEST_CASE_FIXTURE(Fixture, "unrelated_primitives_cannot_be_compared")
TEST_CASE_FIXTURE(BuiltinsFixture, "mm_comparisons_must_return_a_boolean") TEST_CASE_FIXTURE(BuiltinsFixture, "mm_comparisons_must_return_a_boolean")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) // CLI-115687
if (1 || !FFlag::DebugLuauDeferredConstraintResolution)
return; return;
CheckResult result = check(R"( CheckResult result = check(R"(
@ -1286,7 +1347,7 @@ local w = c and 1
CHECK("boolean | number" == toString(requireType("z"))); // 'false' widened to boolean CHECK("boolean | number" == toString(requireType("z"))); // 'false' widened to boolean
if (FFlag::DebugLuauDeferredConstraintResolution) if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK("((false?) & unknown) | number" == toString(requireType("w"))); CHECK("number?" == toString(requireType("w")));
else else
CHECK("(boolean | number)?" == toString(requireType("w"))); CHECK("(boolean | number)?" == toString(requireType("w")));
} }
@ -1364,6 +1425,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "luau_polyfill_is_array_simplified")
TEST_CASE_FIXTURE(BuiltinsFixture, "luau_polyfill_is_array") TEST_CASE_FIXTURE(BuiltinsFixture, "luau_polyfill_is_array")
{ {
// CLI-116480 Subtyping bug: table should probably be a subtype of {[unknown]: unknown}
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
--!strict --!strict
return function(value: any): boolean return function(value: any): boolean
@ -1536,4 +1600,17 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "no_infinite_expansion_of_free_type" * doctes
// just type-checking this code is enough // just type-checking this code is enough
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "compound_operator_on_upvalue")
{
CheckResult result = check(R"(
local byteCursor: number = 0
local function advance(bytes: number)
byteCursor += bytes
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_SUITE_END(); TEST_SUITE_END();

View File

@ -73,18 +73,27 @@ TEST_CASE_FIXTURE(Fixture, "string_function_indirect")
CHECK_EQ(*requireType("p"), *builtinTypes->stringType); CHECK_EQ(*requireType("p"), *builtinTypes->stringType);
} }
TEST_CASE_FIXTURE(Fixture, "CheckMethodsOfNumber") TEST_CASE_FIXTURE(Fixture, "check_methods_of_number")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
local x: number = 9999 local x: number = 9999
function x:y(z: number) function x:y(z: number)
local s: string = z local s: string = z
end end
)"); )");
LUAU_REQUIRE_ERROR_COUNT(2, result); LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK_EQ(toString(result.errors[0]), "Cannot add method to non-table type 'number'");
CHECK_EQ(toString(result.errors[1]), "Type 'number' could not be converted into 'string'"); if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK("Expected type table, got 'number' instead" == toString(result.errors[0]));
CHECK("Type 'number' could not be converted into 'string'" == toString(result.errors[1]));
}
else
{
CHECK_EQ(toString(result.errors[0]), "Cannot add method to non-table type 'number'");
CHECK_EQ(toString(result.errors[1]), "Type 'number' could not be converted into 'string'");
}
} }
TEST_CASE("singleton_types") TEST_CASE("singleton_types")

View File

@ -2252,4 +2252,67 @@ function(obj)
end end
)")); )"));
} }
TEST_CASE_FIXTURE(Fixture, "more_complex_long_disjunction_of_refinements_shouldnt_trip_ice")
{
CHECK_NOTHROW(check(R"(
script:connect(function(obj)
if script.Parent.SeatNumber.Value == "1D" or
script.Parent.SeatNumber.Value == "2D" or
script.Parent.SeatNumber.Value == "3D" or
script.Parent.SeatNumber.Value == "4D" or
script.Parent.SeatNumber.Value == "5D" or
script.Parent.SeatNumber.Value == "6D" or
script.Parent.SeatNumber.Value == "7D" or
script.Parent.SeatNumber.Value == "8D" or
script.Parent.SeatNumber.Value == "9D" or
script.Parent.SeatNumber.Value == "10D" or
script.Parent.SeatNumber.Value == "11D" or
script.Parent.SeatNumber.Value == "12D" or
script.Parent.SeatNumber.Value == "13D" or
script.Parent.SeatNumber.Value == "14D" or
script.Parent.SeatNumber.Value == "15D" or
script.Parent.SeatNumber.Value == "16D" or
script.Parent.SeatNumber.Value == "1C" or
script.Parent.SeatNumber.Value == "2C" or
script.Parent.SeatNumber.Value == "3C" or
script.Parent.SeatNumber.Value == "4C" or
script.Parent.SeatNumber.Value == "5C" or
script.Parent.SeatNumber.Value == "6C" or
script.Parent.SeatNumber.Value == "7C" or
script.Parent.SeatNumber.Value == "8C" or
script.Parent.SeatNumber.Value == "9C" or
script.Parent.SeatNumber.Value == "10C" or
script.Parent.SeatNumber.Value == "11C" or
script.Parent.SeatNumber.Value == "12C" or
script.Parent.SeatNumber.Value == "13C" or
script.Parent.SeatNumber.Value == "14C" or
script.Parent.SeatNumber.Value == "15C" or
script.Parent.SeatNumber.Value == "16C" then
end)
)"));
}
TEST_CASE_FIXTURE(Fixture, "refinements_should_avoid_building_up_big_intersect_families")
{
CHECK_NOTHROW(check(R"(
script:connect(function(obj)
if script.Parent.SeatNumber.Value == "1D" or script.Parent.SeatNumber.Value == "2D" or script.Parent.SeatNumber.Value == "3D" or script.Parent.SeatNumber.Value == "4D" or script.Parent.SeatNumber.Value == "5D" or script.Parent.SeatNumber.Value == "6D" or script.Parent.SeatNumber.Value == "7D" or script.Parent.SeatNumber.Value == "8D" or script.Parent.SeatNumber.Value == "9D" or script.Parent.SeatNumber.Value == "10D" or script.Parent.SeatNumber.Value == "11D" or script.Parent.SeatNumber.Value == "12D" or script.Parent.SeatNumber.Value == "13D" or script.Parent.SeatNumber.Value == "14D" or script.Parent.SeatNumber.Value == "15D" or script.Parent.SeatNumber.Value == "16D" or script.Parent.SeatNumber.Value == "1C" or script.Parent.SeatNumber.Value == "2C" or script.Parent.SeatNumber.Value == "3C" or script.Parent.SeatNumber.Value == "4C" or script.Parent.SeatNumber.Value == "5C" or script.Parent.SeatNumber.Value == "6C" or script.Parent.SeatNumber.Value == "7C" or script.Parent.SeatNumber.Value == "8C" or script.Parent.SeatNumber.Value == "9C" or script.Parent.SeatNumber.Value == "10C" or script.Parent.SeatNumber.Value == "11C" or script.Parent.SeatNumber.Value == "12C" or script.Parent.SeatNumber.Value == "13C" or script.Parent.SeatNumber.Value == "14C" or script.Parent.SeatNumber.Value == "15C" or script.Parent.SeatNumber.Value == "16C" then
if p.Name == script.Parent.Parent.Parent.Parent.Parent.Parent.MainParts.CD.SurfaceGui[script.Parent.SeatNumber.Value].Player.Value or script.Parent.Parent.Parent.Parent.Parent.Parent.MainParts.CD.SurfaceGui[script.Parent.SeatNumber.Value].Player.Value == "" then
else
if script.Parent:FindFirstChild("SeatWeld") then
end
end
else
if p.Name == script.Parent.Parent.Parent.Parent.Parent.Parent.MainParts.AB.SurfaceGui[script.Parent.SeatNumber.Value].Player.Value or script.Parent.Parent.Parent.Parent.Parent.Parent.MainParts.AB.SurfaceGui[script.Parent.SeatNumber.Value].Player.Value == "" then
print("Allowed")
else
if script.Parent:FindFirstChild("SeatWeld") then
end
end
end
end)
)"));
}
TEST_SUITE_END(); TEST_SUITE_END();

View File

@ -153,6 +153,8 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_function_call_with_singletons")
TEST_CASE_FIXTURE(Fixture, "overloaded_function_call_with_singletons_mismatch") TEST_CASE_FIXTURE(Fixture, "overloaded_function_call_with_singletons_mismatch")
{ {
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
function f(g: ((true, string) -> ()) & ((false, number) -> ())) function f(g: ((true, string) -> ()) & ((false, number) -> ()))
g(true, 37) g(true, 37)
@ -192,10 +194,14 @@ TEST_CASE_FIXTURE(Fixture, "enums_using_singletons_mismatch")
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(
"Type '\"bang\"' could not be converted into '\"bar\" | \"baz\" | \"foo\"'; none of the union options are compatible", if (FFlag::DebugLuauDeferredConstraintResolution)
toString(result.errors[0]) CHECK("Type '\"bang\"' could not be converted into '\"bar\" | \"baz\" | \"foo\"'" == toString(result.errors[0]));
); else
CHECK_EQ(
"Type '\"bang\"' could not be converted into '\"bar\" | \"baz\" | \"foo\"'; none of the union options are compatible",
toString(result.errors[0])
);
} }
TEST_CASE_FIXTURE(Fixture, "enums_using_singletons_subtyping") TEST_CASE_FIXTURE(Fixture, "enums_using_singletons_subtyping")
@ -337,10 +343,18 @@ TEST_CASE_FIXTURE(Fixture, "table_properties_type_error_escapes")
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ( if (FFlag::DebugLuauDeferredConstraintResolution)
R"(Table type '{ ["\n"]: number }' not compatible with type '{| ["<>"]: number |}' because the former is missing field '<>')", CHECK(
toString(result.errors[0]) "Type\n"
); " '{ [\"\\n\"]: number }'\n"
"could not be converted into\n"
" '{ [\"<>\"]: number }'" == toString(result.errors[0])
);
else
CHECK_EQ(
R"(Table type '{ ["\n"]: number }' not compatible with type '{| ["<>"]: number |}' because the former is missing field '<>')",
toString(result.errors[0])
);
} }
TEST_CASE_FIXTURE(Fixture, "error_detailed_tagged_union_mismatch_string") TEST_CASE_FIXTURE(Fixture, "error_detailed_tagged_union_mismatch_string")
@ -354,11 +368,16 @@ local a: Animal = { tag = 'cat', cafood = 'something' }
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type 'a' could not be converted into 'Cat | Dog' if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK("Type '{ cafood: string, tag: \"cat\" }' could not be converted into 'Cat | Dog'" == toString(result.errors[0]));
else
{
const std::string expected = R"(Type 'a' could not be converted into 'Cat | Dog'
caused by: caused by:
None of the union options are compatible. For example: None of the union options are compatible. For example:
Table type 'a' not compatible with type 'Cat' because the former is missing field 'catfood')"; Table type 'a' not compatible with type 'Cat' because the former is missing field 'catfood')";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
}
} }
TEST_CASE_FIXTURE(Fixture, "error_detailed_tagged_union_mismatch_bool") TEST_CASE_FIXTURE(Fixture, "error_detailed_tagged_union_mismatch_bool")
@ -372,11 +391,16 @@ local a: Result = { success = false, result = 'something' }
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type 'a' could not be converted into 'Bad | Good' if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK("Type '{ result: string, success: boolean }' could not be converted into 'Bad | Good'" == toString(result.errors[0]));
else
{
const std::string expected = R"(Type 'a' could not be converted into 'Bad | Good'
caused by: caused by:
None of the union options are compatible. For example: None of the union options are compatible. For example:
Table type 'a' not compatible with type 'Bad' because the former is missing field 'error')"; Table type 'a' not compatible with type 'Bad' because the former is missing field 'error')";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
}
} }
TEST_CASE_FIXTURE(Fixture, "parametric_tagged_union_alias") TEST_CASE_FIXTURE(Fixture, "parametric_tagged_union_alias")
@ -418,6 +442,8 @@ local a: Animal = if true then { tag = 'cat', catfood = 'something' } else { tag
TEST_CASE_FIXTURE(Fixture, "widen_the_supertype_if_it_is_free_and_subtype_has_singleton") TEST_CASE_FIXTURE(Fixture, "widen_the_supertype_if_it_is_free_and_subtype_has_singleton")
{ {
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
local function foo(f, x) local function foo(f, x)
if x == "hi" then if x == "hi" then
@ -427,7 +453,7 @@ TEST_CASE_FIXTURE(Fixture, "widen_the_supertype_if_it_is_free_and_subtype_has_si
end end
)"); )");
LUAU_REQUIRE_NO_ERRORS(result); LUAU_CHECK_NO_ERRORS(result);
CHECK_EQ(R"("hi")", toString(requireTypeAtPosition({3, 18}))); CHECK_EQ(R"("hi")", toString(requireTypeAtPosition({3, 18})));
// should be <a...>((string) -> a..., string) -> () but needs lower bounds calculation // should be <a...>((string) -> a..., string) -> () but needs lower bounds calculation
@ -436,6 +462,8 @@ TEST_CASE_FIXTURE(Fixture, "widen_the_supertype_if_it_is_free_and_subtype_has_si
TEST_CASE_FIXTURE(Fixture, "return_type_of_f_is_not_widened") TEST_CASE_FIXTURE(Fixture, "return_type_of_f_is_not_widened")
{ {
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
local function foo(f, x): "hello"? -- anyone there? local function foo(f, x): "hello"? -- anyone there?
return if x == "hi" return if x == "hi"

View File

@ -224,6 +224,8 @@ TEST_CASE_FIXTURE(Fixture, "statements_are_topologically_sorted")
TEST_CASE_FIXTURE(Fixture, "unify_nearly_identical_recursive_types") TEST_CASE_FIXTURE(Fixture, "unify_nearly_identical_recursive_types")
{ {
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
local o local o
o:method() o:method()
@ -263,6 +265,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "weird_case")
TEST_CASE_FIXTURE(Fixture, "dont_ice_when_failing_the_occurs_check") TEST_CASE_FIXTURE(Fixture, "dont_ice_when_failing_the_occurs_check")
{ {
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
--!strict --!strict
local s local s
@ -380,6 +384,8 @@ TEST_CASE_FIXTURE(Fixture, "exponential_blowup_from_copying_types")
// checker. We also want it to somewhat match up with production values, so we push up the parser recursion limit a little bit instead. // checker. We also want it to somewhat match up with production values, so we push up the parser recursion limit a little bit instead.
TEST_CASE_FIXTURE(Fixture, "check_type_infer_recursion_count") TEST_CASE_FIXTURE(Fixture, "check_type_infer_recursion_count")
{ {
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
#if defined(LUAU_ENABLE_ASAN) #if defined(LUAU_ENABLE_ASAN)
int limit = 250; int limit = 250;
#elif defined(_DEBUG) || defined(_NOOPT) #elif defined(_DEBUG) || defined(_NOOPT)
@ -435,6 +441,9 @@ TEST_CASE_FIXTURE(Fixture, "check_expr_recursion_limit")
TEST_CASE_FIXTURE(Fixture, "globals") TEST_CASE_FIXTURE(Fixture, "globals")
{ {
// The new solver does not permit assignments to globals like this.
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
--!nonstrict --!nonstrict
foo = true foo = true
@ -447,6 +456,8 @@ TEST_CASE_FIXTURE(Fixture, "globals")
TEST_CASE_FIXTURE(Fixture, "globals2") TEST_CASE_FIXTURE(Fixture, "globals2")
{ {
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
--!nonstrict --!nonstrict
foo = function() return 1 end foo = function() return 1 end
@ -495,6 +506,8 @@ TEST_CASE_FIXTURE(Fixture, "correctly_scope_locals_do")
TEST_CASE_FIXTURE(Fixture, "checking_should_not_ice") TEST_CASE_FIXTURE(Fixture, "checking_should_not_ice")
{ {
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CHECK_NOTHROW(check(R"( CHECK_NOTHROW(check(R"(
--!nonstrict --!nonstrict
f,g = ... f,g = ...
@ -587,6 +600,8 @@ TEST_CASE_FIXTURE(Fixture, "tc_after_error_recovery_no_assert")
TEST_CASE_FIXTURE(BuiltinsFixture, "tc_after_error_recovery_no_replacement_name_in_error") TEST_CASE_FIXTURE(BuiltinsFixture, "tc_after_error_recovery_no_replacement_name_in_error")
{ {
{ {
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
--!strict --!strict
local t = { x = 10, y = 20 } local t = { x = 10, y = 20 }
@ -607,6 +622,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tc_after_error_recovery_no_replacement_name_
} }
{ {
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
--!strict --!strict
function string.() end function string.() end
@ -680,6 +697,8 @@ TEST_CASE_FIXTURE(Fixture, "cli_39932_use_unifier_in_ensure_methods")
TEST_CASE_FIXTURE(Fixture, "dont_report_type_errors_within_an_AstStatError") TEST_CASE_FIXTURE(Fixture, "dont_report_type_errors_within_an_AstStatError")
{ {
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
foo foo
)"); )");
@ -689,6 +708,8 @@ TEST_CASE_FIXTURE(Fixture, "dont_report_type_errors_within_an_AstStatError")
TEST_CASE_FIXTURE(Fixture, "dont_report_type_errors_within_an_AstExprError") TEST_CASE_FIXTURE(Fixture, "dont_report_type_errors_within_an_AstExprError")
{ {
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
local a = foo: local a = foo:
)"); )");
@ -738,7 +759,10 @@ TEST_CASE_FIXTURE(Fixture, "no_stack_overflow_from_isoptional")
std::optional<TypeId> t0 = lookupType("t0"); std::optional<TypeId> t0 = lookupType("t0");
REQUIRE(t0); REQUIRE(t0);
CHECK_EQ("*error-type*", toString(*t0)); if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK("any" == toString(*t0));
else
CHECK_EQ("*error-type*", toString(*t0));
auto it = std::find_if( auto it = std::find_if(
result.errors.begin(), result.errors.begin(),
@ -1075,6 +1099,8 @@ end
TEST_CASE_FIXTURE(Fixture, "cli_50041_committing_txnlog_in_apollo_client_error") TEST_CASE_FIXTURE(Fixture, "cli_50041_committing_txnlog_in_apollo_client_error")
{ {
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
--!strict --!strict
--!nolint --!nolint
@ -1150,7 +1176,11 @@ TEST_CASE_FIXTURE(Fixture, "type_infer_recursion_limit_no_ice")
)"); )");
LUAU_REQUIRE_ERRORS(result); LUAU_REQUIRE_ERRORS(result);
CHECK_EQ("Code is too complex to typecheck! Consider simplifying the code around this area", toString(result.errors[0]));
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK("Type contains a self-recursive construct that cannot be resolved" == toString(result.errors[0]));
else
CHECK_EQ("Code is too complex to typecheck! Consider simplifying the code around this area", toString(result.errors[0]));
} }
TEST_CASE_FIXTURE(Fixture, "type_infer_recursion_limit_normalizer") TEST_CASE_FIXTURE(Fixture, "type_infer_recursion_limit_normalizer")
@ -1168,7 +1198,11 @@ TEST_CASE_FIXTURE(Fixture, "type_infer_recursion_limit_normalizer")
REQUIRE_MESSAGE(!result.errors.empty(), getErrors(result)); REQUIRE_MESSAGE(!result.errors.empty(), getErrors(result));
CHECK(1 == result.errors.size()); CHECK(1 == result.errors.size());
CHECK(Location{{3, 12}, {3, 46}} == result.errors[0].location);
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK(Location{{3, 22}, {3, 42}} == result.errors[0].location);
else
CHECK(Location{{3, 12}, {3, 46}} == result.errors[0].location);
CHECK_EQ("Code is too complex to typecheck! Consider simplifying the code around this area", toString(result.errors[0])); CHECK_EQ("Code is too complex to typecheck! Consider simplifying the code around this area", toString(result.errors[0]));
} }
@ -1187,6 +1221,9 @@ TEST_CASE_FIXTURE(Fixture, "type_infer_cache_limit_normalizer")
TEST_CASE_FIXTURE(Fixture, "follow_on_new_types_in_substitution") TEST_CASE_FIXTURE(Fixture, "follow_on_new_types_in_substitution")
{ {
// CLI-114134
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
local obj = {} local obj = {}
@ -1355,7 +1392,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "recursive_function_that_invokes_itself_with_
end end
)"); )");
CHECK("<a>(a) -> ()" == toString(requireType("readValue"))); if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK("(unknown) -> ()" == toString(requireType("readValue")));
else
CHECK("<a>(a) -> ()" == toString(requireType("readValue")));
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "recursive_function_that_invokes_itself_with_a_refinement_of_its_parameter_2") TEST_CASE_FIXTURE(BuiltinsFixture, "recursive_function_that_invokes_itself_with_a_refinement_of_its_parameter_2")
@ -1368,7 +1408,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "recursive_function_that_invokes_itself_with_
end end
)"); )");
CHECK("(number) -> ()" == toString(requireType("readValue"))); if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK("(unknown) -> ()" == toString(requireType("readValue")));
else
CHECK("(number) -> ()" == toString(requireType("readValue")));
} }
/* /*
@ -1520,6 +1563,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "lti_must_record_contributing_locations")
*/ */
TEST_CASE_FIXTURE(BuiltinsFixture, "be_sure_to_use_active_txnlog_when_evaluating_a_variadic_overload") TEST_CASE_FIXTURE(BuiltinsFixture, "be_sure_to_use_active_txnlog_when_evaluating_a_variadic_overload")
{ {
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
local function concat<T>(target: {T}, ...: {T} | T): {T} local function concat<T>(target: {T}, ...: {T} | T): {T}
return (nil :: any) :: {T} return (nil :: any) :: {T}

View File

@ -154,6 +154,8 @@ TEST_CASE_FIXTURE(Fixture, "uninhabited_intersection_sub_anything")
TEST_CASE_FIXTURE(Fixture, "uninhabited_table_sub_never") TEST_CASE_FIXTURE(Fixture, "uninhabited_table_sub_never")
{ {
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
function f(arg : { prop : string & number }) : never function f(arg : { prop : string & number }) : never
return arg return arg
@ -164,6 +166,8 @@ TEST_CASE_FIXTURE(Fixture, "uninhabited_table_sub_never")
TEST_CASE_FIXTURE(Fixture, "uninhabited_table_sub_anything") TEST_CASE_FIXTURE(Fixture, "uninhabited_table_sub_anything")
{ {
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
function f(arg : { prop : string & number }) : boolean function f(arg : { prop : string & number }) : boolean
return arg return arg
@ -174,6 +178,8 @@ TEST_CASE_FIXTURE(Fixture, "uninhabited_table_sub_anything")
TEST_CASE_FIXTURE(Fixture, "members_of_failed_typepack_unification_are_unified_with_errorType") TEST_CASE_FIXTURE(Fixture, "members_of_failed_typepack_unification_are_unified_with_errorType")
{ {
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
function f(arg: number) end function f(arg: number) end
local a local a
@ -189,6 +195,8 @@ TEST_CASE_FIXTURE(Fixture, "members_of_failed_typepack_unification_are_unified_w
TEST_CASE_FIXTURE(Fixture, "result_of_failed_typepack_unification_is_constrained") TEST_CASE_FIXTURE(Fixture, "result_of_failed_typepack_unification_is_constrained")
{ {
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
function f(arg: number) return arg end function f(arg: number) return arg end
local a local a

View File

@ -575,12 +575,12 @@ local b: Y<(), ()>
TEST_CASE_FIXTURE(Fixture, "type_alias_backwards_compatible") TEST_CASE_FIXTURE(Fixture, "type_alias_backwards_compatible")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
type X<T> = () -> T type X<T> = () -> T
type Y<T, U> = (T) -> U type Y<T, U> = (T) -> U
type A = X<(number)> type A = X<(number)>
type B = Y<(number), (boolean)> type B = Y<(number), (boolean)>
type C = Y<(number), boolean> type C = Y<(number), boolean>
)"); )");
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
@ -787,48 +787,69 @@ local d: Y<number, string, ...boolean, ...() -> ()>
TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_errors") TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_errors")
{ {
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
type Y<T = T> = { a: T } type Y<T = T> = { a: T }
local a: Y = { a = 2 } local a: Y = { a = 2 }
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(toString(result.errors[0]), "Unknown type 'T'"); CHECK_EQ(toString(result.errors[0]), "Unknown type 'T'");
}
result = check(R"( TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_errors2")
type Y<T... = T...> = { a: (T...) -> () } {
local a: Y<> CheckResult result = check(R"(
type Y<T... = T...> = { a: (T...) -> () }
local a: Y<>
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(toString(result.errors[0]), "Unknown type 'T'"); CHECK_EQ(toString(result.errors[0]), "Unknown type 'T'");
}
result = check(R"( TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_errors3")
type Y<T = string, U... = ...string> = { a: (T) -> U... } {
local a: Y<...number> ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
type Y<T = string, U... = ...string> = { a: (T) -> U... }
local a: Y<...number>
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(toString(result.errors[0]), "Generic type 'Y<T, U...>' expects at least 1 type argument, but none are specified"); CHECK_EQ(toString(result.errors[0]), "Generic type 'Y<T, U...>' expects at least 1 type argument, but none are specified");
}
result = check(R"( TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_errors4")
type Packed<T> = (T) -> T {
local a: Packed ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
type Packed<T> = (T) -> T
local a: Packed
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(toString(result.errors[0]), "Type parameter list is required"); CHECK_EQ(toString(result.errors[0]), "Type parameter list is required");
}
result = check(R"( TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_errors5")
type Y<T, U = T, V> = { a: T } {
local a: Y<number> CheckResult result = check(R"(
type Y<T, U = T, V> = { a: T }
local a: Y<number>
)"); )");
LUAU_REQUIRE_ERRORS(result); LUAU_REQUIRE_ERRORS(result);
}
result = check(R"( TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_errors6")
type Y<T..., U... = T..., V...> = { a: T } {
local a: Y<...number> CheckResult result = check(R"(
type Y<T..., U... = T..., V...> = { a: T }
local a: Y<...number>
)"); )");
LUAU_REQUIRE_ERRORS(result); LUAU_REQUIRE_ERRORS(result);
@ -929,13 +950,27 @@ a = b
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type
if (FFlag::DebugLuauDeferredConstraintResolution)
{
const std::string expected =
"Type\n"
" '() -> (number, ...boolean)'\n"
"could not be converted into\n"
" '() -> (number, ...string)'; at returns().tail().variadic(), boolean is not a subtype of string";
CHECK(expected == toString(result.errors[0]));
}
else
{
const std::string expected = R"(Type
'() -> (number, ...boolean)' '() -> (number, ...boolean)'
could not be converted into could not be converted into
'() -> (number, ...string)' '() -> (number, ...string)'
caused by: caused by:
Type 'boolean' could not be converted into 'string')"; Type 'boolean' could not be converted into 'string')";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
}
} }
// TODO: File a Jira about this // TODO: File a Jira about this
@ -1030,6 +1065,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "detect_cyclic_typepacks2")
TEST_CASE_FIXTURE(Fixture, "unify_variadic_tails_in_arguments") TEST_CASE_FIXTURE(Fixture, "unify_variadic_tails_in_arguments")
{ {
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"( CheckResult result = check(R"(
function foo(...: string): number function foo(...: string): number
return 1 return 1

View File

@ -121,11 +121,11 @@ TEST_CASE_FIXTURE(Fixture, "type_packs_containing_never_is_itself_uninhabitable"
if (FFlag::DebugLuauDeferredConstraintResolution) if (FFlag::DebugLuauDeferredConstraintResolution)
{ {
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ("Function only returns 2 values, but 3 are required here", toString(result.errors[0])); CHECK("Function only returns 2 values, but 3 are required here" == toString(result.errors[0]));
CHECK_EQ("string", toString(requireType("x"))); CHECK("string" == toString(requireType("x")));
CHECK_EQ("never", toString(requireType("y"))); CHECK("never" == toString(requireType("y")));
CHECK_EQ("*error-type*", toString(requireType("z"))); CHECK("nil" == toString(requireType("z")));
} }
else else
{ {
@ -335,8 +335,20 @@ TEST_CASE_FIXTURE(Fixture, "math_operators_and_never")
end end
)"); )");
LUAU_REQUIRE_NO_ERRORS(result); if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ("<a>(nil, a) -> boolean", toString(requireType("mul"))); {
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK(get<ExplicitFunctionAnnotationRecommended>(result.errors[0]));
// CLI-114134 Egraph-based simplification.
// CLI-116549 x ~= nil : false when x : nil
CHECK("<a>(nil, a) -> and<boolean, mul<nil & ~nil, a>>" == toString(requireType("mul")));
}
else
{
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ("<a>(nil, a) -> boolean", toString(requireType("mul")));
}
} }
TEST_CASE_FIXTURE(Fixture, "compare_never") TEST_CASE_FIXTURE(Fixture, "compare_never")

View File

@ -1,26 +1,9 @@
AstQuery.last_argument_function_call_type
AutocompleteTest.anonymous_autofilled_generic_on_argument_type_pack_vararg AutocompleteTest.anonymous_autofilled_generic_on_argument_type_pack_vararg
AutocompleteTest.anonymous_autofilled_generic_type_pack_vararg AutocompleteTest.anonymous_autofilled_generic_type_pack_vararg
AutocompleteTest.autocomplete_string_singletons AutocompleteTest.autocomplete_string_singletons
AutocompleteTest.string_singleton_as_table_key
AutocompleteTest.suggest_table_keys AutocompleteTest.suggest_table_keys
AutocompleteTest.type_correct_suggestion_for_overloads AutocompleteTest.type_correct_suggestion_for_overloads
AutocompleteTest.type_correct_suggestion_in_table AutocompleteTest.type_correct_suggestion_in_table
BuiltinTests.aliased_string_format
BuiltinTests.assert_removes_falsy_types_even_from_type_pack_tail_but_only_for_the_first_type
BuiltinTests.assert_returns_false_and_string_iff_it_knows_the_first_argument_cannot_be_truthy
BuiltinTests.coroutine_resume_anything_goes
BuiltinTests.select_slightly_out_of_range
BuiltinTests.select_way_out_of_range
BuiltinTests.select_with_variadic_typepack_tail_and_string_head
BuiltinTests.set_metatable_needs_arguments
BuiltinTests.setmetatable_should_not_mutate_persisted_types
BuiltinTests.sort_with_bad_predicate
BuiltinTests.string_format_as_method
BuiltinTests.string_format_correctly_ordered_types
BuiltinTests.string_format_report_all_type_errors_at_correct_positions
BuiltinTests.string_format_use_correct_argument2
BuiltinTests.table_freeze_is_generic
GenericsTests.do_not_always_instantiate_generic_intersection_types GenericsTests.do_not_always_instantiate_generic_intersection_types
GenericsTests.error_detailed_function_mismatch_generic_pack GenericsTests.error_detailed_function_mismatch_generic_pack
GenericsTests.error_detailed_function_mismatch_generic_types GenericsTests.error_detailed_function_mismatch_generic_types
@ -47,174 +30,16 @@ IntersectionTypes.overloaded_functions_mentioning_generic_packs
IntersectionTypes.overloaded_functions_mentioning_generics IntersectionTypes.overloaded_functions_mentioning_generics
IntersectionTypes.overloaded_functions_returning_intersections IntersectionTypes.overloaded_functions_returning_intersections
IntersectionTypes.overloadeded_functions_with_never_arguments IntersectionTypes.overloadeded_functions_with_never_arguments
IntersectionTypes.overloadeded_functions_with_never_result
IntersectionTypes.overloadeded_functions_with_overlapping_results_and_variadics
IntersectionTypes.overloadeded_functions_with_unknown_arguments
IntersectionTypes.overloadeded_functions_with_unknown_result
IntersectionTypes.overloadeded_functions_with_weird_typepacks_1
IntersectionTypes.overloadeded_functions_with_weird_typepacks_2
IntersectionTypes.overloadeded_functions_with_weird_typepacks_3
IntersectionTypes.overloadeded_functions_with_weird_typepacks_4
IntersectionTypes.table_write_sealed_indirect
IntersectionTypes.union_saturate_overloaded_functions
Linter.TableOperationsIndexer
ModuleTests.clone_self_property ModuleTests.clone_self_property
Negations.cofinite_strings_can_be_compared_for_equality Negations.cofinite_strings_can_be_compared_for_equality
Normalize.higher_order_function_with_annotation Normalize.higher_order_function_with_annotation
Normalize.negations_of_tables
Normalize.specific_functions_cannot_be_negated
RefinementTest.refine_a_param_that_got_resolved_during_constraint_solving_stage RefinementTest.refine_a_param_that_got_resolved_during_constraint_solving_stage
RefinementTest.x_as_any_if_x_is_instance_elseif_x_is_table RefinementTest.x_as_any_if_x_is_instance_elseif_x_is_table
RefinementTest.x_is_not_instance_or_else_not_part RefinementTest.x_is_not_instance_or_else_not_part
TryUnifyTests.members_of_failed_typepack_unification_are_unified_with_errorType
TryUnifyTests.result_of_failed_typepack_unification_is_constrained
TryUnifyTests.uninhabited_table_sub_anything
TryUnifyTests.uninhabited_table_sub_never
TypeAliases.dont_lose_track_of_PendingExpansionTypes_after_substitution
TypeAliases.generic_param_remap
TypeAliases.mismatched_generic_type_param
TypeAliases.mutually_recursive_generic_aliases
TypeAliases.mutually_recursive_types_restriction_not_ok_1
TypeAliases.mutually_recursive_types_restriction_not_ok_2
TypeAliases.mutually_recursive_types_swapsies_not_ok
TypeAliases.recursive_types_restriction_not_ok
TypeAliases.report_shadowed_aliases
TypeAliases.type_alias_local_mutation
TypeAliases.type_alias_local_rename
TypeAliases.type_alias_of_an_imported_recursive_generic_type
TypeInfer.be_sure_to_use_active_txnlog_when_evaluating_a_variadic_overload
TypeInfer.check_type_infer_recursion_count
TypeInfer.checking_should_not_ice
TypeInfer.cli_50041_committing_txnlog_in_apollo_client_error
TypeInfer.dont_ice_when_failing_the_occurs_check
TypeInfer.dont_report_type_errors_within_an_AstExprError
TypeInfer.dont_report_type_errors_within_an_AstStatError
TypeInfer.follow_on_new_types_in_substitution
TypeInfer.globals
TypeInfer.globals2
TypeInfer.infer_through_group_expr
TypeInfer.no_stack_overflow_from_isoptional
TypeInfer.recursive_function_that_invokes_itself_with_a_refinement_of_its_parameter
TypeInfer.recursive_function_that_invokes_itself_with_a_refinement_of_its_parameter_2
TypeInfer.tc_after_error_recovery_no_replacement_name_in_error
TypeInfer.type_infer_recursion_limit_no_ice
TypeInfer.type_infer_recursion_limit_normalizer
TypeInfer.unify_nearly_identical_recursive_types
TypeInferClasses.cannot_unify_class_instance_with_primitive
TypeInferClasses.class_type_mismatch_with_name_conflict
TypeInferClasses.detailed_class_unification_error
TypeInferClasses.indexable_classes
TypeInferClasses.table_class_unification_reports_sane_errors_for_missing_properties
TypeInferClasses.we_can_report_when_someone_is_trying_to_use_a_table_rather_than_a_class
TypeInferFunctions.another_other_higher_order_function
TypeInferFunctions.bidirectional_checking_of_callback_property
TypeInferFunctions.calling_function_with_anytypepack_doesnt_leak_free_types
TypeInferFunctions.complicated_return_types_require_an_explicit_annotation
TypeInferFunctions.concrete_functions_are_not_supertypes_of_function
TypeInferFunctions.dont_assert_when_the_tarjan_limit_is_exceeded_during_generalization
TypeInferFunctions.dont_give_other_overloads_message_if_only_one_argument_matching_overload_exists
TypeInferFunctions.dont_infer_parameter_types_for_functions_from_their_call_site
TypeInferFunctions.duplicate_functions_with_different_signatures_not_allowed_in_nonstrict
TypeInferFunctions.error_detailed_function_mismatch_arg
TypeInferFunctions.error_detailed_function_mismatch_arg_count
TypeInferFunctions.error_detailed_function_mismatch_ret
TypeInferFunctions.error_detailed_function_mismatch_ret_count
TypeInferFunctions.error_detailed_function_mismatch_ret_mult
TypeInferFunctions.function_cast_error_uses_correct_language
TypeInferFunctions.function_decl_non_self_sealed_overwrite_2
TypeInferFunctions.function_decl_non_self_unsealed_overwrite
TypeInferFunctions.function_does_not_return_enough_values
TypeInferFunctions.function_exprs_are_generalized_at_signature_scope_not_enclosing
TypeInferFunctions.function_is_supertype_of_concrete_functions
TypeInferFunctions.function_statement_sealed_table_assignment_through_indexer
TypeInferFunctions.generic_packs_are_not_variadic
TypeInferFunctions.higher_order_function_4
TypeInferFunctions.improved_function_arg_mismatch_error_nonstrict
TypeInferFunctions.improved_function_arg_mismatch_errors
TypeInferFunctions.infer_anonymous_function_arguments
TypeInferFunctions.infer_anonymous_function_arguments_outside_call
TypeInferFunctions.infer_generic_function_function_argument
TypeInferFunctions.infer_generic_function_function_argument_overloaded
TypeInferFunctions.infer_return_value_type
TypeInferFunctions.inferred_higher_order_functions_are_quantified_at_the_right_time3
TypeInferFunctions.instantiated_type_packs_must_have_a_non_null_scope
TypeInferFunctions.list_only_alternative_overloads_that_match_argument_count
TypeInferFunctions.luau_subtyping_is_np_hard
TypeInferFunctions.no_lossy_function_type
TypeInferFunctions.occurs_check_failure_in_function_return_type
TypeInferFunctions.other_things_are_not_related_to_function
TypeInferFunctions.param_1_and_2_both_takes_the_same_generic_but_their_arguments_are_incompatible
TypeInferFunctions.param_1_and_2_both_takes_the_same_generic_but_their_arguments_are_incompatible_2
TypeInferFunctions.report_exiting_without_return_nonstrict
TypeInferFunctions.return_type_by_overload
TypeInferFunctions.simple_unannotated_mutual_recursion TypeInferFunctions.simple_unannotated_mutual_recursion
TypeInferFunctions.too_few_arguments_variadic
TypeInferFunctions.too_few_arguments_variadic_generic
TypeInferFunctions.too_few_arguments_variadic_generic2
TypeInferFunctions.too_many_arguments
TypeInferFunctions.too_many_return_values_in_parentheses
TypeInferFunctions.too_many_return_values_no_function
TypeInferFunctions.unifier_should_not_bind_free_types
TypeInferLoops.cli_68448_iterators_need_not_accept_nil
TypeInferLoops.dcr_iteration_on_never_gives_never
TypeInferLoops.dcr_xpath_candidates
TypeInferLoops.for_in_loop
TypeInferLoops.for_in_loop_error_on_factory_not_returning_the_right_amount_of_values
TypeInferLoops.for_in_loop_error_on_iterator_requiring_args_but_none_given
TypeInferLoops.for_in_loop_on_error
TypeInferLoops.for_in_loop_on_non_function
TypeInferLoops.for_in_loop_with_next
TypeInferLoops.for_loop
TypeInferLoops.ipairs_produces_integral_indices
TypeInferLoops.iterate_over_properties
TypeInferLoops.iteration_regression_issue_69967_alt
TypeInferLoops.loop_iter_metamethod_nil
TypeInferLoops.loop_iter_metamethod_not_enough_returns
TypeInferLoops.loop_iter_metamethod_ok
TypeInferLoops.loop_iter_metamethod_ok_with_inference
TypeInferLoops.loop_iter_no_indexer_strict
TypeInferLoops.loop_iter_trailing_nil
TypeInferLoops.loop_typecheck_crash_on_empty_optional
TypeInferLoops.properly_infer_iteratee_is_a_free_table
TypeInferLoops.repeat_loop
TypeInferLoops.while_loop
TypeInferModules.require
TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_it_wont_help_2
TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_not_defined_with_colon
TypeInferOOP.inferring_hundreds_of_self_calls_should_not_suffocate_memory
TypeInferOOP.promise_type_error_too_complex
TypeInferOperators.cli_38355_recursive_union
TypeInferOperators.compound_assign_result_must_be_compatible_with_var
TypeInferOperators.concat_op_on_free_lhs_and_string_rhs
TypeInferOperators.concat_op_on_string_lhs_and_free_rhs
TypeInferOperators.disallow_string_and_types_without_metatables_from_arithmetic_binary_ops
TypeInferOperators.luau_polyfill_is_array
TypeInferOperators.mm_comparisons_must_return_a_boolean
TypeInferOperators.reworked_and
TypeInferOperators.strict_binary_op_where_lhs_unknown
TypeInferOperators.typecheck_overloaded_multiply_that_is_an_intersection
TypeInferOperators.typecheck_overloaded_multiply_that_is_an_intersection_on_rhs
TypeInferOperators.typecheck_unary_len_error
TypeInferOperators.typecheck_unary_minus_error
TypeInferOperators.UnknownGlobalCompoundAssign
TypeInferPrimitives.CheckMethodsOfNumber
TypeInferUnknownNever.assign_to_local_which_is_never TypeInferUnknownNever.assign_to_local_which_is_never
TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_never TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_never
TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_sorta_never TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_sorta_never
TypeInferUnknownNever.length_of_never
TypeInferUnknownNever.math_operators_and_never
TypeInferUnknownNever.type_packs_containing_never_is_itself_uninhabitable
TypePackTests.fuzz_typepack_iter_follow_2 TypePackTests.fuzz_typepack_iter_follow_2
TypePackTests.pack_tail_unification_check
TypePackTests.type_alias_backwards_compatible
TypePackTests.type_alias_default_type_errors
TypePackTests.unify_variadic_tails_in_arguments
TypeSingletons.enums_using_singletons_mismatch
TypeSingletons.error_detailed_tagged_union_mismatch_bool
TypeSingletons.error_detailed_tagged_union_mismatch_string
TypeSingletons.overloaded_function_call_with_singletons_mismatch
TypeSingletons.return_type_of_f_is_not_widened
TypeSingletons.table_properties_type_error_escapes
TypeSingletons.widen_the_supertype_if_it_is_free_and_subtype_has_singleton
TypeStatesTest.typestates_preserve_error_suppression_properties TypeStatesTest.typestates_preserve_error_suppression_properties
VisitType.throw_when_limit_is_exceeded VisitType.throw_when_limit_is_exceeded