Skip to content

Commit 78b7bce

Browse files
Nathan Hawesrjmccall
authored andcommitted
[IDE][Refactoring] Update syntactic rename to support braceless multiple trailing closures.
1 parent 58859f5 commit 78b7bce

18 files changed

+178
-83
lines changed

include/swift/IDE/Utils.h

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ struct ResolvedLoc {
222222
ASTWalker::ParentTy Node;
223223
CharSourceRange Range;
224224
std::vector<CharSourceRange> LabelRanges;
225+
Optional<unsigned> FirstTrailingLabel;
225226
LabelRangeType LabelType;
226227
bool IsActive;
227228
bool IsInSelector;
@@ -268,7 +269,8 @@ class NameMatcher: public ASTWalker {
268269
bool tryResolve(ASTWalker::ParentTy Node, SourceLoc NameLoc);
269270
bool tryResolve(ASTWalker::ParentTy Node, DeclNameLoc NameLoc, Expr *Arg);
270271
bool tryResolve(ASTWalker::ParentTy Node, SourceLoc NameLoc, LabelRangeType RangeType,
271-
ArrayRef<CharSourceRange> LabelLocs);
272+
ArrayRef<CharSourceRange> LabelLocs,
273+
Optional<unsigned> FirstTrailingLabel);
272274
bool handleCustomAttrs(Decl *D);
273275
Expr *getApplicableArgFor(Expr* E);
274276

@@ -579,10 +581,10 @@ struct CallArgInfo {
579581
std::vector<CallArgInfo>
580582
getCallArgInfo(SourceManager &SM, Expr *Arg, LabelRangeEndAt EndKind);
581583

582-
// Get the ranges of argument labels from an Arg, either tuple or paren.
583-
// This includes empty ranges for any unlabelled arguments, and excludes
584-
// trailing closures.
585-
std::vector<CharSourceRange>
584+
// Get the ranges of argument labels from an Arg, either tuple or paren, and
585+
// the index of the first trailing closure argument, if any. This includes empty
586+
// ranges for any unlabelled arguments, including the first trailing closure.
587+
std::pair<std::vector<CharSourceRange>, Optional<unsigned>>
586588
getCallArgLabelRanges(SourceManager &SM, Expr *Arg, LabelRangeEndAt EndKind);
587589

588590
/// Whether a decl is defined from clang source.

lib/IDE/Refactoring.cpp

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,12 @@ class Renamer {
108108
/// Adds replacements to rename the given label ranges
109109
/// \return true if the label ranges do not match the old name
110110
bool renameLabels(ArrayRef<CharSourceRange> LabelRanges,
111+
Optional<unsigned> FirstTrailingLabel,
111112
LabelRangeType RangeType, bool isCallSite) {
112113
if (isCallSite)
113-
return renameLabelsLenient(LabelRanges, RangeType);
114+
return renameLabelsLenient(LabelRanges, FirstTrailingLabel, RangeType);
114115

116+
assert(!FirstTrailingLabel);
115117
ArrayRef<StringRef> OldLabels = Old.args();
116118

117119
if (OldLabels.size() != LabelRanges.size())
@@ -261,10 +263,56 @@ class Renamer {
261263
}
262264

263265
bool renameLabelsLenient(ArrayRef<CharSourceRange> LabelRanges,
266+
Optional<unsigned> FirstTrailingLabel,
264267
LabelRangeType RangeType) {
265268

266269
ArrayRef<StringRef> OldNames = Old.args();
267270

271+
// First, match trailing closure arguments in reverse
272+
if (FirstTrailingLabel) {
273+
auto TrailingLabels = LabelRanges.drop_front(*FirstTrailingLabel);
274+
LabelRanges = LabelRanges.take_front(*FirstTrailingLabel);
275+
276+
for (auto LabelIndex: llvm::reverse(indices(TrailingLabels))) {
277+
CharSourceRange Label = TrailingLabels[LabelIndex];
278+
279+
if (Label.getByteLength()) {
280+
if (OldNames.empty())
281+
return true;
282+
283+
while (!labelRangeMatches(Label, LabelRangeType::Selector,
284+
OldNames.back())) {
285+
if ((OldNames = OldNames.drop_back()).empty())
286+
return true;
287+
}
288+
splitAndRenameLabel(Label, LabelRangeType::Selector,
289+
OldNames.size() - 1);
290+
OldNames = OldNames.drop_back();
291+
continue;
292+
}
293+
294+
// empty labelled trailing closure label
295+
if (LabelIndex) {
296+
if (OldNames.empty())
297+
return true;
298+
299+
while (!OldNames.back().empty()) {
300+
if ((OldNames = OldNames.drop_back()).empty())
301+
return true;
302+
}
303+
splitAndRenameLabel(Label, LabelRangeType::Selector,
304+
OldNames.size() - 1);
305+
OldNames = OldNames.drop_back();
306+
continue;
307+
}
308+
309+
// unlabelled trailing closure label
310+
OldNames = OldNames.drop_back();
311+
continue;
312+
}
313+
}
314+
315+
// Next, match the non-trailing arguments.
268316
size_t NameIndex = 0;
269317

270318
for (CharSourceRange Label : LabelRanges) {
@@ -399,7 +447,8 @@ class Renamer {
399447
(Config.Usage != NameUsage::Reference || IsSubscript) &&
400448
Resolved.LabelType == LabelRangeType::CallArg;
401449

402-
if (renameLabels(Resolved.LabelRanges, Resolved.LabelType, isCallSite))
450+
if (renameLabels(Resolved.LabelRanges, Resolved.FirstTrailingLabel,
451+
Resolved.LabelType, isCallSite))
403452
return Config.Usage == NameUsage::Unknown ?
404453
RegionType::Unmatched : RegionType::Mismatch;
405454
}

lib/IDE/SwiftSourceDocInfo.cpp

Lines changed: 42 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ std::vector<ResolvedLoc> NameMatcher::resolve(ArrayRef<UnresolvedLoc> Locs, Arra
102102

103103
// handle any unresolved locs past the end of the last AST node or comment
104104
std::vector<ResolvedLoc> Remaining(Locs.size() - ResolvedLocs.size(), {
105-
ASTWalker::ParentTy(), CharSourceRange(), {}, LabelRangeType::None,
105+
ASTWalker::ParentTy(), CharSourceRange(), {}, None, LabelRangeType::None,
106106
/*isActice*/true, /*isInSelector*/false});
107107
ResolvedLocs.insert(ResolvedLocs.end(), Remaining.begin(), Remaining.end());
108108

@@ -238,15 +238,15 @@ bool NameMatcher::walkToDeclPre(Decl *D) {
238238
LabelRanges = getLabelRanges(ParamList, getSourceMgr());
239239
}
240240
tryResolve(ASTWalker::ParentTy(D), D->getLoc(), LabelRangeType::Param,
241-
LabelRanges);
241+
LabelRanges, None);
242242
} else if (SubscriptDecl *SD = dyn_cast<SubscriptDecl>(D)) {
243243
tryResolve(ASTWalker::ParentTy(D), D->getLoc(), LabelRangeType::NoncollapsibleParam,
244-
getLabelRanges(SD->getIndices(), getSourceMgr()));
244+
getLabelRanges(SD->getIndices(), getSourceMgr()), None);
245245
} else if (EnumElementDecl *EED = dyn_cast<EnumElementDecl>(D)) {
246246
if (auto *ParamList = EED->getParameterList()) {
247247
auto LabelRanges = getEnumParamListInfo(getSourceMgr(), ParamList);
248248
tryResolve(ASTWalker::ParentTy(D), D->getLoc(), LabelRangeType::CallArg,
249-
LabelRanges);
249+
LabelRanges, None);
250250
} else {
251251
tryResolve(ASTWalker::ParentTy(D), D->getLoc());
252252
}
@@ -362,7 +362,8 @@ std::pair<bool, Expr*> NameMatcher::walkToExprPre(Expr *E) {
362362

363363
auto Labels = getCallArgLabelRanges(getSourceMgr(), SubExpr->getIndex(),
364364
LabelRangeEndAt::BeforeElemStart);
365-
tryResolve(ASTWalker::ParentTy(E), E->getLoc(), LabelRangeType::CallArg, Labels);
365+
tryResolve(ASTWalker::ParentTy(E), E->getLoc(), LabelRangeType::CallArg,
366+
Labels.first, Labels.second);
366367
if (isDone())
367368
break;
368369
if (!SubExpr->getIndex()->walk(*this))
@@ -379,7 +380,7 @@ std::pair<bool, Expr*> NameMatcher::walkToExprPre(Expr *E) {
379380
auto Labels = getCallArgLabelRanges(getSourceMgr(), P,
380381
LabelRangeEndAt::BeforeElemStart);
381382
tryResolve(ASTWalker::ParentTy(E), P->getLParenLoc(),
382-
LabelRangeType::CallArg, Labels);
383+
LabelRangeType::CallArg, Labels.first, Labels.second);
383384
break;
384385
}
385386
case ExprKind::Tuple: {
@@ -388,7 +389,7 @@ std::pair<bool, Expr*> NameMatcher::walkToExprPre(Expr *E) {
388389
auto Labels = getCallArgLabelRanges(getSourceMgr(), T,
389390
LabelRangeEndAt::BeforeElemStart);
390391
tryResolve(ASTWalker::ParentTy(E), T->getLParenLoc(),
391-
LabelRangeType::CallArg, Labels);
392+
LabelRangeType::CallArg, Labels.first, Labels.second);
392393
if (isDone())
393394
break;
394395

@@ -485,8 +486,10 @@ bool NameMatcher::walkToTypeReprPre(TypeRepr *T) {
485486
// If we're walking a CustomAttr's type we may have an associated call
486487
// argument to resolve with from its semantic initializer.
487488
if (CustomAttrArg.hasValue() && CustomAttrArg->Loc == T->getLoc()) {
489+
auto Labels = getCallArgLabelRanges(getSourceMgr(), CustomAttrArg->Item,
490+
LabelRangeEndAt::BeforeElemStart);
488491
tryResolve(ASTWalker::ParentTy(T), T->getLoc(), LabelRangeType::CallArg,
489-
getCallArgLabelRanges(getSourceMgr(), CustomAttrArg->Item, LabelRangeEndAt::BeforeElemStart));
492+
Labels.first, Labels.second);
490493
} else {
491494
tryResolve(ASTWalker::ParentTy(T), T->getLoc());
492495
}
@@ -527,7 +530,7 @@ void NameMatcher::skipLocsBefore(SourceLoc Start) {
527530
if (!checkComments()) {
528531
LocsToResolve.pop_back();
529532
ResolvedLocs.push_back({ASTWalker::ParentTy(), CharSourceRange(), {},
530-
LabelRangeType::None, isActive(), isInSelector()});
533+
None, LabelRangeType::None, isActive(), isInSelector()});
531534
}
532535
}
533536
}
@@ -592,7 +595,7 @@ bool NameMatcher::tryResolve(ASTWalker::ParentTy Node, DeclNameLoc NameLoc,
592595
if (NameLoc.isCompound()) {
593596
auto Labels = getSelectorLabelRanges(getSourceMgr(), NameLoc);
594597
bool Resolved = tryResolve(Node, NameLoc.getBaseNameLoc(),
595-
LabelRangeType::Selector, Labels);
598+
LabelRangeType::Selector, Labels, None);
596599
if (!isDone()) {
597600
for (auto Label: Labels) {
598601
if (tryResolve(Node, Label.getStart())) {
@@ -606,23 +609,26 @@ bool NameMatcher::tryResolve(ASTWalker::ParentTy Node, DeclNameLoc NameLoc,
606609
}
607610

608611
if (LocsToResolve.back().ResolveArgLocs) {
609-
if (Arg)
612+
if (Arg) {
613+
auto Labels = getCallArgLabelRanges(getSourceMgr(), Arg,
614+
LabelRangeEndAt::BeforeElemStart);
610615
return tryResolve(Node, NameLoc.getBaseNameLoc(), LabelRangeType::CallArg,
611-
getCallArgLabelRanges(getSourceMgr(), Arg,
612-
LabelRangeEndAt::BeforeElemStart));
616+
Labels.first, Labels.second);
617+
}
613618
}
614619

615620
return tryResolve(Node, NameLoc.getBaseNameLoc());
616621
}
617622

618623
bool NameMatcher::tryResolve(ASTWalker::ParentTy Node, SourceLoc NameLoc) {
619624
assert(!isDone());
620-
return tryResolve(Node, NameLoc, LabelRangeType::None, None);
625+
return tryResolve(Node, NameLoc, LabelRangeType::None, None, None);
621626
}
622627

623628
bool NameMatcher::tryResolve(ASTWalker::ParentTy Node, SourceLoc NameLoc,
624629
LabelRangeType RangeType,
625-
ArrayRef<CharSourceRange> LabelRanges) {
630+
ArrayRef<CharSourceRange> LabelRanges,
631+
Optional<unsigned> FirstTrailingLabel) {
626632
skipLocsBefore(NameLoc);
627633
if (isDone())
628634
return false;
@@ -634,8 +640,8 @@ bool NameMatcher::tryResolve(ASTWalker::ParentTy Node, SourceLoc NameLoc,
634640
if (Range.isValid()) {
635641
if (NameLoc == Next.Loc) {
636642
LocsToResolve.pop_back();
637-
ResolvedLocs.push_back({Node, Range, LabelRanges, RangeType,
638-
isActive(), isInSelector()});
643+
ResolvedLocs.push_back({Node, Range, LabelRanges, FirstTrailingLabel,
644+
RangeType, isActive(), isInSelector()});
639645
if (isDone())
640646
return true;
641647
WasResolved = true;
@@ -649,7 +655,7 @@ bool NameMatcher::tryResolve(ASTWalker::ParentTy Node, SourceLoc NameLoc,
649655
Range.getByteLength() - 1);
650656
if (NewRange.getStart() == Next.Loc) {
651657
LocsToResolve.pop_back();
652-
ResolvedLocs.push_back({Node, NewRange, {}, LabelRangeType::None,
658+
ResolvedLocs.push_back({Node, NewRange, {}, None, LabelRangeType::None,
653659
isActive(), isInSelector()});
654660
WasResolved = true;
655661
}
@@ -881,6 +887,7 @@ std::vector<CallArgInfo> swift::ide::
881887
getCallArgInfo(SourceManager &SM, Expr *Arg, LabelRangeEndAt EndKind) {
882888
std::vector<CallArgInfo> InfoVec;
883889
if (auto *TE = dyn_cast<TupleExpr>(Arg)) {
890+
auto FirstTrailing = TE->getUnlabeledTrailingClosureIndexOfPackedArgument();
884891
for (size_t ElemIndex: range(TE->getNumElements())) {
885892
Expr *Elem = TE->getElement(ElemIndex);
886893
if (isa<DefaultArgumentExpr>(Elem))
@@ -889,14 +896,14 @@ getCallArgInfo(SourceManager &SM, Expr *Arg, LabelRangeEndAt EndKind) {
889896
SourceLoc LabelStart(Elem->getStartLoc());
890897
SourceLoc LabelEnd(LabelStart);
891898

892-
auto NameIdentifier = TE->getElementName(ElemIndex);
893-
if (!NameIdentifier.empty()) {
894-
LabelStart = TE->getElementNameLoc(ElemIndex);
895-
if (EndKind == LabelRangeEndAt::LabelNameOnly)
896-
LabelEnd = LabelStart.getAdvancedLoc(NameIdentifier.getLength());
899+
bool IsTrailingClosure = FirstTrailing && ElemIndex >= *FirstTrailing;
900+
SourceLoc NameLoc = TE->getElementNameLoc(ElemIndex);
901+
if (NameLoc.isValid()) {
902+
LabelStart = NameLoc;
903+
if (EndKind == LabelRangeEndAt::LabelNameOnly || IsTrailingClosure) {
904+
LabelEnd = Lexer::getLocForEndOfToken(SM, NameLoc);
905+
}
897906
}
898-
bool IsTrailingClosure = TE->hasTrailingClosure() &&
899-
ElemIndex == TE->getNumElements() - 1;
900907
InfoVec.push_back({getSingleNonImplicitChild(Elem),
901908
CharSourceRange(SM, LabelStart, LabelEnd), IsTrailingClosure});
902909
}
@@ -911,17 +918,19 @@ getCallArgInfo(SourceManager &SM, Expr *Arg, LabelRangeEndAt EndKind) {
911918
return InfoVec;
912919
}
913920

914-
std::vector<CharSourceRange> swift::ide::
921+
std::pair<std::vector<CharSourceRange>, Optional<unsigned>> swift::ide::
915922
getCallArgLabelRanges(SourceManager &SM, Expr *Arg, LabelRangeEndAt EndKind) {
916923
std::vector<CharSourceRange> Ranges;
917924
auto InfoVec = getCallArgInfo(SM, Arg, EndKind);
918925

919-
auto EndWithoutTrailing = std::remove_if(InfoVec.begin(), InfoVec.end(),
920-
[](CallArgInfo &Info) {
921-
return Info.IsTrailingClosure;
922-
});
923-
std::transform(InfoVec.begin(), EndWithoutTrailing,
924-
std::back_inserter(Ranges),
926+
Optional<unsigned> FirstTrailing;
927+
auto I = std::find_if(InfoVec.begin(), InfoVec.end(), [](CallArgInfo &Info) {
928+
return Info.IsTrailingClosure;
929+
});
930+
if (I != InfoVec.end())
931+
FirstTrailing = std::distance(InfoVec.begin(), I);
932+
933+
std::transform(InfoVec.begin(), InfoVec.end(), std::back_inserter(Ranges),
925934
[](CallArgInfo &Info) { return Info.LabelRange; });
926-
return Ranges;
935+
return {Ranges, FirstTrailing};
927936
}

lib/Migrator/APIDiffMigratorPass.cpp

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -505,11 +505,16 @@ struct APIDiffMigratorPass : public ASTMigratorPass, public SourceEntityWalker {
505505
auto Ranges = getCallArgLabelRanges(SM, Arg,
506506
LabelRangeEndAt::LabelNameOnly);
507507
llvm::SmallVector<uint8_t, 2> ToRemoveIndices;
508-
for (unsigned I = 0; I < Ranges.size(); I ++) {
508+
for (unsigned I = 0; I < Ranges.first.size(); I ++) {
509509
if (std::any_of(IgnoreArgIndex.begin(), IgnoreArgIndex.end(),
510510
[I](unsigned Ig) { return Ig == I; }))
511511
continue;
512-
auto LR = Ranges[I];
512+
513+
// Ignore the first trailing closure label
514+
if (Ranges.second && I == Ranges.second)
515+
continue;
516+
517+
auto LR = Ranges.first[I];
513518
if (Idx < NewName.argSize()) {
514519
auto Label = NewName.args()[Idx++];
515520

@@ -528,7 +533,7 @@ struct APIDiffMigratorPass : public ASTMigratorPass, public SourceEntityWalker {
528533
auto Ranges = getCallArgLabelRanges(SM, Arg,
529534
LabelRangeEndAt::BeforeElemStart);
530535
for (auto I : ToRemoveIndices) {
531-
Editor.remove(Ranges[I]);
536+
Editor.remove(Ranges.first[I]);
532537
}
533538
}
534539
}

test/refactoring/SyntacticRename/FindRangeOutputs/callsites/defaults.swift.expected

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,11 @@ func /*trailing:def*/withTrailingClosure(x: Int, y: () -> Int) {}
2121
// valid
2222
/*trailing:call*/withTrailingClosure(x: 2, y: { return 1})
2323
/*trailing:call*/withTrailingClosure(x: 2) { return 1}
24-
25-
// false positives
26-
/*trailing:call*/withTrailingClosure(x: 1, y: 2) { return 1}
27-
/*trailing:call*/withTrailingClosure(x: 1, y: 2) { return 1}
2824
/*trailing:call*/withTrailingClosure(x: 2)
2925
{ return 1}
26+
// invalid
27+
/*trailing:call*/withTrailingClosure(x: 1, y: 2) { return 1}
28+
/*trailing:call*/withTrailingClosure(x: 1, y: 2) { return 1}
3029

3130
func /*trailing-only:def*/trailingOnly(a: () -> ()) {}
3231
/*trailing-only:call*/trailingOnly(a: {})

test/refactoring/SyntacticRename/FindRangeOutputs/callsites/trailing.swift.expected

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,11 @@ func /*trailing:def*/<base>withTrailingClosure</base>(<arglabel index=0>x</argla
2121
// valid
2222
/*trailing:call*/<base>withTrailingClosure</base>(<callarg index=0>x</callarg><callcolon index=0>: </callcolon>2, <callarg index=1>y</callarg><callcolon index=1>: </callcolon>{ return 1})
2323
/*trailing:call*/<base>withTrailingClosure</base>(<callarg index=0>x</callarg><callcolon index=0>: </callcolon>2) { return 1}
24-
25-
// false positives
26-
/*trailing:call*/<base>withTrailingClosure</base>(<callarg index=0>x</callarg><callcolon index=0>: </callcolon>1, <callarg index=1>y</callarg><callcolon index=1>: </callcolon>2) { return 1}
27-
/*trailing:call*/<base>withTrailingClosure</base>(<callarg index=0>x</callarg><callcolon index=0>: </callcolon>1, <callarg index=1>y</callarg><callcolon index=1>: </callcolon>2) { return 1}
2824
/*trailing:call*/<base>withTrailingClosure</base>(<callarg index=0>x</callarg><callcolon index=0>: </callcolon>2)
2925
{ return 1}
26+
// invalid
27+
/*trailing:call*/withTrailingClosure(x: 1, y: 2) { return 1}
28+
/*trailing:call*/withTrailingClosure(x: 1, y: 2) { return 1}
3029

3130
func /*trailing-only:def*/trailingOnly(a: () -> ()) {}
3231
/*trailing-only:call*/trailingOnly(a: {})

test/refactoring/SyntacticRename/FindRangeOutputs/callsites/trailing_only.swift.expected

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,11 @@ func /*trailing:def*/withTrailingClosure(x: Int, y: () -> Int) {}
2121
// valid
2222
/*trailing:call*/withTrailingClosure(x: 2, y: { return 1})
2323
/*trailing:call*/withTrailingClosure(x: 2) { return 1}
24-
25-
// false positives
26-
/*trailing:call*/withTrailingClosure(x: 1, y: 2) { return 1}
27-
/*trailing:call*/withTrailingClosure(x: 1, y: 2) { return 1}
2824
/*trailing:call*/withTrailingClosure(x: 2)
2925
{ return 1}
26+
// invalid
27+
/*trailing:call*/withTrailingClosure(x: 1, y: 2) { return 1}
28+
/*trailing:call*/withTrailingClosure(x: 1, y: 2) { return 1}
3029

3130
func /*trailing-only:def*/<base>trailingOnly</base>(<arglabel index=0>a</arglabel><param index=0></param>: () -> ()) {}
3231
/*trailing-only:call*/<base>trailingOnly</base>(<callarg index=0>a</callarg><callcolon index=0>: </callcolon>{})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
func /*test:def*/<base>test</base>(<arglabel index=0>x</arglabel><param index=0></param>: () -> () = {}, <arglabel index=1>_</arglabel><param index=1> xx</param>: () -> () = {}, <arglabel index=2>y</arglabel><param index=2></param>: () -> () = {}, <arglabel index=3>z</arglabel><param index=3></param>: ()->() = {}) {}
2+
3+
/*test:call*/<base>test</base>(<callarg index=0>x</callarg><callcolon index=0>: </callcolon>{}, <callcombo index=1></callcombo>{}) {}
4+
/*test:call*/<base>test</base>(<callarg index=0>x</callarg><callcolon index=0>: </callcolon>{}) {} <sel index=3>z</sel>: {}
5+
/*test:call*/<base>test</base>(<callarg index=0>x</callarg><callcolon index=0>: </callcolon>{}, <callcombo index=1></callcombo>{}) {}
6+
/*test:call*/<base>test</base> {} <sel index=2>y</sel>: {}
7+
/*test:call*/<base>test</base>(<callarg index=2>y</callarg><callcolon index=2>: </callcolon>{}) {}
8+
9+

0 commit comments

Comments
 (0)