Sync to upstream/release/639 (#1368)

# What's Changed?

- Variety of bugfixes in the new solver

## New Solver

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

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

---
Co-authored-by: Aaron Weiss <aaronweiss@roblox.com>
Co-authored-by: Andy Friesen <afriesen@roblox.com>
Co-authored-by: Vighnesh Vijay <vvijay@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>

---------

Co-authored-by: Aaron Weiss <aaronweiss@roblox.com>
Co-authored-by: Alexander McCord <amccord@roblox.com>
Co-authored-by: Andy Friesen <afriesen@roblox.com>
Co-authored-by: Aviral Goel <agoel@roblox.com>
Co-authored-by: David Cope <dcope@roblox.com>
Co-authored-by: Lily Brown <lbrown@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
Co-authored-by: Junseo Yoo <jyoo@roblox.com>
This commit is contained in:
Vighnesh-V 2024-08-16 11:29:33 -07:00 committed by GitHub
parent 1788e237fc
commit 25f91aa8b8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
38 changed files with 1474 additions and 910 deletions

View File

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

View File

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

View File

@ -29,7 +29,6 @@
LUAU_FASTINT(LuauCheckRecursionLimit);
LUAU_FASTFLAG(DebugLuauLogSolverToJson);
LUAU_FASTFLAG(DebugLuauMagicTypes);
LUAU_FASTFLAG(LuauDeclarationExtraPropData);
namespace Luau
{
@ -550,6 +549,13 @@ bool mustDeferIntersection(TypeId ty)
}
} // namespace
enum RefinementsOpKind
{
Intersect,
Refine,
None
};
void ConstraintGenerator::applyRefinements(const ScopePtr& scope, Location location, RefinementId refinement)
{
if (!refinement)
@ -558,6 +564,23 @@ void ConstraintGenerator::applyRefinements(const ScopePtr& scope, Location locat
RefinementContext refinements;
std::vector<ConstraintV> constraints;
computeRefinement(scope, location, refinement, &refinements, /*sense*/ true, /*eq*/ false, &constraints);
auto flushConstraints = [this, &scope, &location](RefinementsOpKind kind, TypeId ty, std::vector<TypeId>& discriminants)
{
if (discriminants.empty())
return ty;
if (kind == RefinementsOpKind::None)
{
LUAU_ASSERT(false);
return ty;
}
std::vector<TypeId> args = {ty};
const TypeFunction& func = kind == RefinementsOpKind::Intersect ? builtinTypeFunctions().intersectFunc : builtinTypeFunctions().refineFunc;
LUAU_ASSERT(!func.name.empty());
args.insert(args.end(), discriminants.begin(), discriminants.end());
TypeId resultType = createTypeFunctionInstance(func, args, {}, scope, location);
discriminants.clear();
return resultType;
};
for (auto& [def, partition] : refinements)
{
@ -566,41 +589,52 @@ void ConstraintGenerator::applyRefinements(const ScopePtr& scope, Location locat
TypeId ty = *defTy;
if (partition.shouldAppendNilType)
ty = arena->addType(UnionType{{ty, builtinTypes->nilType}});
// Intersect ty with every discriminant type. If either type is not
// sufficiently solved, we queue the intersection up via an
// IntersectConstraint.
// For each discriminant ty, we accumulated it onto ty, creating a longer and longer
// sequence of refine constraints. On every loop of this we called mustDeferIntersection.
// For sufficiently large types, we would blow the stack.
// Instead, we record all the discriminant types in sequence
// and then dispatch a single refine constraint with multiple arguments. This helps us avoid
// the potentially expensive check on mustDeferIntersection
std::vector<TypeId> discriminants;
RefinementsOpKind kind = RefinementsOpKind::None;
bool mustDefer = mustDeferIntersection(ty);
for (TypeId dt : partition.discriminantTypes)
{
if (mustDeferIntersection(ty) || mustDeferIntersection(dt))
mustDefer = mustDefer || mustDeferIntersection(dt);
if (mustDefer)
{
TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().refineFunc, {ty, dt}, {}, scope, location);
if (kind == RefinementsOpKind::Intersect)
ty = flushConstraints(kind, ty, discriminants);
kind = RefinementsOpKind::Refine;
ty = resultType;
discriminants.push_back(dt);
}
else
{
switch (shouldSuppressErrors(normalizer, ty))
{
case ErrorSuppression::DoNotSuppress:
{
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:
ErrorSuppression status = shouldSuppressErrors(normalizer, ty);
if (status == ErrorSuppression::NormalizationFailed)
reportError(location, NormalizationTooComplex{});
ty = makeIntersect(scope, location, ty, dt);
break;
if (kind == RefinementsOpKind::Refine)
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;
}
}
@ -1532,17 +1566,14 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareClas
ftv->hasSelf = true;
if (FFlag::LuauDeclarationExtraPropData)
{
FunctionDefinition defn;
FunctionDefinition defn;
defn.definitionModuleName = module->name;
defn.definitionLocation = prop.location;
// No data is preserved for varargLocation
defn.originalNameLocation = prop.nameLocation;
defn.definitionModuleName = module->name;
defn.definitionLocation = prop.location;
// No data is preserved for varargLocation
defn.originalNameLocation = prop.nameLocation;
ftv->definition = defn;
}
ftv->definition = defn;
}
}
@ -1550,12 +1581,9 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareClas
if (props.count(propName) == 0)
{
if (FFlag::LuauDeclarationExtraPropData)
props[propName] = {propTy, /*deprecated*/ false, /*deprecatedSuggestion*/ "", prop.location};
else
props[propName] = {propTy};
props[propName] = {propTy, /*deprecated*/ false, /*deprecatedSuggestion*/ "", prop.location};
}
else if (FFlag::LuauDeclarationExtraPropData)
else
{
Luau::Property& prop = props[propName];
TypeId currentTy = prop.type();
@ -1583,31 +1611,6 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareClas
reportError(declaredClass->location, GenericError{format("Cannot overload non-function class member '%s'", propName.c_str())});
}
}
else
{
TypeId currentTy = props[propName].type();
// We special-case this logic to keep the intersection flat; otherwise we
// would create a ton of nested intersection types.
if (const IntersectionType* itv = get<IntersectionType>(currentTy))
{
std::vector<TypeId> options = itv->parts;
options.push_back(propTy);
TypeId newItv = arena->addType(IntersectionType{std::move(options)});
props[propName] = {newItv};
}
else if (get<FunctionType>(currentTy))
{
TypeId intersection = arena->addType(IntersectionType{{currentTy, propTy}});
props[propName] = {intersection};
}
else
{
reportError(declaredClass->location, GenericError{format("Cannot overload non-function class member '%s'", propName.c_str())});
}
}
}
return ControlFlow::None;
@ -1641,13 +1644,10 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareFunc
FunctionDefinition defn;
if (FFlag::LuauDeclarationExtraPropData)
{
defn.definitionModuleName = module->name;
defn.definitionLocation = global->location;
defn.varargLocation = global->vararg ? std::make_optional(global->varargLocation) : std::nullopt;
defn.originalNameLocation = global->nameLocation;
}
defn.definitionModuleName = module->name;
defn.definitionLocation = global->location;
defn.varargLocation = global->vararg ? std::make_optional(global->varargLocation) : std::nullopt;
defn.originalNameLocation = global->nameLocation;
TypeId fnType = arena->addType(FunctionType{TypeLevel{}, funScope.get(), std::move(genericTys), std::move(genericTps), paramPack, retPack, defn});
FunctionType* ftv = getMutable<FunctionType>(fnType);
@ -1989,7 +1989,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExpr* expr, std::
Inference result;
if (auto group = expr->as<AstExprGroup>())
result = check(scope, group->expr, expectedType, forceSingleton);
result = check(scope, group->expr, expectedType, forceSingleton, generalize);
else if (auto stringExpr = expr->as<AstExprConstantString>())
result = check(scope, stringExpr, expectedType, forceSingleton);
else if (expr->is<AstExprConstantNumber>())
@ -2188,6 +2188,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprIndexExpr* in
{
if (auto constantString = indexExpr->index->as<AstExprConstantString>())
{
module->astTypes[indexExpr->index] = builtinTypes->stringType;
const RefinementKey* key = dfg->getRefinementKey(indexExpr);
return checkIndexName(scope, key, indexExpr->expr, constantString->value.data, indexExpr->location);
}
@ -3005,7 +3006,13 @@ TypeId ConstraintGenerator::resolveType(const ScopePtr& scope, AstType* ty, bool
}
else if (p.typePack)
{
packParameters.push_back(resolveTypePack(scope, p.typePack, /* inTypeArguments */ true));
TypePackId tp = resolveTypePack(scope, p.typePack, /*inTypeArguments*/ true);
// If we need more regular types, we can use single element type packs to fill those in
if (parameters.size() < alias->typeParams.size() && size(tp) == 1 && finite(tp) && first(tp))
parameters.push_back(*first(tp));
else
packParameters.push_back(tp);
}
else
{

View File

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

View File

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

View File

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

View File

@ -231,14 +231,17 @@ std::pair<OverloadResolver::Analysis, ErrorVec> OverloadResolver::checkOverload_
// is ok.
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
// definitely does not match.
if (args->head.size() > requiredHead.size())
{
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}};
}
@ -250,7 +253,7 @@ std::pair<OverloadResolver::Analysis, ErrorVec> OverloadResolver::checkOverload_
if (!subtyping.isSubtype(builtinTypes->nilType, requiredHead[i]).isSubtype)
{
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}};
}

