Skip to content

Commit 535f925

Browse files
vegorov-rbxandyfriesenannieetangaatxehgoldstein
authored
Sync to upstream/release/701 (#2099)
Hello everyone! This week we have type inference improvements for refinements and user-defined type functions as well as new optimizations in Native Code Generation. Merged earlier, but now available in the release are implementations of [math.isnan, math.isinf and math.isfinite for Math Library](https://rfcs.luau.org/math-isnan-isfinite-isinf.html) RFC and long awaited [Explicit type parameter instantiation](https://rfcs.luau.org/explicit-type-parameter-instantiation.html) RFC! ### What's New - Fixed a bug that incorrectly allowed chained aliases to depend on aliases defined in deeper directories - Added an optional `to_alias_fallback` C API to natively support host-injected aliases - Fixed building fuzzers using make - Fixed serialization of userdata type mappings from `userdataTypes` compiler option which caused a crash on bytecode load ## Analysis - Recursion counters have been added to the non-strict mode typechecker to prevent crashes - Irreducible user-defined type functions are now marked as solved, improving consistency of type inference results - Refinements against `unknown` type now work more consistently, resulting in more accurate inference ```luau --!strict local Class = {} Class.__index = Class type Class = setmetatable<{ A: number }, typeof(Class)> function Class.Foo(x: Class, y: Class, z: Class) if y == z then return end local bar = y.A -- correctly infers number instead of any end ``` ## Runtime - Added load-store optimizations for loading components of a vector in Native Code Generation (NCG). This should reduce the need to place repeatedly accessed vector components in locals and improve vector component shuffling - NCG will exit to VM earlier when unsafe environment is detected, reducing work it has to throw away - NCG will now optimize better across interrupt callback invocations; interrupts are to observe state and error/yield, general modification of VM state is not allowed in it ## Internal Contributors Co-authored-by: Andy Friesen <[email protected]> Co-authored-by: Annie Tang <[email protected]> Co-authored-by: Ariel Weiss <[email protected]> Co-authored-by: Hunter Goldstein <[email protected]> Co-authored-by: Ilya Rezvov <[email protected]> Co-authored-by: Sora Kanosue <[email protected]> Co-authored-by: Varun Saini <[email protected]> Co-authored-by: Vyacheslav Egorov <[email protected]>
1 parent 87133b6 commit 535f925

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1277
-332
lines changed

Analysis/include/Luau/TypeFunctionRuntimeBuilder.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ struct TypeFunctionRuntimeBuilderState
3131
};
3232

3333
TypeFunctionTypeId serialize(TypeId ty, TypeFunctionRuntimeBuilderState* state);
34+
TypeFunctionTypePackId serialize(TypePackId tp, TypeFunctionRuntimeBuilderState* state);
35+
3436
TypeId deserialize(TypeFunctionTypeId ty, TypeFunctionRuntimeBuilderState* state);
37+
TypePackId deserialize(TypeFunctionTypePackId tp, TypeFunctionRuntimeBuilderState* state);
3538

3639
} // namespace Luau

Analysis/src/BuiltinTypeFunctions.cpp

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ LUAU_DYNAMIC_FASTINT(LuauTypeFamilyApplicationCartesianProductLimit)
2222
LUAU_DYNAMIC_FASTINTVARIABLE(LuauStepRefineRecursionLimit, 64)
2323
LUAU_FASTFLAG(LuauReduceSetTypeStackPressure)
2424

25-
LUAU_FASTFLAGVARIABLE(LuauRefineNoRefineAlways)
25+
LUAU_FASTFLAGVARIABLE(LuauRefineNoRefineAlways2)
2626
LUAU_FASTFLAGVARIABLE(LuauRefineDistributesOverUnions)
2727
LUAU_FASTFLAG(LuauEGFixGenericsList)
2828
LUAU_FASTFLAG(LuauNoMoreComparisonTypeFunctions)
@@ -1304,28 +1304,38 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
13041304
}
13051305

