Skip to content

Commit 35755ce

Browse files
committed
[Exclusivity] Don't suggest copying to a local when swapAt() is appropriate
Update static exclusivity diagnostics to not suggest copying to a local when a call to swapAt() should be used instead. We already emit a FixIt in this case, so don't suggest an an helpful fix in the diagnostic. rdar://problem/32296784
1 parent 7b57998 commit 35755ce

File tree

5 files changed

+84
-44
lines changed

5 files changed

+84
-44
lines changed

include/swift/AST/DiagnosticsSIL.def

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,14 +92,18 @@ NOTE(previous_inout_alias,none,
9292
WARNING(exclusivity_access_required_swift3,none,
9393
"overlapping accesses to %0, but "
9494
"%select{initialization|read|modification|deinitialization}1 requires "
95-
"exclusive access; consider copying to a local variable",
96-
(StringRef, unsigned))
95+
"exclusive access; "
96+
"%select{consider copying to a local variable|"
97+
"consider calling MutableCollection.swapAt(_:_:)}2",
98+
(StringRef, unsigned, bool))
9799

98100
ERROR(exclusivity_access_required,none,
99101
"overlapping accesses to %0, but "
100102
"%select{initialization|read|modification|deinitialization}1 requires "
101-
"exclusive access; consider copying to a local variable",
102-
(StringRef, unsigned))
103+
"exclusive access; "
104+
"%select{consider copying to a local variable|"
105+
"consider calling MutableCollection.swapAt(_:_:)}2",
106+
(StringRef, unsigned, bool))
103107

