Skip to content

Commit bb39565

Browse files
vegorov-rbxandyfriesenannieetangaatxehgoldstein
authored
Sync to upstream/release/694 (#2033)
## What's Changed? This week we have improvements in new type solver inference, performance optimizations for the new type solver as well as fixes for optimization passes in native code generation. - Fixed the order of errors returned by `Frontend::getCheckResult` with `accumulateNested` flag - Typechecker now uses `userdata` instead of `class` as the extern type name ## New Type Solver - When a string is passed to a function expecting an argument that might be a string singleton, bidirectional type inference will choose the lower bound (string literal) for that argument (Fixes #2010) - Fixed incorrect definition of `vector.lerp` (Fixes #2024) - Added error suppression in type path traversal. Without it, errors with `*error-type*` were sometimes visible (Fixes #1840) - Fixed another case of combinatorial explosion in union type normalization which could have caused a hang - Fixed a crash on out-of-bounds access during `for..in` statement typechecking ## Runtime - Fixed an assertion in native code generation in a sequence of `nil` and `boolean` stores to a local - Fixed incorrect lowering in rare cases when LuauCodegenDirectCompare was enabled ## 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: Vyacheslav Egorov <[email protected]>
1 parent 0084576 commit bb39565

38 files changed

+740
-326
lines changed

Analysis/include/Luau/Normalize.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,8 @@ enum class NormalizationResult
202202
// * G is a union of generic/free/blocked types, intersected with a normalized type
203203
struct NormalizedType
204204
{
205+
NotNull<BuiltinTypes> builtinTypes;
206+
205207
// The top part of the type.
206208
// This type is either never, unknown, or any.
207209
// If this type is not never, all the other fields are null.

Analysis/src/AutocompleteCore.cpp

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ LUAU_FASTFLAG(LuauSolverV2)
2727
LUAU_FASTINT(LuauTypeInferIterationLimit)
2828
LUAU_FASTINT(LuauTypeInferRecursionLimit)
2929
LUAU_FASTFLAGVARIABLE(DebugLuauMagicVariableNames)
30-
LUAU_FASTFLAGVARIABLE(LuauIncludeBreakContinueStatements)
3130
LUAU_FASTFLAGVARIABLE(LuauSuggestHotComments)
3231
LUAU_FASTFLAG(LuauAutocompleteAttributes)
3332

@@ -1203,7 +1202,6 @@ static bool isBindingLegalAtCurrentPosition(const Symbol& symbol, const Binding&
12031202

12041203
static bool isValidBreakContinueContext(const std::vector<AstNode*>& ancestry, Position position)
12051204
{
1206-
LUAU_ASSERT(FFlag::LuauIncludeBreakContinueStatements);
12071205
for (auto it = ancestry.rbegin(); it != ancestry.rend(); ++it)
12081206
{
12091207
if ((*it)->is<AstStatFunction>() || (*it)->is<AstStatLocalFunction>() || (*it)->is<AstExprFunction>() || (*it)->is<AstStatTypeFunction>() ||
@@ -1267,18 +1265,10 @@ static AutocompleteEntryMap autocompleteStatement(
12671265
scope = scope->parent;
12681266
}
12691267

1270-
if (FFlag::LuauIncludeBreakContinueStatements)
1268+
bool shouldIncludeBreakAndContinue = isValidBreakContinueContext(ancestry, position);
1269+
for (const std::string_view kw : kStatementStartingKeywords)
12711270
{
1272-
bool shouldIncludeBreakAndContinue = isValidBreakContinueContext(ancestry, position);
1273-
for (const std::string_view kw : kStatementStartingKeywords)
1274-
{
1275-
if ((kw != "break" && kw != "continue") || shouldIncludeBreakAndContinue)
1276-
result.emplace(kw, AutocompleteEntry{AutocompleteEntryKind::Keyword});
1277-
}
1278-
}
1279-
else
1280-
{
1281-
for (const std::string_view kw : kStatementStartingKeywords)
1271+
if ((kw != "break" && kw != "continue") || shouldIncludeBreakAndContinue)
12821272
result.emplace(kw, AutocompleteEntry{AutocompleteEntryKind::Keyword});
12831273
}
12841274

Analysis/src/ConstraintSolver.cpp

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ LUAU_FASTFLAG(DebugLuauStringSingletonBasedOnQuotes)
5252
LUAU_FASTFLAG(LuauPushTypeConstraint2)
5353
LUAU_FASTFLAGVARIABLE(LuauScopedSeenSetInLookupTableProp)
5454
LUAU_FASTFLAGVARIABLE(LuauIterableBindNotUnify)
55+
LUAU_FASTFLAGVARIABLE(LuauAvoidOverloadSelectionForFunctionType)
5556

5657
namespace Luau
5758
{
@@ -1579,10 +1580,15 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
15791580
if (FFlag::LuauTrackUniqueness && c.callSite)
15801581
findUniqueTypes(NotNull{&uniqueTypes}, c.callSite->args, NotNull{&module->astTypes});
15811582

1582-
auto [status, overload] = resolver.selectOverload(fn, argsPack, NotNull{&uniqueTypes}, /*useFreeTypeBounds*/ force);
15831583
TypeId overloadToUse = fn;
1584-
if (status == OverloadResolver::Analysis::Ok)
1585-
overloadToUse = overload;
1584+
// NOTE: This probably ends up capturing union types as well, but
1585+
// that should be fairly uncommon.
1586+
if (!FFlag::LuauAvoidOverloadSelectionForFunctionType || !is<FunctionType>(fn))
1587+
{
1588+
auto [status, overload] = resolver.selectOverload(fn, argsPack, NotNull{&uniqueTypes}, /*useFreeTypeBounds*/ force);
1589+
if (status == OverloadResolver::Analysis::Ok)
1590+
overloadToUse = overload;
1591+
}
15861592

15871593
TypeId inferredTy = arena->addType(FunctionType{TypeLevel{}, argsPack, c.result});
15881594
Unifier2 u2{NotNull{arena}, builtinTypes, constraint->scope, NotNull{&iceReporter}};

Analysis/src/EmbeddedBuiltinDefinitions.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
22
#include "Luau/BuiltinDefinitions.h"
33

4-
LUAU_FASTFLAGVARIABLE(LuauTypeCheckerVectorLerp)
4+
LUAU_FASTFLAGVARIABLE(LuauTypeCheckerVectorLerp2)
55
LUAU_FASTFLAGVARIABLE(LuauRawGetHandlesNil)
66

77
namespace Luau
@@ -339,7 +339,7 @@ declare vector: {
339339
clamp: @checked (vec: vector, min: vector, max: vector) -> vector,
340340
max: @checked (vector, ...vector) -> vector,
341341
min: @checked (vector, ...vector) -> vector,
342-
lerp: @checked (vec1: vector, vec2: vector, t: number) -> number,
342+
lerp: @checked (vec1: vector, vec2: vector, t: number) -> vector,
343343
344344
zero: vector,
345345
one: vector,
@@ -389,7 +389,7 @@ std::string getBuiltinDefinitionSource()
389389
result += kBuiltinDefinitionDebugSrc;
390390
result += kBuiltinDefinitionUtf8Src;
391391
result += kBuiltinDefinitionBufferSrc;
392-
if (FFlag::LuauTypeCheckerVectorLerp)
392+
if (FFlag::LuauTypeCheckerVectorLerp2)
393393
{
394394
result += kBuiltinDefinitionVectorSrc;
395395
}

Analysis/src/Frontend.cpp

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ LUAU_FASTFLAG(LuauEmplaceNotPushBack)
4747
LUAU_FASTFLAG(LuauExplicitSkipBoundTypes)
4848
LUAU_FASTFLAG(LuauNoConstraintGenRecursionLimitIce)
4949
LUAU_FASTFLAGVARIABLE(LuauBatchedExecuteTask)
50+
LUAU_FASTFLAGVARIABLE(LuauAccumulateErrorsInOrder)
5051

5152
namespace Luau
5253
{
@@ -286,18 +287,39 @@ ErrorVec accumulateErrors(
286287

287288
Module& module = *modulePtr;
288289

289-
std::sort(
290-
module.errors.begin(),
291-
module.errors.end(),
292-
[](const TypeError& e1, const TypeError& e2) -> bool
293-
{
294-
return e1.location.begin > e2.location.begin;
295-
}
296-
);
290+
if (FFlag::LuauAccumulateErrorsInOrder)
291+
{
292+
size_t prevSize = result.size();
293+
294+
// Append module errors in reverse order
295+
result.insert(result.end(), module.errors.rbegin(), module.errors.rend());
296+
297+
// Sort them in the reverse order as well
298+
std::stable_sort(
299+
result.begin() + prevSize,
300+
result.end(),
301+
[](const TypeError& e1, const TypeError& e2) -> bool
302+
{
303+
return e1.location.begin > e2.location.begin;
304+
}
305+
);
306+
}
307+
else
308+
{
309+
std::sort(
310+
module.errors.begin(),
311+
module.errors.end(),
312+
[](const TypeError& e1, const TypeError& e2) -> bool
313+
{
314+
return e1.location.begin > e2.location.begin;
315+
}
316+
);
297317

298-
result.insert(result.end(), module.errors.begin(), module.errors.end());
318+
result.insert(result.end(), module.errors.begin(), module.errors.end());
319+
}
299320
}
300321

322+
// Now we reverse errors from all modules and since they were inserted and sorted in reverse, it should be in order
301323
std::reverse(result.begin(), result.end());
302324

303325
return result;

Analysis/src/Normalize.cpp

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ LUAU_FASTINTVARIABLE(LuauNormalizeUnionLimit, 100)
2424
LUAU_FASTFLAG(LuauSolverV2)
2525
LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver)
2626
LUAU_FASTFLAG(LuauReduceSetTypeStackPressure)
27+
LUAU_FASTFLAGVARIABLE(LuauImproveNormalizeExternTypeCheck)
2728
LUAU_FASTFLAG(LuauPassBindableGenericsByReference)
29+
LUAU_FASTFLAGVARIABLE(LuauNormalizerUnionTyvarsTakeMaxSize)
2830

2931
namespace Luau
3032
{
@@ -144,7 +146,8 @@ bool NormalizedFunctionType::isNever() const
144146
}
145147

146148
NormalizedType::NormalizedType(NotNull<BuiltinTypes> builtinTypes)
147-
: tops(builtinTypes->neverType)
149+
: builtinTypes(builtinTypes)
150+
, tops(builtinTypes->neverType)
148151
, booleans(builtinTypes->neverType)
149152
, errors(builtinTypes->neverType)
150153
, nils(builtinTypes->neverType)
@@ -170,10 +173,21 @@ bool NormalizedType::isUnknown() const
170173
{
171174
if (auto ct = get<ExternType>(t))
172175
{
173-
if (ct->name == "class" && disj.empty())
176+
if (FFlag::LuauImproveNormalizeExternTypeCheck)
174177
{
175-
isTopExternType = true;
176-
break;
178+
if (t == builtinTypes->externType && disj.empty())
179+
{
180+
isTopExternType = true;
181+
break;
182+
}
183+
}
184+
else
185+
{
186+
if (ct->name == "userdata" && disj.empty())
187+
{
188+
isTopExternType = true;
189+
break;
190+
}
177191
}
178192
}
179193
}
@@ -1539,8 +1553,17 @@ NormalizationResult Normalizer::unionNormals(NormalizedType& here, const Normali
15391553
return NormalizationResult::True;
15401554
}
15411555

1542-
if (here.tyvars.size() * there.tyvars.size() >= size_t(FInt::LuauNormalizeUnionLimit))
1543-
return NormalizationResult::HitLimits;
1556+
if (FFlag::LuauNormalizerUnionTyvarsTakeMaxSize)
1557+
{
1558+
auto maxSize = std::max(here.tyvars.size(), there.tyvars.size());
1559+
if (maxSize * maxSize >= size_t(FInt::LuauNormalizeUnionLimit))
1560+
return NormalizationResult::HitLimits;
1561+
}
1562+
else
1563+
{
1564+
if (here.tyvars.size() * there.tyvars.size() >= size_t(FInt::LuauNormalizeUnionLimit))
1565+
return NormalizationResult::HitLimits;
1566+
}
15441567

15451568
for (auto it = there.tyvars.begin(); it != there.tyvars.end(); it++)
15461569
{

Analysis/src/Simplify.cpp

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@ LUAU_FASTFLAG(LuauSolverV2)
2121
LUAU_DYNAMIC_FASTINTVARIABLE(LuauSimplificationComplexityLimit, 8)
2222
LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeSimplificationIterationLimit, 128)
2323
LUAU_FASTFLAG(LuauRefineDistributesOverUnions)
24-
LUAU_FASTFLAGVARIABLE(LuauSimplifyAnyAndUnion)
2524
LUAU_FASTFLAG(LuauReduceSetTypeStackPressure)
2625
LUAU_FASTFLAG(LuauPushTypeConstraint2)
2726
LUAU_FASTFLAGVARIABLE(LuauMorePreciseExternTableRelation)
2827
LUAU_FASTFLAGVARIABLE(LuauSimplifyRefinementOfReadOnlyProperty)
2928
LUAU_FASTFLAGVARIABLE(LuauExternTableIndexersIntersect)
29+
LUAU_FASTFLAGVARIABLE(LuauSimplifyMoveTableProps)
3030

3131
namespace Luau
3232
{
@@ -1467,11 +1467,25 @@ std::optional<TypeId> TypeSimplifier::basicIntersect(TypeId left, TypeId right)
14671467

14681468
if (areDisjoint)
14691469
{
1470-
TableType::Props mergedProps = lt->props;
1471-
for (const auto& [name, rightProp] : rt->props)
1472-
mergedProps[name] = rightProp;
14731470

1474-
return arena->addType(TableType{mergedProps, std::nullopt, TypeLevel{}, lt->scope, TableState::Sealed});
1471+
if (FFlag::LuauSimplifyMoveTableProps)
1472+
{
1473+
TableType merged{TableState::Sealed, TypeLevel{}, lt->scope};
1474+
merged.props = lt->props;
1475+
1476+
for (const auto& [name, rightProp] : rt->props)
1477+
merged.props[name] = rightProp;
1478+
1479+
return arena->addType(std::move(merged));
1480+
}
1481+
else
1482+
{
1483+
TableType::Props mergedProps = lt->props;
1484+
for (const auto& [name, rightProp] : rt->props)
1485+
mergedProps[name] = rightProp;
1486+
1487+
return arena->addType(TableType{mergedProps, std::nullopt, TypeLevel{}, lt->scope, TableState::Sealed});
1488+
}
14751489
}
14761490
}
14771491

@@ -1527,9 +1541,9 @@ TypeId TypeSimplifier::intersect(TypeId left, TypeId right)
15271541
return right;
15281542
if (get<UnknownType>(right) && !get<ErrorType>(left))
15291543
return left;
1530-
if (FFlag::LuauSimplifyAnyAndUnion && get<AnyType>(left) && get<UnionType>(right))
1544+
if (get<AnyType>(left) && get<UnionType>(right))
15311545
return union_(builtinTypes->errorType, right);
1532-
if (FFlag::LuauSimplifyAnyAndUnion && get<UnionType>(left) && get<AnyType>(right))
1546+
if (get<UnionType>(left) && get<AnyType>(right))
15331547
return union_(builtinTypes->errorType, left);
15341548
if (get<AnyType>(left))
15351549
return arena->addType(UnionType{{right, builtinTypes->errorType}});

Analysis/src/TableLiteralInference.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "Luau/Unifier2.h"
1515

1616
LUAU_FASTFLAGVARIABLE(LuauPushTypeConstraintIntersection)
17+
LUAU_FASTFLAGVARIABLE(LuauPushTypeConstraintSingleton)
1718

1819
namespace Luau
1920
{
@@ -124,6 +125,23 @@ struct BidirectionalTypePusher
124125
if (ft && get<SingletonType>(ft->lowerBound) && fastIsSubtype(solver->builtinTypes->stringType, ft->upperBound) &&
125126
fastIsSubtype(ft->lowerBound, solver->builtinTypes->stringType))
126127
{
128+
if (FFlag::LuauPushTypeConstraintSingleton && maybeSingleton(expectedType) && maybeSingleton(ft->lowerBound))
129+
{
130+
// If we see a pattern like:
131+
//
132+
// local function foo<T>(my_enum: "foo" | "bar" | T) -> T
133+
// return my_enum
134+
// end
135+
// local var = foo("meow")
136+
//
137+
// ... where we are attempting to push a singleton onto any string
138+
// literal, and the lower bound is still a singleton, then snap
139+
// to said lower bound.
140+
emplaceType<BoundType>(asMutable(exprType), ft->lowerBound);
141+
solver->unblock(exprType, expr->location);
142+
return exprType;
143+
}
144+
127145
// if the upper bound is a subtype of the expected type, we can push the expected type in
128146
Relation upperBoundRelation = relate(ft->upperBound, expectedType);
129147
if (upperBoundRelation == Relation::Subset || upperBoundRelation == Relation::Coincident)

Analysis/src/Type.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1011,7 +1011,7 @@ BuiltinTypes::BuiltinTypes()
10111011
, threadType(arena->addType(Type{PrimitiveType{PrimitiveType::Thread}, /*persistent*/ true}))
10121012
, bufferType(arena->addType(Type{PrimitiveType{PrimitiveType::Buffer}, /*persistent*/ true}))
10131013
, functionType(arena->addType(Type{PrimitiveType{PrimitiveType::Function}, /*persistent*/ true}))
1014-
, externType(arena->addType(Type{ExternType{"class", {}, std::nullopt, std::nullopt, {}, {}, {}, {}}, /*persistent*/ true}))
1014+
, externType(arena->addType(Type{ExternType{"userdata", {}, std::nullopt, std::nullopt, {}, {}, {}, {}}, /*persistent*/ true}))
10151015
, tableType(arena->addType(Type{PrimitiveType{PrimitiveType::Table}, /*persistent*/ true}))
10161016
, emptyTableType(arena->addType(Type{TableType{TableState::Sealed, TypeLevel{}, nullptr}, /*persistent*/ true}))
10171017
, trueType(arena->addType(Type{SingletonType{BooleanSingleton{true}}, /*persistent*/ true}))

Analysis/src/TypeChecker2.cpp

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ LUAU_FASTFLAGVARIABLE(LuauRemoveGenericErrorForParams)
4848
LUAU_FASTFLAG(LuauNoConstraintGenRecursionLimitIce)
4949
LUAU_FASTFLAGVARIABLE(LuauAddErrorCaseForIncompatibleTypePacks)
5050
LUAU_FASTFLAGVARIABLE(LuauAddConditionalContextForTernary)
51-
LUAU_FASTFLAGVARIABLE(LuauCheckForInWithSubtyping)
51+
LUAU_FASTFLAGVARIABLE(LuauCheckForInWithSubtyping2)
5252
LUAU_FASTFLAGVARIABLE(LuauNoOrderingTypeFunctions)
5353
LUAU_FASTFLAG(LuauPassBindableGenericsByReference)
5454

@@ -1000,11 +1000,11 @@ void TypeChecker2::visit(AstStatForIn* forInStatement)
10001000
else
10011001
reportError(GenericError{"next() does not return enough values"}, forInStatement->values.data[0]->location);
10021002

1003-
if (FFlag::LuauCheckForInWithSubtyping)
1003+
if (FFlag::LuauCheckForInWithSubtyping2)
10041004
return;
10051005
}
10061006

1007-
if (!FFlag::LuauCheckForInWithSubtyping)
1007+
if (!FFlag::LuauCheckForInWithSubtyping2)
10081008
{
10091009
for (size_t i = 0; i < std::min(expectedVariableTypes.head.size(), variableTypes.size()); ++i)
10101010
testIsSubtype(variableTypes[i], expectedVariableTypes.head[i], forInStatement->vars.data[i]->location);
@@ -1039,7 +1039,7 @@ void TypeChecker2::visit(AstStatForIn* forInStatement)
10391039
else
10401040
reportError(CountMismatch{2, std::nullopt, firstIterationArgCount, CountMismatch::Arg}, forInStatement->values.data[0]->location);
10411041

1042-
if (FFlag::LuauCheckForInWithSubtyping)
1042+
if (FFlag::LuauCheckForInWithSubtyping2)
10431043
return;
10441044
}
10451045
else if (actualArgCount < minCount)
@@ -1049,11 +1049,11 @@ void TypeChecker2::visit(AstStatForIn* forInStatement)
10491049
else
10501050
reportError(CountMismatch{2, std::nullopt, firstIterationArgCount, CountMismatch::Arg}, forInStatement->values.data[0]->location);
10511051

1052-
if (FFlag::LuauCheckForInWithSubtyping)
1052+
if (FFlag::LuauCheckForInWithSubtyping2)
10531053
return;
10541054
}
10551055

1056-
if (FFlag::LuauCheckForInWithSubtyping)
1056+
if (FFlag::LuauCheckForInWithSubtyping2)
10571057
{
10581058
const TypeId iterFunc = follow(iterTys[0]);
10591059

@@ -3401,7 +3401,7 @@ bool TypeChecker2::testIsSubtype(TypePackId subTy, TypePackId superTy, Location
34013401

34023402
void TypeChecker2::maybeReportSubtypingError(const TypeId subTy, const TypeId superTy, const Location& location)
34033403
{
3404-
LUAU_ASSERT(FFlag::LuauCheckForInWithSubtyping);
3404+
LUAU_ASSERT(FFlag::LuauCheckForInWithSubtyping2);
34053405
switch (shouldSuppressErrors(NotNull{&normalizer}, subTy).orElse(shouldSuppressErrors(NotNull{&normalizer}, superTy)))
34063406
{
34073407
case ErrorSuppression::Suppress:
@@ -3420,7 +3420,7 @@ void TypeChecker2::maybeReportSubtypingError(const TypeId subTy, const TypeId su
34203420

34213421
void TypeChecker2::testIsSubtypeForInStat(const TypeId iterFunc, const TypeId prospectiveFunc, const AstStatForIn& forInStat)
34223422
{
3423-
LUAU_ASSERT(FFlag::LuauCheckForInWithSubtyping);
3423+
LUAU_ASSERT(FFlag::LuauCheckForInWithSubtyping2);
34243424
LUAU_ASSERT(get<FunctionType>(follow(iterFunc)));
34253425
LUAU_ASSERT(get<FunctionType>(follow(prospectiveFunc)));
34263426

@@ -3481,7 +3481,9 @@ void TypeChecker2::testIsSubtypeForInStat(const TypeId iterFunc, const TypeId pr
34813481
if (*pf == TypePath::PackField::Arguments)
34823482
{
34833483
// The first component of `forInStat.values` is the iterator function itself
3484-
Location loc = index->index >= forInStat.values.size ? iterFuncLocation : forInStat.values.data[index->index + 1]->location;
3484+
Location loc = index->index + 1 >= forInStat.values.size
3485+
? iterFuncLocation
3486+
: forInStat.values.data[index->index + 1]->location;
34853487
maybeReportSubtypingError(*subLeaf, *superLeaf, loc);
34863488
}
34873489
else if (*pf == TypePath::PackField::Returns)

0 commit comments

Comments
 (0)