13061306
std::vector<TypeId> discriminantTypes;
1307-
for (size_t i = 1; i < typeParams.size(); i++)
1308-
discriminantTypes.push_back(follow(typeParams.at(i)));
1309-
1310-
if (FFlag::LuauRefineNoRefineAlways)
1307+
if (FFlag::LuauRefineNoRefineAlways2)
13111308
{
1312-
bool hasAnyRealRefinements = false;
1313-
for (auto discriminant : discriminantTypes)
1309+
for (size_t i = 1; i < typeParams.size(); i++)
13141310
{
1311+
auto discriminant = follow(typeParams[i]);
1312+
1313+
// Filter out any top level types that are meaningless to refine
1314+
// against.
1315+
if (is<UnknownType, NoRefineType>(discriminant))
1316+
continue;
1317+
13151318
// If the discriminant type is only:
1316-
// - The `*no-refine*` type or,
1317-
// - tables, metatables, unions, intersections, functions, or negations _containing_ `*no-refine*`.
1319+
// - The `*no-refine*` type (covered above) or;
1320+
// - tables, metatables, unions, intersections, functions, or
1321+
// negations containing `*no-refine*` (covered below).
13181322
// There's no point in refining against it.
13191323
ContainsRefinableType crt;
13201324
crt.traverse(discriminant);
13211325

1322-
hasAnyRealRefinements = hasAnyRealRefinements || crt.found;
1326+
if (crt.found)
1327+
discriminantTypes.push_back(discriminant);
13231328
}
13241329

13251330
// if we don't have any real refinements, i.e. they're all `*no-refine*`, then we can reduce immediately.
1326-
if (!hasAnyRealRefinements)
1331+
if (discriminantTypes.empty())
13271332
return {targetTy, {}};
13281333
}
1334+
else
1335+
{
1336+
for (size_t i = 1; i < typeParams.size(); i++)
1337+
discriminantTypes.push_back(follow(typeParams.at(i)));
1338+
}
13291339

13301340
const bool targetIsPending = isBlockedOrUnsolvedType(targetTy);
13311341