View File

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

View File

@ -627,6 +627,11 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
result = isCovariantWith(env, p);
else if (auto p = get2<SingletonType, SingletonType>(subTy, superTy))
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))
result = isCovariantWith(env, p);
else if (auto p = get2<TableType, TableType>(subTy, superTy))

View File

@ -389,6 +389,10 @@ TypeId matchLiteralType(
TypeId tProp = follow(*propTy);
if (get<BlockedType>(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
LUAU_ASSERT(!"Unexpected");

View File

@ -1445,37 +1445,40 @@ struct TypeChecker2
else
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: ";
std::vector<TypeId> overloads;
if (resolver.nonviableOverloads.empty())
for (const auto& [ty, p] : resolver.resolution)
{
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)
@ -1756,7 +1759,7 @@ struct TypeChecker2
if (get<TypeFunctionInstanceType>(follow(retTy)))
{
TypeFunctionReductionGuessResult result = guesser.guessTypeFunctionReductionForFunctionExpr(*fn, inferredFtv, retTy);
if (result.shouldRecommendAnnotation)
if (result.shouldRecommendAnnotation && !get<UnknownType>(result.guessedReturnType))
reportError(
ExplicitFunctionAnnotationRecommended{std::move(result.guessedFunctionAnnotations), result.guessedReturnType},
fn->location
@ -1838,6 +1841,17 @@ struct TypeChecker2
if (nty && nty->shouldSuppressErrors())
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 (isOptional(operandType))

View File

@ -659,12 +659,9 @@ TypeFunctionReductionResult<TypeId> lenTypeFunction(
if (normTy->shouldSuppressErrors())
return {ctx->builtins->numberType, false, {}, {}};
// if we have an uninhabited type (like `never`), we can never observe that the operator didn't work.
if (inhabited == NormalizationResult::False)
return {ctx->builtins->neverType, false, {}, {}};
// # always returns a number, even if its operand is never.
// 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, {}, {}};
// we use the normalized operand here in case there was an intersection or union.
@ -1576,86 +1573,116 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
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");
LUAU_ASSERT(false);
}
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
if (isPending(targetTy, ctx->solver))
return {std::nullopt, false, {targetTy}, {}};
else if (isPending(discriminantTy, ctx->solver))
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)
else
{
std::optional<TypeId> targetMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, targetTy);
std::optional<TypeId> discriminantMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, discriminantTy);
if (!targetMaybeGeneralized)
return {std::nullopt, false, {targetTy}, {}};
else if (!discriminantMaybeGeneralized)
return {std::nullopt, false, {discriminantTy}, {}};
targetTy = *targetMaybeGeneralized;
discriminantTy = *discriminantMaybeGeneralized;
for (auto t : discriminantTypes)
{
if (isPending(t, ctx->solver))
return {std::nullopt, false, {t}, {}};
}
}
// we need a more complex check for blocking on the discriminant in particular
FindRefinementBlockers frb;
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))
// Refine a target type and a discriminant one at a time.
// Returns result : TypeId, toBlockOn : vector<TypeId>
auto stepRefine = [&ctx](TypeId target, TypeId discriminant) -> std::pair<TypeId, std::vector<TypeId>>
{
SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, targetTy, discriminantTy);
if (!result.blockedTypes.empty())
return {std::nullopt, false, {result.blockedTypes.begin(), result.blockedTypes.end()}, {}};
std::vector<TypeId> toBlock;
if (ctx->solver)
{
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();
}
// 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, {}, {}};
return {target, false, {}, {}};
}
TypeFunctionReductionResult<TypeId> singletonTypeFunction(

View File

@ -34,8 +34,6 @@ LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAGVARIABLE(LuauRemoveBadRelationalOperatorWarning, false)
LUAU_FASTFLAGVARIABLE(LuauOkWithIteratingOverTableProperties, false)
LUAU_FASTFLAGVARIABLE(LuauReusableSubstitutions, false)
LUAU_FASTFLAG(LuauDeclarationExtraPropData)
namespace Luau
{
@ -1756,28 +1754,22 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareClass&
ftv->argTypes = addTypePack(TypePack{{classTy}, ftv->argTypes});
ftv->hasSelf = true;
if (FFlag::LuauDeclarationExtraPropData)
{
FunctionDefinition defn;
FunctionDefinition defn;
defn.definitionModuleName = currentModule->name;
defn.definitionLocation = prop.location;
// No data is preserved for varargLocation
defn.originalNameLocation = prop.nameLocation;
defn.definitionModuleName = currentModule->name;
defn.definitionLocation = prop.location;
// No data is preserved for varargLocation
defn.originalNameLocation = prop.nameLocation;
ftv->definition = defn;
}
ftv->definition = defn;
}
}
if (assignTo.count(propName) == 0)
{
if (FFlag::LuauDeclarationExtraPropData)
assignTo[propName] = {propTy, /*deprecated*/ false, /*deprecatedSuggestion*/ "", prop.location};
else
assignTo[propName] = {propTy};
assignTo[propName] = {propTy, /*deprecated*/ false, /*deprecatedSuggestion*/ "", prop.location};
}
else if (FFlag::LuauDeclarationExtraPropData)
else
{
Luau::Property& prop = assignTo[propName];
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())});
}
}
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;
@ -1870,13 +1837,10 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareFuncti
FunctionDefinition defn;
if (FFlag::LuauDeclarationExtraPropData)
{
defn.definitionModuleName = currentModule->name;
defn.definitionLocation = global.location;
defn.varargLocation = global.vararg ? std::make_optional(global.varargLocation) : std::nullopt;
defn.originalNameLocation = global.nameLocation;
}
defn.definitionModuleName = currentModule->name;
defn.definitionLocation = global.location;
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});
FunctionType* ftv = getMutable<FunctionType>(fnType);
@ -4991,24 +4955,12 @@ TypeId TypeChecker::instantiate(const ScopePtr& scope, TypeId ty, Location locat
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)
reusableInstantiation.childLimit = *instantiationChildLimit;
if (instantiationChildLimit)
reusableInstantiation.childLimit = *instantiationChildLimit;
instantiated = reusableInstantiation.substitute(ty);
}
else
{
Instantiation instantiation{log, &currentModule->internalTypes, builtinTypes, scope->level, /*scope*/ nullptr};
if (instantiationChildLimit)
instantiation.childLimit = *instantiationChildLimit;
instantiated = instantiation.substitute(ty);
}
instantiated = reusableInstantiation.substitute(ty);
if (instantiated.has_value())
return *instantiated;