104108
ERROR(exclusivity_access_required_unknown_decl,none,
105109
"overlapping accesses, but "

lib/SILOptimizer/Mandatory/DiagnoseStaticExclusivity.cpp

Lines changed: 64 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -500,38 +500,45 @@ static bool isCallToStandardLibrarySwap(CallExpr *CE, ASTContext &Ctx) {
500500
}
501501
#endif
502502

503-
/// Do a sytactic pattern match to try to safely suggest a Fix-It to rewrite
504-
/// calls like swap(&collection[index1], &collection[index2]) to
503+
/// Do a syntactic pattern match to determine whether the call is a call
504+
/// to swap(&base[index1], &base[index2]), which can
505+
/// be replaced with a call to MutableCollection.swapAt(_:_:) on base.
506+
///
507+
/// Returns true if the call can be replaced. Returns the call expression,
508+
/// the base expression, and the two indices as out expressions.
505509
///
506510
/// This method takes an array of all the ApplyInsts for calls to swap()
507-
/// in the function to avoid need to construct a parent map over the AST
511+
/// in the function to avoid needing to construct a parent map over the AST
508512
/// to find the CallExpr for the inout accesses.
509-
static void
510-
tryFixItWithCallToCollectionSwapAt(const BeginAccessInst *Access1,
511-
const BeginAccessInst *Access2,
512-
ArrayRef<ApplyInst *> CallsToSwap,
513-
ASTContext &Ctx,
514-
InFlightDiagnostic &Diag) {
513+
static bool
514+
canReplaceWithCallToCollectionSwapAt(const BeginAccessInst *Access1,
515+
const BeginAccessInst *Access2,
516+
ArrayRef<ApplyInst *> CallsToSwap,
517+
ASTContext &Ctx,
518+
CallExpr *&FoundCall,
519+
Expr *&Base,
520+
Expr *&Index1,
521+
Expr *&Index2) {
515522
if (CallsToSwap.empty())
516-
return;
523+
return false;
517524

518525
// Inout arguments must be modifications.
519526
if (Access1->getAccessKind() != SILAccessKind::Modify ||
520527
Access2->getAccessKind() != SILAccessKind::Modify) {
521-
return;
528+
return false;
522529
}
523530

524531
SILLocation Loc1 = Access1->getLoc();
525532
SILLocation Loc2 = Access2->getLoc();
526533
if (Loc1.isNull() || Loc2.isNull())
527-
return;
534+
return false;
528535

529536
auto *InOut1 = Loc1.getAsASTNode<InOutExpr>();
530537
auto *InOut2 = Loc2.getAsASTNode<InOutExpr>();
531538
if (!InOut1 || !InOut2)
532-
return;
539+
return false;
533540

534-
CallExpr *FoundCall = nullptr;
541+
FoundCall = nullptr;
535542
// Look through all the calls to swap() recorded in the function to find
536543
// which one we're diagnosing.
537544
for (ApplyInst *AI : CallsToSwap) {
@@ -554,22 +561,22 @@ tryFixItWithCallToCollectionSwapAt(const BeginAccessInst *Access1,
554561
}
555562
}
556563
if (!FoundCall)
557-
return;
564+
return false;
558565

559566
// We found a call to swap(&e1, &e2). Now check to see whether it
560567
// matches the form swap(&someCollection[index1], &someCollection[index2]).
561568
auto *SE1 = dyn_cast<SubscriptExpr>(InOut1->getSubExpr());
562569
if (!SE1)
563-
return;
570+
return false;
564571
auto *SE2 = dyn_cast<SubscriptExpr>(InOut2->getSubExpr());
565572
if (!SE2)
566-
return;
573+
return false;
567574

568575
// Do the two subscripts refer to the same subscript declaration?
569576
auto *Decl1 = cast<SubscriptDecl>(SE1->getDecl().getDecl());
570577
auto *Decl2 = cast<SubscriptDecl>(SE2->getDecl().getDecl());
571578
if (Decl1 != Decl2)
572-
return;
579+
return false;
573580

574581
ProtocolDecl *MutableCollectionDecl = Ctx.getMutableCollectionDecl();
575582

@@ -594,7 +601,7 @@ tryFixItWithCallToCollectionSwapAt(const BeginAccessInst *Access1,
594601
}
595602

596603
if (!IsSubscriptOnMutableCollection)
597-
return;
604+
return false;
598605

599606
// We're swapping two subscripts on mutable collections -- but are they
600607
// the same collection? Approximate this by checking for textual
@@ -605,24 +612,33 @@ tryFixItWithCallToCollectionSwapAt(const BeginAccessInst *Access1,
605612
StringRef Base2Text = extractExprText(SE2->getBase(), SM);
606613

607614
if (Base1Text != Base2Text)
608-
return;
615+
return false;
609616

610-
auto *Index1 = dyn_cast<ParenExpr>(SE1->getIndex());
611-
if (!Index1)
612-
return;
617+
auto *Index1Paren = dyn_cast<ParenExpr>(SE1->getIndex());
618+
if (!Index1Paren)
619+
return false;
613620

614-
auto *Index2 = dyn_cast<ParenExpr>(SE2->getIndex());
615-
if (!Index2)
616-
return;
621+
auto *Index2Paren = dyn_cast<ParenExpr>(SE2->getIndex());
622+
if (!Index2Paren)
623+
return false;
617624

618-
StringRef Index1Text = extractExprText(Index1->getSubExpr(), SM);
619-
StringRef Index2Text = extractExprText(Index2->getSubExpr(), SM);
625+
Base = SE1->getBase();
626+
Index1 = Index1Paren->getSubExpr();
627+
Index2 = Index2Paren->getSubExpr();
628+
return true;
629+
}
620630

621-
// Suggest replacing with call with a call to swapAt().
631+
/// Suggest replacing with call with a call to swapAt().
632+
static void addSwapAtFixit(InFlightDiagnostic &Diag, CallExpr *&FoundCall,
633+
Expr *Base, Expr *&Index1, Expr *&Index2,
634+
SourceManager &SM) {
635+
StringRef BaseText = extractExprText(Base, SM);
636+
StringRef Index1Text = extractExprText(Index1, SM);
637+
StringRef Index2Text = extractExprText(Index2, SM);
622638
SmallString<64> FixItText;
623639
{
624640
llvm::raw_svector_ostream Out(FixItText);
625-
Out << Base1Text << ".swapAt(" << Index1Text << ", " << Index2Text << ")";
641+
Out << BaseText << ".swapAt(" << Index1Text << ", " << Index2Text << ")";
626642
}
627643

628644
Diag.fixItReplace(FoundCall->getSourceRange(), FixItText);
@@ -686,15 +702,27 @@ static void diagnoseExclusivityViolation(const ConflictingAccess &Violation,
686702
SILModule &M = FirstAccess.getInstruction()->getModule();
687703
std::string PathDescription = getPathDescription(
688704
VD->getBaseName(), BaseType, MainAccess.getSubPath(), M);
705+
706+
// Determine whether we can safely suggest replacing the violation with
707+
// a call to MutableCollection.swapAt().
708+
bool SuggestSwapAt = false;
709+
CallExpr *CallToReplace = nullptr;
710+
Expr *Base = nullptr;
711+
Expr *SwapIndex1 = nullptr;
712+
Expr *SwapIndex2 = nullptr;
713+
if (SecondAccess.getRecordKind() == RecordedAccessKind::BeginInstruction) {
714+
SuggestSwapAt = canReplaceWithCallToCollectionSwapAt(
715+
FirstAccess.getInstruction(), SecondAccess.getInstruction(),
716+
CallsToSwap, Ctx, CallToReplace, Base, SwapIndex1, SwapIndex2);
717+
}
718+
689719
auto D =
690720
diagnose(Ctx, MainAccess.getAccessLoc().getSourceLoc(), DiagnosticID,
691-
PathDescription, AccessKindForMain);
721+
PathDescription, AccessKindForMain, SuggestSwapAt);
692722
D.highlight(RangeForMain);
693-
if (SecondAccess.getRecordKind() == RecordedAccessKind::BeginInstruction) {
694-
tryFixItWithCallToCollectionSwapAt(FirstAccess.getInstruction(),
695-
SecondAccess.getInstruction(),
696-
CallsToSwap, Ctx, D);
697-
}
723+
if (SuggestSwapAt)
724+
addSwapAtFixit(D, CallToReplace, Base, SwapIndex1, SwapIndex2,
725+
Ctx.SourceMgr);
698726
} else {
699727
auto DiagnosticID = (Ctx.LangOpts.isSwiftVersion3() ?
700728
diag::exclusivity_access_required_unknown_decl_swift3 :

test/SILGen/polymorphic_inout_aliasing.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class Story {
1111
}
1212

1313
func test() {
14-
// expected-warning@+2 {{overlapping accesses to 'finalStored', but modification requires exclusive access; consider copying to a local variable}}
14+
// expected-warning@+2 {{overlapping accesses to 'finalStored', but modification requires exclusive access; consider calling MutableCollection.swapAt(_:_:)}}
1515
// expected-note@+1 {{conflicting access is here}}
1616
swap(&self.finalStored[0], &self.finalStored[1])
1717
swap(&self.overridableStored[0], &self.overridableStored[1])
@@ -24,7 +24,7 @@ protocol Storied {
2424
}
2525

2626
func testProtocol<T: Storied>(x: inout T) {
27-
// expected-warning@+2 {{overlapping accesses to 'x', but modification requires exclusive access; consider copying to a local variable}}
27+
// expected-warning@+2 {{overlapping accesses to 'x', but modification requires exclusive access; consider calling MutableCollection.swapAt(_:_:)}}
2828
// expected-note@+1 {{conflicting access is here}}
2929
swap(&x.protocolRequirement[0], &x.protocolRequirement[1])
3030
}

test/SILOptimizer/exclusivity_static_diagnostics.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ func inoutOnInoutParameter(p: inout Int) {
2727
func swapNoSuppression(_ i: Int, _ j: Int) {
2828
var a: [Int] = [1, 2, 3]
2929

30-
// expected-error@+2{{overlapping accesses to 'a', but modification requires exclusive access; consider copying to a local variable}}
30+
// expected-error@+2{{overlapping accesses to 'a', but modification requires exclusive access; consider calling MutableCollection.swapAt(_:_:)}}
3131
// expected-note@+1{{conflicting access is here}}
32-
swap(&a[i], &a[j]) // no-warning
32+
swap(&a[i], &a[j])
3333
}
3434

3535
class SomeClass { }

test/SILOptimizer/exclusivity_static_diagnostics_swift3.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,11 @@ func diagnoseOnSameField() {
2828
// expected-note@+1{{conflicting access is here}}
2929
takesTwoInouts(&x.f, &x.f)
3030
}
31+
32+
func diagnoseSwapOnMutableCollection(_ i: Int, _ j: Int) {
33+
var a: [Int] = [1, 2, 3]
34+
35+
// expected-warning@+2{{overlapping accesses to 'a', but modification requires exclusive access; consider calling MutableCollection.swapAt(_:_:)}}
36+
// expected-note@+1{{conflicting access is here}}
37+
swap(&a[i], &a[j])
38+
}

0 commit comments

Comments
 (0)