@@ -1385,8 +1395,8 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
13851395
}
13861396
else
13871397
{
1388-
// FFlag::LuauRefineNoRefineAlways moves this check upwards so that it runs even if the thing being refined is pending.
1389-
if (!FFlag::LuauRefineNoRefineAlways)
1398+
// FFlag::LuauRefineNoRefineAlways2 moves this check upwards so that it runs even if the thing being refined is pending.
1399+
if (!FFlag::LuauRefineNoRefineAlways2)
13901400
{
13911401
// If the discriminant type is only:
13921402
// - The `*no-refine*` type or,

Analysis/src/ConstraintSolver.cpp

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,13 @@ LUAU_FASTFLAG(LuauReduceSetTypeStackPressure)
4747
LUAU_FASTFLAG(DebugLuauStringSingletonBasedOnQuotes)
4848
LUAU_FASTFLAG(LuauExplicitTypeExpressionInstantiation)
4949
LUAU_FASTFLAG(LuauPushTypeConstraint2)
50-
LUAU_FASTFLAGVARIABLE(LuauScopedSeenSetInLookupTableProp)
5150
LUAU_FASTFLAGVARIABLE(LuauIterableBindNotUnify)
5251
LUAU_FASTFLAGVARIABLE(LuauAvoidOverloadSelectionForFunctionType)
5352
LUAU_FASTFLAG(LuauSimplifyIntersectionNoTreeSet)
5453
LUAU_FASTFLAG(LuauInstantiationUsesGenericPolarity)
5554
LUAU_FASTFLAG(LuauPushTypeConstraintLambdas2)
5655
LUAU_FASTFLAGVARIABLE(LuauPushTypeConstriantAlwaysCompletes)
56+
LUAU_FASTFLAG(LuauMarkUnscopedGenericsAsSolved)
5757

5858
namespace Luau
5959
{
@@ -2672,7 +2672,11 @@ bool ConstraintSolver::tryDispatch(const ReduceConstraint& c, NotNull<const Cons
26722672
unblock(r, constraint->location);
26732673

26742674
for (TypeId ity : result.irreducibleTypes)
2675+
{
26752676
uninhabitedTypeFunctions.insert(ity);
2677+
if (FFlag::LuauMarkUnscopedGenericsAsSolved)
2678+
unblock(ity, constraint->location);
2679+
}
26762680

26772681
bool reductionFinished = result.blockedTypes.empty() && result.blockedPacks.empty();
26782682

@@ -3228,12 +3232,7 @@ TablePropLookupResult ConstraintSolver::lookupTableProp(
32283232
if (seen.contains(subjectType))
32293233
return {};
32303234

3231-
std::optional<ScopedSeenSet<Set<TypeId>, TypeId>> ss; // This won't be needed once LuauScopedSeenSetInLookupTableProp is clipped.
3232-
3233-
if (FFlag::LuauScopedSeenSetInLookupTableProp)
3234-
ss.emplace(seen, subjectType);
3235-
else
3236-
seen.insert(subjectType);
3235+
ScopedSeenSet<Set<TypeId>, TypeId> ss{seen, subjectType};
32373236

32383237
subjectType = follow(subjectType);
32393238

Analysis/src/EqSatSimplification.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ Id toId(
365365
return res;
366366
};
367367

368-
if (auto tt = get<TableType>(ty))
368+
if (get<TableType>(ty))
369369
return egraph.add(TImportedTable{ty});
370370
else if (get<MetatableType>(ty))
371371
return egraph.add(TOpaque{ty});

Analysis/src/NonStrictTypeChecker.cpp

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,28 @@
44
#include "Luau/Ast.h"
55
#include "Luau/AstQuery.h"
66
#include "Luau/Common.h"
7+
#include "Luau/Def.h"
8+
#include "Luau/Error.h"
9+
#include "Luau/Normalize.h"
10+
#include "Luau/RecursionCounter.h"
711
#include "Luau/Simplify.h"
8-
#include "Luau/Type.h"
912
#include "Luau/Subtyping.h"
10-
#include "Luau/Normalize.h"
11-
#include "Luau/Error.h"
1213
#include "Luau/TimeTrace.h"
14+
#include "Luau/ToString.h"
15+
#include "Luau/Type.h"
1316
#include "Luau/TypeArena.h"
1417
#include "Luau/TypeFunction.h"
15-
#include "Luau/Def.h"
16-
#include "Luau/ToString.h"
1718
#include "Luau/TypeUtils.h"
1819

1920
#include <iterator>
2021

2122
LUAU_FASTFLAG(DebugLuauMagicTypes)
2223

24+
LUAU_FASTINTVARIABLE(LuauNonStrictTypeCheckerRecursionLimit, 300)
2325
LUAU_FASTFLAG(LuauEmplaceNotPushBack)
2426
LUAU_FASTFLAGVARIABLE(LuauUnreducedTypeFunctionsDontTriggerWarnings)
2527
LUAU_FASTFLAGVARIABLE(LuauNonStrictFetchScopeOnce)
28+
LUAU_FASTFLAGVARIABLE(LuauAddRecursionCounterToNonStrictTypeChecker)
2629

2730
namespace Luau
2831
{
@@ -318,6 +321,14 @@ struct NonStrictTypeChecker
318321

319322
NonStrictContext visit(AstStatBlock* block)
320323
{
324+
std::optional<RecursionCounter> _rc;
325+
if (FFlag::LuauAddRecursionCounterToNonStrictTypeChecker)
326+
{
327+
_rc.emplace(&nonStrictRecursionCount);
328+
if (FInt::LuauNonStrictTypeCheckerRecursionLimit > 0 && nonStrictRecursionCount >= FInt::LuauNonStrictTypeCheckerRecursionLimit)
329+
return {};
330+
}
331+
321332
auto StackPusher = pushStack(block);
322333
NonStrictContext ctx;
323334

@@ -511,6 +522,14 @@ struct NonStrictTypeChecker
511522

512523
NonStrictContext visit(AstExpr* expr, ValueContext context)
513524
{
525+
std::optional<RecursionCounter> _rc;
526+
if (FFlag::LuauAddRecursionCounterToNonStrictTypeChecker)
527+
{
528+
_rc.emplace(&nonStrictRecursionCount);
529+
if (FInt::LuauNonStrictTypeCheckerRecursionLimit > 0 && nonStrictRecursionCount >= FInt::LuauNonStrictTypeCheckerRecursionLimit)
530+
return {};
531+
}
532+
514533
auto pusher = pushStack(expr);
515534
if (auto e = expr->as<AstExprGroup>())
516535
return visit(e, context);
@@ -809,6 +828,14 @@ struct NonStrictTypeChecker
809828

810829
NonStrictContext visit(AstExprTable* table)
811830
{
831+
std::optional<RecursionCounter> _rc;
832+
if (FFlag::LuauAddRecursionCounterToNonStrictTypeChecker)
833+
{
834+
_rc.emplace(&nonStrictRecursionCount);
835+
if (FInt::LuauNonStrictTypeCheckerRecursionLimit > 0 && nonStrictRecursionCount >= FInt::LuauNonStrictTypeCheckerRecursionLimit)
836+
return {};
837+
}
838+
812839
for (auto [_, key, value] : table->items)
813840
{
814841
if (key)
@@ -1291,6 +1318,8 @@ struct NonStrictTypeChecker
12911318
}
12921319

12931320
private:
1321+
int nonStrictRecursionCount = 0;
1322+
12941323
TypeId getOrCreateNegation(TypeId baseType)
12951324
{
12961325
TypeId& cachedResult = cachedNegations[baseType];
@@ -1326,7 +1355,7 @@ void checkNonStrict(
13261355
typeChecker.visit(sourceModule.root);
13271356
unfreeze(module->interfaceTypes);
13281357
copyErrors(module->errors, module->interfaceTypes, builtinTypes);
1329-
1358+
13301359
module->errors.erase(
13311360
std::remove_if(
13321361
module->errors.begin(),

Analysis/src/Subtyping.cpp

Lines changed: 15 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ LUAU_FASTINTVARIABLE(LuauSubtypingReasoningLimit, 100)
2727
LUAU_FASTFLAG(LuauEmplaceNotPushBack)
2828
LUAU_FASTFLAGVARIABLE(LuauSubtypingReportGenericBoundMismatches2)
2929
LUAU_FASTFLAGVARIABLE(LuauTrackUniqueness)
30-
LUAU_FASTFLAGVARIABLE(LuauSubtypingUnionsAndIntersectionsInGenericBounds)
3130
LUAU_FASTFLAGVARIABLE(LuauIndexInMetatableSubtyping)
3231
LUAU_FASTFLAGVARIABLE(LuauSubtypingPackRecursionLimits)
3332
LUAU_FASTFLAGVARIABLE(LuauSubtypingPrimitiveAndGenericTableTypes)
@@ -865,23 +864,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
865864

866865
SubtypingResult result;
867866

868-
if (auto subUnion = get<UnionType>(subTy); subUnion && !FFlag::LuauSubtypingUnionsAndIntersectionsInGenericBounds)
869-
result = isCovariantWith(env, subUnion, superTy, scope);
870-
else if (auto superUnion = get<UnionType>(superTy); superUnion && !FFlag::LuauSubtypingUnionsAndIntersectionsInGenericBounds)
871-
{
872-
result = isCovariantWith(env, subTy, superUnion, scope);
873-
if (!result.isSubtype && !result.normalizationTooComplex)
874-
result = trySemanticSubtyping(env, subTy, superTy, scope, result);
875-
}
876-
else if (auto superIntersection = get<IntersectionType>(superTy); superIntersection && !FFlag::LuauSubtypingUnionsAndIntersectionsInGenericBounds)
877-
result = isCovariantWith(env, subTy, superIntersection, scope);
878-
else if (auto subIntersection = get<IntersectionType>(subTy); subIntersection && !FFlag::LuauSubtypingUnionsAndIntersectionsInGenericBounds)
879-
{
880-
result = isCovariantWith(env, subIntersection, superTy, scope);
881-
if (!result.isSubtype && !result.normalizationTooComplex)
882-
result = trySemanticSubtyping(env, subTy, superTy, scope, result);
883-
}
884-
else if (get<AnyType>(superTy))
867+
if (get<AnyType>(superTy))
885868
result = {true};
886869

887870
// We have added this as an exception - the set of inhabitants of any is exactly the set of inhabitants of unknown (since error has no
@@ -895,8 +878,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
895878
result =
896879
isCovariantWith(env, builtinTypes->unknownType, superTy, scope).andAlso(isCovariantWith(env, builtinTypes->errorType, superTy, scope));
897880
}
898-
else if (get<UnknownType>(superTy) && // flag delays recursing into unions and inters, so only handle this case if subTy isn't a union or inter
899-
(FFlag::LuauSubtypingUnionsAndIntersectionsInGenericBounds ? !get<UnionType>(subTy) && !get<IntersectionType>(subTy) : true))
881+
else if (get<UnknownType>(superTy) && !get<UnionType>(subTy) && !get<IntersectionType>(subTy))
900882
{
901883
LUAU_ASSERT(!get<AnyType>(subTy)); // TODO: replace with ice.
902884
LUAU_ASSERT(!get<UnionType>(subTy)); // TODO: replace with ice.
@@ -951,25 +933,17 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
951933
}
952934
}
953935
else if (auto subUnion = get<UnionType>(subTy))
954-
{
955-
LUAU_ASSERT(FFlag::LuauSubtypingUnionsAndIntersectionsInGenericBounds);
956936
result = isCovariantWith(env, subUnion, superTy, scope);
957-
}
958937
else if (auto superUnion = get<UnionType>(superTy))
959938
{
960-
LUAU_ASSERT(FFlag::LuauSubtypingUnionsAndIntersectionsInGenericBounds);
961939
result = isCovariantWith(env, subTy, superUnion, scope);
962940
if (!result.isSubtype && !result.normalizationTooComplex)
963941
result = trySemanticSubtyping(env, subTy, superTy, scope, result);
964942
}
965943
else if (auto superIntersection = get<IntersectionType>(superTy))
966-
{
967-
LUAU_ASSERT(FFlag::LuauSubtypingUnionsAndIntersectionsInGenericBounds);
968944
result = isCovariantWith(env, subTy, superIntersection, scope);
969-
}
970945
else if (auto subIntersection = get<IntersectionType>(subTy))
971946
{
972-
LUAU_ASSERT(FFlag::LuauSubtypingUnionsAndIntersectionsInGenericBounds);
973947
result = isCovariantWith(env, subIntersection, superTy, scope);
974948
if (!result.isSubtype && !result.normalizationTooComplex)
975949
result = trySemanticSubtyping(env, subTy, superTy, scope, result);
@@ -2797,26 +2771,21 @@ SubtypingResult Subtyping::checkGenericBounds(
27972771
result.genericBoundsMismatches.emplace_back(genericName, bounds.lowerBound, bounds.upperBound);
27982772
else if (!boundsResult.isSubtype)
27992773
{
2800-
if (FFlag::LuauSubtypingUnionsAndIntersectionsInGenericBounds)
2774+
// Check if the bounds are error suppressing before reporting a mismatch
2775+
switch (shouldSuppressErrors(normalizer, lowerBound).orElse(shouldSuppressErrors(normalizer, upperBound)))
28012776
{
2802-
// Check if the bounds are error suppressing before reporting a mismatch
2803-
switch (shouldSuppressErrors(normalizer, lowerBound).orElse(shouldSuppressErrors(normalizer, upperBound)))
2804-
{
2805-
case ErrorSuppression::Suppress:
2806-
break;
2807-
case ErrorSuppression::NormalizationFailed:
2808-
// intentionally fallthrough here since we couldn't prove this was error-suppressing
2809-
[[fallthrough]];
2810-
case ErrorSuppression::DoNotSuppress:
2811-
result.genericBoundsMismatches.emplace_back(genericName, bounds.lowerBound, bounds.upperBound);
2812-
break;
2813-
default:
2814-
LUAU_ASSERT(0);
2815-
break;
2816-
}
2817-
}
2818-
else
2777+
case ErrorSuppression::Suppress:
2778+
break;
2779+
case ErrorSuppression::NormalizationFailed:
2780+
// intentionally fallthrough here since we couldn't prove this was error-suppressing
2781+
[[fallthrough]];
2782+
case ErrorSuppression::DoNotSuppress:
28192783
result.genericBoundsMismatches.emplace_back(genericName, bounds.lowerBound, bounds.upperBound);
2784+
break;
2785+
default:
2786+
LUAU_ASSERT(0);
2787+
break;
2788+
}
28202789
}
28212790

28222791
result.andAlso(boundsResult);

Analysis/src/TxnLog.cpp

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99
#include <algorithm>
1010
#include <stdexcept>
1111

12-
LUAU_FASTFLAGVARIABLE(LuauOccursCheckInCommit)
13-
1412
namespace Luau
1513
{
1614

@@ -228,12 +226,7 @@ void TxnLog::commit()
228226
{
229227
const TypeId unfollowed = &rep.get()->pending;
230228

231-
if (FFlag::LuauOccursCheckInCommit)
232-
{
233-
if (!occurs(*this, unfollowed, ty))
234-
asMutable(ty)->reassign(*unfollowed);
235-
}
236-
else
229+
if (!occurs(*this, unfollowed, ty))
237230
asMutable(ty)->reassign(*unfollowed);
238231
}
239232
}

Analysis/src/TypeChecker2.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ LUAU_FASTFLAGVARIABLE(LuauNewOverloadResolver)
4545
LUAU_FASTFLAG(LuauPassBindableGenericsByReference)
4646
LUAU_FASTFLAG(LuauSimplifyIntersectionNoTreeSet)
4747
LUAU_FASTFLAG(LuauAddRefinementToAssertions)
48-
LUAU_FASTFLAGVARIABLE(LuauNoCustomHandlingOfReasonsingsForForIn)
4948
LUAU_FASTFLAGVARIABLE(LuauSuppressIndexingIntoError)
5049

5150
namespace Luau
@@ -550,7 +549,7 @@ TypeId TypeChecker2::lookupAnnotation(AstType* annotation)
550549
{
551550
if (auto ann = ref->parameters.data[0].type)
552551
{
553-
TypeId argTy = lookupAnnotation(ref->parameters.data[0].type);
552+
TypeId argTy = lookupAnnotation(ann);
554553
luauPrintLine(
555554
format("_luau_print (%d, %d): %s\n", annotation->location.begin.line, annotation->location.begin.column, toString(argTy).c_str())
556555
);

Analysis/src/TypeFunction.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ LUAU_FASTFLAG(DebugLuauEqSatSimplification)
3535

3636
LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies)
3737
LUAU_FASTFLAGVARIABLE(LuauEnqueueUnionsOfDistributedTypeFunctions)
38+
LUAU_FASTFLAGVARIABLE(LuauMarkUnscopedGenericsAsSolved)
3839

3940
namespace Luau
4041
{
@@ -586,6 +587,12 @@ struct TypeFunctionReducer
586587
// Let the caller know this type will not become reducible
587588
result.irreducibleTypes.insert(subject);
588589

590+
if (FFlag::LuauMarkUnscopedGenericsAsSolved)
591+
{
592+
if (getState(subject) == TypeFunctionInstanceState::Unsolved)
593+
setState(subject, TypeFunctionInstanceState::Solved);
594+
}
595+
589596
if (FFlag::DebugLuauLogTypeFamilies)
590597
printf("Irreducible due to an unscoped generic type\n");
591598

0 commit comments

Comments
 (0)