View File

@ -19,7 +19,6 @@ LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
LUAU_FASTFLAGVARIABLE(DebugLuauDeferredConstraintResolution, false)
LUAU_FASTFLAGVARIABLE(LuauNativeAttribute, false)
LUAU_FASTFLAGVARIABLE(LuauAttributeSyntaxFunExpr, false)
LUAU_FASTFLAGVARIABLE(LuauDeclarationExtraPropData, false)
LUAU_FASTFLAGVARIABLE(LuauUserDefinedTypeFunctions, false)
namespace Luau
@ -938,16 +937,10 @@ AstStat* Parser::parseTypeFunction(const Location& start)
AstDeclaredClassProp Parser::parseDeclaredClassMethod()
{
Location start;
if (FFlag::LuauDeclarationExtraPropData)
start = lexer.current().location;
Location start = lexer.current().location;
nextLexeme();
if (!FFlag::LuauDeclarationExtraPropData)
start = lexer.current().location;
Name fnName = parseName("function name");
// TODO: generic method declarations CLI-39909
@ -972,7 +965,7 @@ AstDeclaredClassProp Parser::parseDeclaredClassMethod()
expectMatchAndConsume(')', matchParen);
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<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)
{
return AstDeclaredClassProp{
fnName.name,
FFlag::LuauDeclarationExtraPropData ? fnName.location : Location{},
reportTypeError(Location(start, end), {}, "'self' must be present as the unannotated first parameter"),
true
fnName.name, fnName.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
);
return AstDeclaredClassProp{
fnName.name,
FFlag::LuauDeclarationExtraPropData ? fnName.location : Location{},
fnType,
true,
FFlag::LuauDeclarationExtraPropData ? Location(start, end) : Location{}
};
return AstDeclaredClassProp{fnName.name, fnName.location, fnType, true, Location(start, end)};
}
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)
return reportStatError(Location(start, end), {}, {}, "All declaration parameters must be annotated");
if (FFlag::LuauDeclarationExtraPropData)
return allocator.alloc<AstStatDeclareFunction>(
Location(start, end),
attributes,
globalName.name,
globalName.location,
generics,
genericPacks,
AstTypeList{copy(vars), varargAnnotation},
copy(varNames),
vararg,
varargLocation,
retTypes
);
else
return allocator.alloc<AstStatDeclareFunction>(
Location(start, end),
attributes,
globalName.name,
Location{},
generics,
genericPacks,
AstTypeList{copy(vars), varargAnnotation},
copy(varNames),
false,
Location{},
retTypes
);
return allocator.alloc<AstStatDeclareFunction>(
Location(start, end),
attributes,
globalName.name,
globalName.location,
generics,
genericPacks,
AstTypeList{copy(vars), varargAnnotation},
copy(varNames),
vararg,
varargLocation,
retTypes
);
}
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();
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;
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)
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");
props.push_back(AstDeclaredClassProp{
AstName(chars->data), Location(nameBegin, nameEnd), type, false, Location(begin.location, lexer.previousLocation())
});
}
else
{
std::optional<AstArray<char>> chars = parseCharArray();
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");
report(begin.location, "String literal contains malformed escape sequence or \\0");
}
}
else if (lexer.current().type == '[')
@ -1178,7 +1132,7 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
indexer = parseTableIndexer(AstTableAccess::ReadWrite, std::nullopt);
}
}
else if (FFlag::LuauDeclarationExtraPropData)
else
{
Location propStart = lexer.current().location;
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())}
);
}
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;
@ -1206,9 +1153,7 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
expectAndConsume(':', "global variable declaration");
AstType* type = parseType(/* in declaration context */ true);
return allocator.alloc<AstStatDeclareGlobal>(
Location(start, type->location), globalName->name, FFlag::LuauDeclarationExtraPropData ? globalName->location : Location{}, type
);
return allocator.alloc<AstStatDeclareGlobal>(Location(start, type->location), globalName->name, globalName->location, type);
}
else
{

View File

@ -17,6 +17,8 @@
#include <string.h>
LUAU_FASTFLAGVARIABLE(LuauErrorResumeCleanupArgs, false)
/*
** {======================================================
** Error-recovery functions
@ -426,9 +428,13 @@ static void resume_handle(lua_State* L, void* ud)
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));
incr_top(L);
return LUA_ERRRUN;
@ -455,11 +461,11 @@ int lua_resume(lua_State* L, lua_State* from, int nargs)
{
int status;
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;
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->isactive = true;
@ -484,11 +490,11 @@ int lua_resumeerror(lua_State* L, lua_State* from)
{
int status;
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;
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->isactive = true;

View File

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

View File

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

View File

@ -1658,6 +1658,10 @@ table.create(42, {} :: {})
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"(
local t1 = {} -- ok: empty
local t2 = {1, 2} -- ok: array

View File

@ -440,11 +440,11 @@ struct NormalizeFixture : Fixture
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();
CheckResult result = check("type _Res = " + annotation);
LUAU_REQUIRE_NO_ERRORS(result);
LUAU_REQUIRE_ERROR_COUNT(expectedErrors, result);
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")
{
CHECK(nullptr == toNormalizedType("Not<(boolean) -> boolean>"));
CHECK(nullptr == toNormalizedType("Not<(boolean) -> boolean>", FFlag::DebugLuauDeferredConstraintResolution ? 1 : 0));
}
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")
{
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("table" == toString(normal("Not<Not<tbl>>")));
}

View File

@ -17,7 +17,6 @@ LUAU_FASTINT(LuauTypeLengthLimit);
LUAU_FASTINT(LuauParseErrorLimit);
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
LUAU_FASTFLAG(LuauAttributeSyntaxFunExpr);
LUAU_FASTFLAG(LuauDeclarationExtraPropData);
LUAU_FASTFLAG(LuauUserDefinedTypeFunctions);
namespace
@ -1918,8 +1917,6 @@ function func():end
TEST_CASE_FIXTURE(Fixture, "parse_declarations")
{
ScopedFastFlag luauDeclarationExtraPropData{FFlag::LuauDeclarationExtraPropData, true};
AstStatBlock* stat = parseEx(R"(
declare foo: number
declare function bar(x: number): string
@ -1957,8 +1954,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_declarations")
TEST_CASE_FIXTURE(Fixture, "parse_class_declarations")
{
ScopedFastFlag luauDeclarationExtraPropData{FFlag::LuauDeclarationExtraPropData, true};
AstStatBlock* stat = parseEx(R"(
declare class Foo
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")
{
// We erroneously report an extra error in this case when the new solver is enabled.
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
type T<A> = (A...) -> ()
)");
@ -240,6 +243,9 @@ TEST_CASE_FIXTURE(Fixture, "dependent_generic_aliases")
TEST_CASE_FIXTURE(Fixture, "mutually_recursive_generic_aliases")
{
// CLI-116108
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
--!strict
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")
{
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
const std::string code = R"(
-- 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>?}
@ -535,11 +543,13 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_import_mutation")
TEST_CASE_FIXTURE(Fixture, "type_alias_local_mutation")
{
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
type Cool = { a: number, b: string }
local c: Cool = { a = 1, b = "s" }
type NotCool<x> = Cool
)");
type Cool = { a: number, b: string }
local c: Cool = { a = 1, b = "s" }
type NotCool<x> = Cool
)");
LUAU_REQUIRE_NO_ERRORS(result);
std::optional<TypeId> ty = requireType("c");
@ -554,6 +564,8 @@ type NotCool<x> = Cool
TEST_CASE_FIXTURE(Fixture, "type_alias_local_rename")
{
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
type Cool = { a: number, b: string }
type NotCool = Cool
@ -615,16 +627,16 @@ type X = Import.X
TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_of_an_imported_recursive_generic_type")
{
fileResolver.source["game/A"] = R"(
export type X<T, U> = { a: T, b: U, C: X<T, U>? }
return {}
export type X<T, U> = { a: T, b: U, C: X<T, U>? }
return {}
)";
CheckResult aResult = frontend.check("game/A");
LUAU_REQUIRE_NO_ERRORS(aResult);
CheckResult bResult = check(R"(
local Import = require(game.A)
type X<T, U> = Import.X<T, U>
local Import = require(game.A)
type X<T, U> = Import.X<T, U>
)");
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}));
bResult = check(R"(
local Import = require(game.A)
type X<T, U> = Import.X<U, T>
local Import = require(game.A)
type X<T, U> = Import.X<U, T>
)");
LUAU_REQUIRE_NO_ERRORS(bResult);
@ -648,8 +660,16 @@ type X<T, U> = Import.X<U, T>
ty2 = lookupType("X");
REQUIRE(ty2);
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 |}?");
if (FFlag::DebugLuauDeferredConstraintResolution)
{
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")
@ -691,6 +711,9 @@ TEST_CASE_FIXTURE(Fixture, "mutually_recursive_types_restriction_ok")
TEST_CASE_FIXTURE(Fixture, "mutually_recursive_types_restriction_not_ok_1")
{
// CLI-116108
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
-- OK because forwarded types are used with their parameters.
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")
{
// CLI-116108
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
-- Not OK because forwarded types are used with different types than their parameters.
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")
{
// CLI-116108
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
type Tree1<T,U> = { data: T, children: {Tree2<U,T>} }
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")
{
// CLI-116108
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
-- this would be an infinite type if we allowed it
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")
{
// 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,
// which has the type alias FakeString point to the type alias `string` that which points to `number`.
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")
{
// CLI-114134 - We need egraphs to properly simplify these types.
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
fileResolver.source["game/ReactCurrentDispatcher"] = R"(
export type BasicStateAction<S> = ((S) -> S) | S
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")
{
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
--!strict
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")
{
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
local function nifty(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")
{
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check(R"(
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")
{
// CLI-115690
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check(R"(
--!strict
string.format("%s", 123)
@ -694,10 +705,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "bad_select_should_not_crash")
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.
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
{
@ -709,6 +720,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "bad_select_should_not_crash")
TEST_CASE_FIXTURE(BuiltinsFixture, "select_way_out_of_range")
{
// CLI-115720
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check(R"(
select(5432598430953240958)
)");
@ -720,6 +735,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "select_way_out_of_range")
TEST_CASE_FIXTURE(BuiltinsFixture, "select_slightly_out_of_range")
{
// CLI-115720
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check(R"(
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")
{
// CLI-115720
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check(R"(
--!nonstrict
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")
{
// CLI-115690
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check("local _ = ('%s'):format(5)");
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")
{
// CLI-115690
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check(R"(
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")
{
// CLI-115690
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check(R"(
local fmt = string.format
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")
{
// CLI-115690
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check(R"(
("%s%d%s"):format(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")
{
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check(R"(
local function f(...: number?)
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")
{
if (FFlag::DebugLuauDeferredConstraintResolution)
{
// CLI-114134 - egraph simplification
return;
}
CheckResult result = check(R"(
local function f(x: nil)
return assert(x, "hmm")
@ -1080,7 +1128,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_is_generic")
)");
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_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")
{
// 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"(
local a = {b=setmetatable}
a.b()
a:b()
a:b({})
local a = {b=setmetatable}
a.b()
a:b()
a:b({})
)");
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");
@ -1109,8 +1163,8 @@ a:b({})
TEST_CASE_FIXTURE(Fixture, "typeof_unresolved_function")
{
CheckResult result = check(R"(
local function f(a: typeof(f)) end
)");
local function f(a: typeof(f)) end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
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")
{
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
function makeClone(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));
}
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")
{
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")
{
// This is allowed in the new solver
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
local v = Vector2.New(0, 5)
v = 444
@ -364,9 +397,17 @@ TEST_CASE_FIXTURE(ClassFixture, "table_class_unification_reports_sane_errors_for
foo(a)
)");
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]));
if (FFlag::DebugLuauDeferredConstraintResolution)
{
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")
@ -412,15 +453,27 @@ b(a)
)");
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:
Property 'Y' is not compatible.
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")
{
// CLI-116433
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
local i = ChildClass.New()
type ChildClass = { x: number }
@ -611,10 +664,14 @@ TEST_CASE_FIXTURE(ClassFixture, "indexable_classes")
local y = x[true]
)");
CHECK_EQ(
toString(result.errors.at(0)), "Type 'boolean' could not be converted into 'number | string'; none of the union options are compatible"
);
if (FFlag::DebugLuauDeferredConstraintResolution)
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"
);
}
{
CheckResult result = check(R"(
@ -622,9 +679,14 @@ TEST_CASE_FIXTURE(ClassFixture, "indexable_classes")
x[true] = 42
)");
CHECK_EQ(
toString(result.errors.at(0)), "Type 'boolean' could not be converted into 'number | string'; none of the union options are compatible"
);
if (FFlag::DebugLuauDeferredConstraintResolution)
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)
@ -633,7 +695,13 @@ TEST_CASE_FIXTURE(ClassFixture, "indexable_classes")
local x : IndexableClass
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"(
@ -682,7 +750,7 @@ TEST_CASE_FIXTURE(ClassFixture, "indexable_classes")
local y = x["key"]
)");
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
CHECK_EQ(toString(result.errors.at(0)), "Type 'string' could not be converted into 'number'");
}

View File

@ -7,8 +7,6 @@
#include "doctest.h"
LUAU_FASTFLAG(LuauDeclarationExtraPropData)
using namespace Luau;
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")
{
ScopedFastFlag luauDeclarationExtraPropData{FFlag::LuauDeclarationExtraPropData, true};
loadDefinition(R"(
declare class MyClass
function myMethod(self)

View File

@ -11,6 +11,7 @@
#include "ClassFixture.h"
#include "Fixture.h"
#include "ScopedFlags.h"
#include "doctest.h"
using namespace Luau;
@ -255,7 +256,7 @@ TEST_CASE_FIXTURE(Fixture, "list_only_alternative_overloads_that_match_argument_
REQUIRE(ei);
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
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")
{
// This is not part of the new non-strict specification currently.
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
--!nonstrict
@ -476,13 +480,28 @@ TEST_CASE_FIXTURE(Fixture, "another_higher_order_function")
TEST_CASE_FIXTURE(Fixture, "another_other_higher_order_function")
{
CheckResult result = check(R"(
local d
d:foo()
d:foo()
)");
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CheckResult result = check(R"(
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")
@ -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")
{
// This is not part of the spec for the new non-strict mode currently.
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
--!nonstrict
function foo(): number
@ -612,7 +634,7 @@ TEST_CASE_FIXTURE(Fixture, "complicated_return_types_require_an_explicit_annotat
local i = 0
function most_of_the_natural_numbers(): number?
if i < 10 then
i = i + 1
i += 1
return i
else
return nil
@ -737,7 +759,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "higher_order_function_4")
end
end
function mergesort(arr, comp)
function mergesort<T>(arr: {T}, comp: (T, T) -> boolean)
local work = {}
for i = 1, #arr do
work[i] = arr[i]
@ -756,7 +778,11 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "higher_order_function_4")
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.
@ -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")
{
// new non-strict mode spec does not include this error yet.
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
--!nonstrict
@ -985,11 +1014,17 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "calling_function_with_anytypepack_doesnt_lea
opts.exhaustive = true;
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")
{
// FIXME: CLI-116157 variadic and generic type packs seem to be interacting incorrectly.
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
--!strict
@ -1011,6 +1046,9 @@ TEST_CASE_FIXTURE(Fixture, "too_many_return_values")
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"(
--!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")
{
// FIXME: CLI-116157 variadic and generic type packs seem to be interacting incorrectly.
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
--!strict
@ -1072,13 +1113,25 @@ TEST_CASE_FIXTURE(Fixture, "function_does_not_return_enough_values")
end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
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);
auto tpm = get<TypePackMismatch>(result.errors[0]);
REQUIRE(tpm);
CHECK("number, string" == toString(tpm->wantedTp));
CHECK("number" == toString(tpm->givenTp));
}
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")
@ -1100,13 +1153,19 @@ TEST_CASE_FIXTURE(Fixture, "function_cast_error_uses_correct_language")
REQUIRE(tm1);
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]);
REQUIRE(tm2);
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")
@ -1123,7 +1182,10 @@ TEST_CASE_FIXTURE(Fixture, "no_lossy_function_type")
LUAU_REQUIRE_NO_ERRORS(result);
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));
REQUIRE(ftv);
CHECK(ftv->hasSelf);
@ -1166,13 +1228,20 @@ TEST_CASE_FIXTURE(Fixture, "return_type_by_overload")
LUAU_REQUIRE_ERRORS(result);
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?
CHECK_EQ("string", toString(requireType("z")));
}
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
CheckResult result = check(R"(
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")
{
// 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"(
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)
@ -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")
{
// 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"(
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
@ -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")
{
// 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"(
type Table = { x: number, y: number }
local f: (Table) -> number = function(t) return t.x + t.y end
@ -1412,11 +1490,18 @@ local function i(): ...{string|number}
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")
{
// FIXME: CLI-116111 test disabled until type path stringification is improved
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
type A = (number, number) -> string
type B = (number) -> string
@ -1437,6 +1522,9 @@ caused by:
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"(
type A = (number, number) -> 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")
{
// FIXME: CLI-116111 test disabled until type path stringification is improved
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
type A = (number, number) -> (number)
type B = (number, number) -> (number, boolean)
@ -1478,6 +1569,9 @@ caused by:
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"(
type A = (number, number) -> string
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")
{
// FIXME: CLI-116111 test disabled until type path stringification is improved
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
type A = (number, number) -> (number, string)
type B = (number, number) -> (number, boolean)
@ -1578,9 +1675,18 @@ t.f = function(x)
end
)");
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')");
if (FFlag::DebugLuauDeferredConstraintResolution)
{
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")
@ -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")
{
// 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"(
local foo
@ -1637,8 +1746,15 @@ t.f = function(x)
end
)");
LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK_EQ(toString(result.errors[0]), R"(Type
if (FFlag::DebugLuauDeferredConstraintResolution)
{
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'
could not be converted into
'((number) -> number)?'
@ -1651,7 +1767,8 @@ could not be converted into
caused by:
Argument #1 type is not compatible.
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")
@ -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")
{
// 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"(
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")
{
// FIXME: CLI-116157 variadic and generic type packs seem to be interacting incorrectly.
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
function test(a: number, b: string, ...)
return 1
@ -1733,6 +1856,9 @@ wrapper(test)
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"(
function test(a: number, b: string, ...)
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")));
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
CHECK_EQ("({+ p: {+ q: nil +} +}) -> nil", toString(requireType("g")));
}
@ -1854,24 +1980,43 @@ u.b().foo()
)");
LUAU_REQUIRE_ERROR_COUNT(9, result);
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");
if (FFlag::DebugLuauDeferredConstraintResolution)
{
// These improvements to the error messages are currently regressed in the new type solver.
CHECK_EQ(toString(result.errors[0]), "Argument count mismatch. Function expects 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[2]), "Argument count mismatch. Function expects 1 to 3 arguments, 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[4]), "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
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"(
--!nonstrict
local function foo(a, b) end
foo(string.find("hello", "e"))
--!nonstrict
local function foo(a, b) end
foo(string.find("hello", "e"))
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
@ -1880,6 +2025,9 @@ foo(string.find("hello", "e"))
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"(
--!strict
@ -1954,7 +2102,16 @@ TEST_CASE_FIXTURE(Fixture, "concrete_functions_are_not_supertypes_of_function")
LUAU_REQUIRE_ERROR_COUNT(2, result);
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);
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")
@ -1991,9 +2148,8 @@ end
TEST_CASE_FIXTURE(BuiltinsFixture, "dont_assert_when_the_tarjan_limit_is_exceeded_during_generalization")
{
ScopedFastInt sfi{FInt::LuauTarjanChildLimit, 2};
if (!FFlag::DebugLuauDeferredConstraintResolution)
return;
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
ScopedFastInt sfi{FInt::LuauTarjanChildLimit, 1};
CheckResult result = check(R"(
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);
CHECK_MESSAGE(get<CodeTooComplex>(result.errors[0]), "Expected CodeTooComplex but got: " << toString(result.errors[0]));
CHECK(Location({1, 17}, {1, 18}) == result.errors[0].location);
CHECK_MESSAGE(get<UnificationTooComplex>(result.errors[0]), "Expected UnificationTooComplex but got: " << toString(result.errors[0]));
}
/* 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")
{
CheckResult result = check(R"(
function pcall<A..., R...>(...: A...): R...
function pcall<A..., R...>(...: (A...) -> R...): (boolean, R...)
return nil :: any
end
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);
// note that b is not in the generic list; it is free, the unconstrained type of `bar`.
CHECK(toString(requireType("foo")) == "<a>(a) -> b");
if (FFlag::DebugLuauDeferredConstraintResolution)
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")
@ -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 })
)");
// 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.
if (FFlag::DebugLuauDeferredConstraintResolution)
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
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:
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')";
CHECK_EQ(expected, toString(result.errors[0]));
CHECK_EQ("Type 'vec2' could not be converted into 'number'", toString(result.errors[1]));
CHECK_EQ(expected, toString(result.errors[0]));
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")
@ -2134,10 +2307,22 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "param_1_and_2_both_takes_the_same_generic_bu
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'");
CHECK_EQ(toString(result.errors[1]), "Type 'number' could not be converted into 'boolean'");
auto tm = get<TypeMismatch>(result.errors[0]);
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")
@ -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")
{
// This test is blocking CI until subtyping is complete.
if (!FFlag::DebugLuauDeferredConstraintResolution)
return;
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
CheckResult result = check(R"(
@ -2201,7 +2382,9 @@ TEST_CASE_FIXTURE(Fixture, "generic_packs_are_not_variadic")
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")
@ -2316,14 +2499,11 @@ end
if (FFlag::DebugLuauDeferredConstraintResolution)
{
LUAU_REQUIRE_ERROR_COUNT(4, result);
CHECK(
toString(result.errors[0]) ==
"Operator '-' could not be applied to operands of types unknown and number; there is no corresponding overload for __sub"
);
LUAU_REQUIRE_ERROR_COUNT(5, result);
CHECK(get<ConstraintSolvingIncompleteError>(result.errors[0]));
CHECK(
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(
toString(result.errors[2]) ==
@ -2333,6 +2513,10 @@ end
toString(result.errors[3]) ==
"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
{
@ -2415,17 +2599,15 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tf_suggest_arg_type_2")
frontend.options.retainFullTypeGraphs = false;
CheckResult result = check(R"(
local function escape_fslash(pre)
return (#pre % 2 == 0 and '\\' or '') .. pre .. '.'
end
)");
local function escape_fslash(pre)
return (#pre % 2 == 0 and '\\' or '') .. pre .. '.'
end
)");
LUAU_REQUIRE_ERRORS(result);
auto err = get<ExplicitFunctionAnnotationRecommended>(result.errors.back());
LUAU_ASSERT(err);
CHECK("unknown" == toString(err->recommendedReturn));
REQUIRE(err->recommendedArgs.size() == 1);
CHECK("a" == toString(err->recommendedArgs[0].second));
auto err = get<NotATable>(result.errors.back());
REQUIRE(err);
CHECK("a" == toString(err->ty));
}
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);
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(location.begin.line == 7);
CHECK(location.end.line == 7);
CHECK("((Point) -> ())?" == toString(tm->wantedType));
CHECK("({ read z: number }) -> ()" == toString(tm->givenType));
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")
@ -2698,7 +2895,7 @@ TEST_CASE_FIXTURE(Fixture, "unifier_should_not_bind_free_types")
auto tm2 = get<TypePackMismatch>(result.errors[1]);
REQUIRE(tm2);
CHECK(toString(tm2->wantedTp) == "string");
CHECK(toString(tm2->givenTp) == "~(false?)");
CHECK(toString(tm2->givenTp) == "buffer | class | function | number | string | table | thread | true");
}
else
{

View File

@ -4,6 +4,7 @@
#include "Fixture.h"
#include "ScopedFlags.h"
#include "doctest.h"
using namespace Luau;
@ -331,6 +332,9 @@ TEST_CASE_FIXTURE(Fixture, "table_intersection_write_sealed")
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"(
type X = { x: (number) -> number }
type Y = { y: (string) -> string }
@ -368,6 +372,7 @@ caused by:
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
CheckResult result = check(R"(
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")
{
ScopedFastFlag dcr{
FFlag::DebugLuauDeferredConstraintResolution, false
}; // CLI-116474 Semantic subtyping of assignments needs to decide how to interpret intersections of functions
CheckResult result = check(R"(
function f(x: ((number) -> number) & ((string) -> string))
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")
{
ScopedFastFlag dcr{
FFlag::DebugLuauDeferredConstraintResolution, false
}; // CLI-116474 Semantic subtyping of assignments needs to decide how to interpret intersections of functions
CheckResult result = check(R"(
function f<a...,b...>()
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")
{
ScopedFastFlag dcr{
FFlag::DebugLuauDeferredConstraintResolution, false
}; // CLI-116474 Semantic subtyping of assignments needs to decide how to interpret intersections of functions
CheckResult result = check(R"(
function f<a...,b...>()
function g(x : ((number) -> number?) & ((unknown) -> string?))
@ -801,12 +815,36 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_never_result")
end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type
if (FFlag::DebugLuauDeferredConstraintResolution)
{
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)'
could not be converted into
'(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")
@ -830,6 +868,9 @@ could not be converted into
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"(
function f(x : ((string?) -> (string | number)) & ((number?) -> ...number))
local y : ((nil) -> (number, number?)) = x -- OK
@ -856,11 +897,18 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_1")
end
)");
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"
);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
LUAU_REQUIRE_NO_ERRORS(result);
}
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")
@ -874,11 +922,18 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_2")
end
)");
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"
);
if (FFlag::DebugLuauDeferredConstraintResolution)
{
LUAU_REQUIRE_NO_ERRORS(result);
}
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")
@ -892,12 +947,19 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_3")
end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type
if (FFlag::DebugLuauDeferredConstraintResolution)
{
LUAU_REQUIRE_NO_ERRORS(result);
}
else
{
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type
'(() -> (a...)) & (() -> (number?, a...))'
could not be converted into
'() -> 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")
@ -912,11 +974,27 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_4")
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK_EQ(
R"(Type
'((a...) -> ()) & ((number, a...) -> number)'
could not be converted into
'(number?) -> ()'; none of the intersection parts are compatible)";
CHECK_EQ(expected, toString(result.errors[0]));
'((a...) -> ()) & ((number, a...) -> number)'; at [0].returns(), is not a subtype of number
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")

View File

@ -32,7 +32,14 @@ TEST_CASE_FIXTURE(Fixture, "for_loop")
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")
@ -107,8 +114,17 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "iteration_regression_issue_69967_alt")
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ("number", toString(requireType("x")));
CHECK_EQ("string", toString(requireType("y")));
if (FFlag::DebugLuauDeferredConstraintResolution)
{
// 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")
@ -124,12 +140,23 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop")
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(*builtinTypes->numberType, *requireType("n"));
CHECK_EQ(*builtinTypes->stringType, *requireType("s"));
if (FFlag::DebugLuauDeferredConstraintResolution)
{
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")
{
// CLI-116494 The generics K and V are leaking out of the next() function somehow.
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
local n
local s
@ -240,11 +267,17 @@ TEST_CASE_FIXTURE(Fixture, "for_in_loop_on_error")
LUAU_REQUIRE_ERROR_COUNT(2, result);
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")
{
// We report a spuriouus duplicate error here.
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
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")
{
// Spurious duplicate errors
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
local function hasDivisors(value: number, table)
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")
{
// CLI-116496
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
function prime_iter(state, index)
return 1
@ -380,7 +419,10 @@ TEST_CASE_FIXTURE(Fixture, "while_loop")
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")
@ -394,7 +436,10 @@ TEST_CASE_FIXTURE(Fixture, "repeat_loop")
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")
@ -490,7 +535,15 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "properly_infer_iteratee_is_a_free_table")
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")
@ -532,7 +585,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "ipairs_produces_integral_indices")
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")
@ -639,6 +695,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "unreachable_code_after_infinite_loop")
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};
CheckResult result = check(R"(
@ -704,6 +764,9 @@ TEST_CASE_FIXTURE(Fixture, "loop_iter_basic")
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"(
local t: {string} = {}
local extra
@ -718,7 +781,11 @@ TEST_CASE_FIXTURE(Fixture, "loop_iter_trailing_nil")
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"(
local t = {}
@ -742,7 +809,8 @@ TEST_CASE_FIXTURE(Fixture, "loop_iter_no_indexer_nonstrict")
TEST_CASE_FIXTURE(BuiltinsFixture, "loop_iter_metamethod_nil")
{
if (!FFlag::DebugLuauDeferredConstraintResolution)
// CLI-116499 Free types persisting until typechecking time.
if (1 || !FFlag::DebugLuauDeferredConstraintResolution)
return;
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")
{
if (!FFlag::DebugLuauDeferredConstraintResolution)
// CLI-116500
if (1 || !FFlag::DebugLuauDeferredConstraintResolution)
return;
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")
{
if (!FFlag::DebugLuauDeferredConstraintResolution)
// CLI-116500
if (1 || !FFlag::DebugLuauDeferredConstraintResolution)
return;
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")
{
if (!FFlag::DebugLuauDeferredConstraintResolution)
// CLI-116500
if (1 || !FFlag::DebugLuauDeferredConstraintResolution)
return;
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")
{
// CLI-116500
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check(R"(
local function makeEnum(members)
local enum = {}
@ -975,6 +1050,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "dcr_iteration_fragmented_keys")
TEST_CASE_FIXTURE(BuiltinsFixture, "dcr_xpath_candidates")
{
// CLI-116500
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check(R"(
type Instance = {}
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);
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")
{
// CLI-116498 - Sometimes you can iterate over tables with no indexer.
ScopedFastFlag sff0{FFlag::DebugLuauDeferredConstraintResolution, false};
ScopedFastFlag sff{FFlag::LuauOkWithIteratingOverTableProperties, true};
CheckResult result = check(R"(

View File

@ -56,12 +56,24 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "require")
return {hooty=hooty}
)";
fileResolver.source["game/B"] = R"(
local Hooty = require(game.A)
if (FFlag::DebugLuauDeferredConstraintResolution)
{
fileResolver.source["game/B"] = R"(
local Hooty = require(game.A)
local h -- free!
local i = Hooty.hooty(h)
)";
local h = 4
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");
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")
{
// CLI-116571 method calls are missing arity checking?
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
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")
{
// CLI-116571 method calls are missing arity checking?
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
local someTable = {}
@ -138,7 +144,10 @@ TEST_CASE_FIXTURE(Fixture, "inferring_hundreds_of_self_calls_should_not_suffocat
)");
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")
@ -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))
{
// 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;
// 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);
CHECK_EQ("Vec3", toString(requireType("a")));
CHECK_EQ("Vec3", toString(requireType("b")));
CHECK_EQ("Vec3", toString(requireType("c")));
CHECK_EQ("Vec3", toString(requireType("d")));
CHECK_EQ("Vec3", toString(requireType("e")));
CHECK("Vec3" == toString(requireType("a")));
CHECK("Vec3" == toString(requireType("b")));
CHECK("Vec3" == toString(requireType("c")));
CHECK("Vec3" == toString(requireType("d")));
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")
@ -223,11 +227,15 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typecheck_overloaded_multiply_that_is_an_int
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ("Vec3", toString(requireType("a")));
CHECK_EQ("Vec3", toString(requireType("b")));
CHECK_EQ("Vec3", toString(requireType("c")));
CHECK_EQ("Vec3", toString(requireType("d")));
CHECK_EQ("Vec3", toString(requireType("e")));
CHECK("Vec3" == toString(requireType("a")));
CHECK("Vec3" == toString(requireType("b")));
CHECK("Vec3" == toString(requireType("c")));
CHECK("Vec3" == toString(requireType("d")));
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK("mul<string, Vec3>" == toString(requireType("e")));
else
CHECK_EQ("Vec3", toString(requireType("e")));
}
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);
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")
@ -593,17 +607,32 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typecheck_unary_minus_error")
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]);
REQUIRE_EQ(*tm->wantedType, *builtinTypes->booleanType);
// given type is the typeof(foo) which is complex to compare against
// This second error is spurious. We should not be reporting it.
CHECK(get<TypeMismatch>(result.errors[1]));
}
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")
{
// CLI-116463
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
--!strict
local mt = {}
@ -673,22 +702,34 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "disallow_string_and_types_without_metatables
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)
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
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]);
REQUIRE(tm2);
CHECK_EQ(*tm2->wantedType, *builtinTypes->numberType);
CHECK_EQ(*tm2->givenType, *requireType("foo"));
GenericError* gen1 = get<GenericError>(result.errors[1]);
REQUIRE(gen1);
if (FFlag::DebugLuauDeferredConstraintResolution)
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
@ -712,8 +753,16 @@ TEST_CASE_FIXTURE(Fixture, "concat_op_on_free_lhs_and_string_rhs")
end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
REQUIRE(get<CannotInferBinaryOperation>(result.errors[0]));
if (FFlag::DebugLuauDeferredConstraintResolution)
{
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")
@ -726,7 +775,10 @@ TEST_CASE_FIXTURE(Fixture, "concat_op_on_string_lhs_and_free_rhs")
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")
@ -746,11 +798,10 @@ TEST_CASE_FIXTURE(Fixture, "strict_binary_op_where_lhs_unknown")
{
LUAU_REQUIRE_ERROR_COUNT(ops.size(), result);
CHECK_EQ(
"Type function instance Add<a, b> depends on generic function parameters but does not appear in the function signature; this "
"construct cannot be type-checked at this time",
"Operator '+' could not be applied to operands of types unknown and unknown; there is no corresponding overload for __add",
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
{
@ -833,6 +884,9 @@ TEST_CASE_FIXTURE(Fixture, "error_on_invalid_operand_types_to_relational_operato
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"(
--!strict
local _
@ -847,14 +901,17 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "UnknownGlobalCompoundAssign")
{
// In non-strict mode, global definition is still allowed
{
CheckResult result = check(R"(
--!nonstrict
a = a + 1
print(a)
)");
if (!FFlag::DebugLuauDeferredConstraintResolution)
{
CheckResult result = check(R"(
--!nonstrict
a = a + 1
print(a)
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(toString(result.errors[0]), "Unknown global 'a'");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(toString(result.errors[0]), "Unknown global 'a'");
}
}
// 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
{
CheckResult result = check(R"(
--!nonstrict
a += 1
print(a)
)");
if (!FFlag::DebugLuauDeferredConstraintResolution)
{
CheckResult result = check(R"(
--!nonstrict
a += 1
print(a)
)");
LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK_EQ(toString(result.errors[0]), "Unknown global 'a'");
LUAU_REQUIRE_ERROR_COUNT(2, result);
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")
{
if (!FFlag::DebugLuauDeferredConstraintResolution)
// CLI-115687
if (1 || !FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check(R"(
@ -1286,7 +1347,7 @@ local w = c and 1
CHECK("boolean | number" == toString(requireType("z"))); // 'false' widened to boolean
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK("((false?) & unknown) | number" == toString(requireType("w")));
CHECK("number?" == toString(requireType("w")));
else
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")
{
// CLI-116480 Subtyping bug: table should probably be a subtype of {[unknown]: unknown}
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
--!strict
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
}
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();

View File

@ -73,18 +73,27 @@ TEST_CASE_FIXTURE(Fixture, "string_function_indirect")
CHECK_EQ(*requireType("p"), *builtinTypes->stringType);
}
TEST_CASE_FIXTURE(Fixture, "CheckMethodsOfNumber")
TEST_CASE_FIXTURE(Fixture, "check_methods_of_number")
{
CheckResult result = check(R"(
local x: number = 9999
function x:y(z: number)
local s: string = z
end
)");
local x: number = 9999
function x:y(z: number)
local s: string = z
end
)");
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")

View File

@ -2252,4 +2252,67 @@ function(obj)
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();

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")
{
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
function f(g: ((true, string) -> ()) & ((false, number) -> ()))
g(true, 37)
@ -192,10 +194,14 @@ TEST_CASE_FIXTURE(Fixture, "enums_using_singletons_mismatch")
)");
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",
toString(result.errors[0])
);
if (FFlag::DebugLuauDeferredConstraintResolution)
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")
@ -337,10 +343,18 @@ TEST_CASE_FIXTURE(Fixture, "table_properties_type_error_escapes")
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(
R"(Table type '{ ["\n"]: number }' not compatible with type '{| ["<>"]: number |}' because the former is missing field '<>')",
toString(result.errors[0])
);
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK(
"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")
@ -354,11 +368,16 @@ local a: Animal = { tag = 'cat', cafood = 'something' }
)");
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:
None of the union options are compatible. For example:
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")
@ -372,11 +391,16 @@ local a: Result = { success = false, result = 'something' }
)");
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:
None of the union options are compatible. For example:
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")
@ -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")
{
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
local function foo(f, x)
if x == "hi" then
@ -427,7 +453,7 @@ TEST_CASE_FIXTURE(Fixture, "widen_the_supertype_if_it_is_free_and_subtype_has_si
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
LUAU_CHECK_NO_ERRORS(result);
CHECK_EQ(R"("hi")", toString(requireTypeAtPosition({3, 18})));
// 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")
{
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
local function foo(f, x): "hello"? -- anyone there?
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")
{
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
local o
o:method()
@ -263,6 +265,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "weird_case")
TEST_CASE_FIXTURE(Fixture, "dont_ice_when_failing_the_occurs_check")
{
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
--!strict
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.
TEST_CASE_FIXTURE(Fixture, "check_type_infer_recursion_count")
{
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
#if defined(LUAU_ENABLE_ASAN)
int limit = 250;
#elif defined(_DEBUG) || defined(_NOOPT)
@ -435,6 +441,9 @@ TEST_CASE_FIXTURE(Fixture, "check_expr_recursion_limit")
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"(
--!nonstrict
foo = true
@ -447,6 +456,8 @@ TEST_CASE_FIXTURE(Fixture, "globals")
TEST_CASE_FIXTURE(Fixture, "globals2")
{
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
--!nonstrict
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")
{
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CHECK_NOTHROW(check(R"(
--!nonstrict
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")
{
{
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
--!strict
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"(
--!strict
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")
{
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
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")
{
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
local a = foo:
)");
@ -738,7 +759,10 @@ TEST_CASE_FIXTURE(Fixture, "no_stack_overflow_from_isoptional")
std::optional<TypeId> t0 = lookupType("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(
result.errors.begin(),
@ -1075,6 +1099,8 @@ end
TEST_CASE_FIXTURE(Fixture, "cli_50041_committing_txnlog_in_apollo_client_error")
{
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
--!strict
--!nolint
@ -1150,7 +1176,11 @@ TEST_CASE_FIXTURE(Fixture, "type_infer_recursion_limit_no_ice")
)");
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")
@ -1168,7 +1198,11 @@ TEST_CASE_FIXTURE(Fixture, "type_infer_recursion_limit_normalizer")
REQUIRE_MESSAGE(!result.errors.empty(), getErrors(result));
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]));
}
@ -1187,6 +1221,9 @@ TEST_CASE_FIXTURE(Fixture, "type_infer_cache_limit_normalizer")
TEST_CASE_FIXTURE(Fixture, "follow_on_new_types_in_substitution")
{
// CLI-114134
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
local obj = {}
@ -1355,7 +1392,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "recursive_function_that_invokes_itself_with_
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")
@ -1368,7 +1408,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "recursive_function_that_invokes_itself_with_
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")
{
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
local function concat<T>(target: {T}, ...: {T} | T): {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")
{
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
function f(arg : { prop : string & number }) : never
return arg
@ -164,6 +166,8 @@ TEST_CASE_FIXTURE(Fixture, "uninhabited_table_sub_never")
TEST_CASE_FIXTURE(Fixture, "uninhabited_table_sub_anything")
{
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
function f(arg : { prop : string & number }) : boolean
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")
{
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
function f(arg: number) end
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")
{
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
function f(arg: number) return arg end
local a

View File

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

View File

@ -121,11 +121,11 @@ TEST_CASE_FIXTURE(Fixture, "type_packs_containing_never_is_itself_uninhabitable"
if (FFlag::DebugLuauDeferredConstraintResolution)
{
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_EQ("never", toString(requireType("y")));
CHECK_EQ("*error-type*", toString(requireType("z")));
CHECK("string" == toString(requireType("x")));
CHECK("never" == toString(requireType("y")));
CHECK("nil" == toString(requireType("z")));
}
else
{
@ -335,8 +335,20 @@ TEST_CASE_FIXTURE(Fixture, "math_operators_and_never")
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ("<a>(nil, a) -> boolean", toString(requireType("mul")));
if (FFlag::DebugLuauDeferredConstraintResolution)
{
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")

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_type_pack_vararg
AutocompleteTest.autocomplete_string_singletons
AutocompleteTest.string_singleton_as_table_key
AutocompleteTest.suggest_table_keys
AutocompleteTest.type_correct_suggestion_for_overloads
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.error_detailed_function_mismatch_generic_pack
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_returning_intersections
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
Negations.cofinite_strings_can_be_compared_for_equality
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.x_as_any_if_x_is_instance_elseif_x_is_table
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.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.index_on_union_of_tables_for_properties_that_is_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.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
VisitType.throw_when_limit_is_exceeded