Skip to content

Commit a07b35a

Browse files
omochiowenv
andauthored
[Diagnostics] Improve {{none}} fix-it verifier (#30791)
* [Diagnostics] Improve {{none}} fix-it verifier * split two conditions * define "none" constant * support plural * use Twine and add comment for replacement range * check if {{none}} is at the end * use noneMarkerStartLoc * update second {{none}} error message Co-Authored-By: Owen Voorhees <[email protected]> * update test case for second {{none}} * fix test case for new {{none}} check * Use named struct * set const Co-authored-by: Owen Voorhees <[email protected]>
1 parent 0f15af5 commit a07b35a

File tree

6 files changed

+336
-41
lines changed

6 files changed

+336
-41
lines changed

lib/Frontend/DiagnosticVerifier.cpp

Lines changed: 130 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ struct ExpectedFixIt {
3434
} // end namespace swift
3535

3636
namespace {
37+
38+
static constexpr StringLiteral fixitExpectationNoneString("none");
39+
3740
struct ExpectedDiagnosticInfo {
3841
// This specifies the full range of the "expected-foo {{}}" specifier.
3942
const char *ExpectedStart, *ExpectedEnd = nullptr;
@@ -46,7 +49,7 @@ struct ExpectedDiagnosticInfo {
4649

4750
// This is true if a '{{none}}' is present to mark that there should be no
4851
// extra fixits.
49-
bool noExtraFixitsMayAppear = false;
52+
bool noExtraFixitsMayAppear() const { return noneMarkerStartLoc != nullptr; };
5053

5154
// This is the raw input buffer for the message text, the part in the
5255
// {{...}}
@@ -59,6 +62,9 @@ struct ExpectedDiagnosticInfo {
5962

6063
std::vector<ExpectedFixIt> Fixits;
6164

65+
// Loc of {{none}}
66+
const char *noneMarkerStartLoc = nullptr;
67+
6268
ExpectedDiagnosticInfo(const char *ExpectedStart,
6369
DiagnosticKind Classification)
6470
: ExpectedStart(ExpectedStart), Classification(Classification) {}
@@ -288,16 +294,15 @@ DiagnosticVerifier::Result DiagnosticVerifier::verifyFile(unsigned BufferID) {
288294
unsigned PrevExpectedContinuationLine = 0;
289295

290296
std::vector<ExpectedDiagnosticInfo> ExpectedDiagnostics;
291-
292-
auto addError = [&](const char *Loc, std::string message,
297+
298+
auto addError = [&](const char *Loc, const Twine &message,
293299
ArrayRef<llvm::SMFixIt> FixIts = {}) {
294300
auto loc = SourceLoc(SMLoc::getFromPointer(Loc));
295301
auto diag = SM.GetMessage(loc, llvm::SourceMgr::DK_Error, message,
296302
{}, FixIts);
297303
Errors.push_back(diag);
298304
};
299-
300-
305+
301306
// Scan the memory buffer looking for expected-note/warning/error.
302307
for (size_t Match = InputFile.find("expected-");
303308
Match != StringRef::npos; Match = InputFile.find("expected-", Match+1)) {
@@ -457,11 +462,25 @@ DiagnosticVerifier::Result DiagnosticVerifier::verifyFile(unsigned BufferID) {
457462
ExtraChecks = ExtraChecks.substr(EndLoc+2).ltrim();
458463

459464
// Special case for specifying no fixits should appear.
460-
if (FixItStr == "none") {
461-
Expected.noExtraFixitsMayAppear = true;
465+
if (FixItStr == fixitExpectationNoneString) {
466+
if (Expected.noneMarkerStartLoc) {
467+
addError(FixItStr.data() - 2,
468+
Twine("A second {{") + fixitExpectationNoneString +
469+
"}} was found. It may only appear once in an expectation.");
470+
break;
471+
}
472+
473+
Expected.noneMarkerStartLoc = FixItStr.data() - 2;
462474
continue;
463475
}
464-
476+
477+
if (Expected.noneMarkerStartLoc) {
478+
addError(Expected.noneMarkerStartLoc, Twine("{{") +
479+
fixitExpectationNoneString +
480+
"}} must be at the end.");
481+
break;
482+
}
483+
465484
// Parse the pieces of the fix-it.
466485
size_t MinusLoc = FixItStr.find('-');
467486
if (MinusLoc == StringRef::npos) {
@@ -547,46 +566,121 @@ DiagnosticVerifier::Result DiagnosticVerifier::verifyFile(unsigned BufferID) {
547566

548567
auto &FoundDiagnostic = *FoundDiagnosticIter;
549568

550-
const char *IncorrectFixit = nullptr;
569+
const char *missedFixitLoc = nullptr;
551570
// Verify that any expected fix-its are present in the diagnostic.
552571
for (auto fixit : expected.Fixits) {
553572
// If we found it, we're ok.
554-
if (!checkForFixIt(fixit, FoundDiagnostic, InputFile))
555-
IncorrectFixit = fixit.StartLoc;
573+
if (!checkForFixIt(fixit, FoundDiagnostic, InputFile)) {
574+
missedFixitLoc = fixit.StartLoc;
575+
break;
576+
}
556577
}
557578

558-
bool matchedAllFixIts =
559-
expected.Fixits.size() == FoundDiagnostic.FixIts.size();
579+
const bool isUnexpectedFixitsSeen =
580+
expected.Fixits.size() < FoundDiagnostic.FixIts.size();
581+
582+
struct ActualFixitsPhrase {
583+
std::string phrase;
584+
std::string actualFixits;
585+
};
586+
587+
auto makeActualFixitsPhrase =
588+
[&](ArrayRef<DiagnosticInfo::FixIt> actualFixits)
589+
-> ActualFixitsPhrase {
590+
std::string actualFixitsStr = renderFixits(actualFixits, InputFile);
591+
592+
auto phrase = Twine("actual fix-it") +
593+
(actualFixits.size() >= 2 ? "s" : "") +
594+
" seen: " + actualFixitsStr;
595+
return ActualFixitsPhrase{phrase.str(), actualFixitsStr};
596+
};
597+
598+
auto emitFixItsError = [&](const char *location, const Twine &message,
599+
const char *replStartLoc, const char *replEndLoc,
600+
const std::string &replStr) {
601+
llvm::SMFixIt fix(llvm::SMRange(SMLoc::getFromPointer(replStartLoc),
602+
SMLoc::getFromPointer(replEndLoc)),
603+
replStr);
604+
addError(location, message, fix);
605+
};
560606

561607
// If we have any expected fixits that didn't get matched, then they are
562608
// wrong. Replace the failed fixit with what actually happened.
563-
if (IncorrectFixit) {
609+
610+
if (missedFixitLoc) {
611+
// If we had an incorrect expected fixit, render it and produce a fixit
612+
// of our own.
613+
614+
assert(!expected.Fixits.empty() &&
615+
"some fix-its should be expected here");
616+
617+
const char *replStartLoc = expected.Fixits.front().StartLoc;
618+
const char *replEndLoc = expected.Fixits.back().EndLoc;
619+
620+
std::string message = "expected fix-it not seen";
621+
std::string actualFixits;
622+
564623
if (FoundDiagnostic.FixIts.empty()) {
565-
addError(IncorrectFixit, "expected fix-it not seen");
624+
/// If actual fix-its is empty,
625+
/// eat a space before first marker.
626+
/// For example,
627+
///
628+
/// @code
629+
/// expected-error {{message}} {{1-2=aa}}
630+
/// ~~~~~~~~~~~
631+
/// ^ remove
632+
/// @endcode
633+
if (replStartLoc[-1] == ' ') {
634+
replStartLoc--;
635+
}
566636
} else {
567-
// If we had an incorrect expected fixit, render it and produce a fixit
568-
// of our own.
569-
auto actual = renderFixits(FoundDiagnostic.FixIts, InputFile);
570-
auto replStartLoc = SMLoc::getFromPointer(expected.Fixits[0].StartLoc);
571-
auto replEndLoc = SMLoc::getFromPointer(expected.Fixits.back().EndLoc);
572-
573-
llvm::SMFixIt fix(llvm::SMRange(replStartLoc, replEndLoc), actual);
574-
addError(IncorrectFixit,
575-
"expected fix-it not seen; actual fix-its: " + actual, fix);
637+
auto phrase = makeActualFixitsPhrase(FoundDiagnostic.FixIts);
638+
actualFixits = phrase.actualFixits;
639+
message += "; " + phrase.phrase;
576640
}
577-
} else if (expected.noExtraFixitsMayAppear &&
578-
!matchedAllFixIts &&
579-
!expected.mayAppear) {
580-
// If there was no fixit specification, but some were produced, add a
581-
// fixit to add them in.
582-
auto actual = renderFixits(FoundDiagnostic.FixIts, InputFile);
583-
auto replStartLoc = SMLoc::getFromPointer(expected.ExpectedEnd - 8); // {{none}} length
584-
auto replEndLoc = SMLoc::getFromPointer(expected.ExpectedEnd);
585-
586-
llvm::SMFixIt fix(llvm::SMRange(replStartLoc, replEndLoc), actual);
587-
addError(replStartLoc.getPointer(), "expected no fix-its; actual fix-it seen: " + actual, fix);
641+
642+
emitFixItsError(missedFixitLoc, message, replStartLoc, replEndLoc,
643+
actualFixits);
644+
} else if (expected.noExtraFixitsMayAppear() && isUnexpectedFixitsSeen) {
645+
// If unexpected fixit were produced, add a fixit to add them in.
646+
647+
assert(!FoundDiagnostic.FixIts.empty() &&
648+
"some fix-its should be produced here");
649+
assert(expected.noneMarkerStartLoc && "none marker location is null");
650+
651+
const char *replStartLoc = nullptr, *replEndLoc = nullptr;
652+
std::string message;
653+
if (expected.Fixits.empty()) {
654+
message = "expected no fix-its";
655+
replStartLoc = expected.noneMarkerStartLoc;
656+
replEndLoc = expected.noneMarkerStartLoc;
657+
} else {
658+
message = "unexpected fix-it seen";
659+
replStartLoc = expected.Fixits.front().StartLoc;
660+
replEndLoc = expected.Fixits.back().EndLoc;
661+
}
662+
663+
auto phrase = makeActualFixitsPhrase(FoundDiagnostic.FixIts);
664+
std::string actualFixits = phrase.actualFixits;
665+
message += "; " + phrase.phrase;
666+
667+
if (replStartLoc == replEndLoc) {
668+
/// If no fix-its was expected and range of replacement is empty,
669+
/// insert space after new last marker.
670+
/// For example:
671+
///
672+
/// @code
673+
/// expected-error {{message}} {{none}}
674+
/// ^
675+
/// insert `{{1-2=aa}} `
676+
/// @endcode
677+
actualFixits += " ";
678+
}
679+
680+
emitFixItsError(expected.noneMarkerStartLoc, message, replStartLoc,
681+
replEndLoc, actualFixits);
588682
}
589-
683+
590684
// Actually remove the diagnostic from the list, so we don't match it
591685
// again. We do have to do this after checking fix-its, though, because
592686
// the diagnostic owns its fix-its.

test/FixCode/verify-fixits.swift

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,62 @@ func f1() {
66
guard true { return } // expected-error {{...}}
77

88
guard true { return } // expected-error {{expected 'else' after 'guard' condition}} {{none}}
9-
}
9+
}
10+
11+
func labeledFunc(aa: Int, bb: Int) {}
12+
13+
func test0Fixits() {
14+
undefinedFunc() // expected-error {{use of unresolved identifier 'undefinedFunc'}}
15+
16+
undefinedFunc() // expected-error {{use of unresolved identifier 'undefinedFunc'}} {{1-1=a}}
17+
18+
undefinedFunc() // expected-error {{use of unresolved identifier 'undefinedFunc'}} {{1-1=a}} {{2-2=b}}
19+
20+
undefinedFunc() // expected-error {{use of unresolved identifier 'undefinedFunc'}} {{none}}
21+
22+
undefinedFunc() // expected-error {{use of unresolved identifier 'undefinedFunc'}} {{1-1=a}} {{none}}
23+
24+
undefinedFunc() // expected-error {{use of unresolved identifier 'undefinedFunc'}} {{1-1=a}} {{2-2=b}} {{none}}
25+
}
26+
27+
func test1Fixits() {
28+
labeledFunc(aax: 0, bb: 1) // expected-error {{incorrect argument label in call (have 'aax:bb:', expected 'aa:bb:')}}
29+
30+
labeledFunc(aax: 0, bb: 1) // expected-error {{incorrect argument label in call (have 'aax:bb:', expected 'aa:bb:')}} {{15-18=aa}}
31+
32+
labeledFunc(aax: 0, bb: 1) // expected-error {{incorrect argument label in call (have 'aax:bb:', expected 'aa:bb:')}} {{15-18=xx}}
33+
34+
labeledFunc(aax: 0, bb: 1) // expected-error {{incorrect argument label in call (have 'aax:bb:', expected 'aa:bb:')}} {{15-18=aa}} {{15-18=xx}}
35+
36+
labeledFunc(aax: 0, bb: 1) // expected-error {{incorrect argument label in call (have 'aax:bb:', expected 'aa:bb:')}} {{15-18=xx}} {{15-18=aa}}
37+
38+
labeledFunc(aax: 0, bb: 1) // expected-error {{incorrect argument label in call (have 'aax:bb:', expected 'aa:bb:')}} {{none}}
39+
40+
labeledFunc(aax: 0, bb: 1) // expected-error {{incorrect argument label in call (have 'aax:bb:', expected 'aa:bb:')}} {{15-18=aa}} {{none}}
41+
42+
labeledFunc(aax: 0, bb: 1) // expected-error {{incorrect argument label in call (have 'aax:bb:', expected 'aa:bb:')}} {{15-18=xx}} {{none}}
43+
44+
labeledFunc(aax: 0, bb: 1) // expected-error {{incorrect argument label in call (have 'aax:bb:', expected 'aa:bb:')}} {{15-18=aa}} {{15-18=xx}} {{none}}
45+
46+
labeledFunc(aax: 0, bb: 1) // expected-error {{incorrect argument label in call (have 'aax:bb:', expected 'aa:bb:')}} {{15-18=xx}} {{15-18=aa}} {{none}}
47+
}
48+
49+
func test2Fixits() {
50+
labeledFunc(aax: 0, bbx: 1) // expected-error {{incorrect argument labels in call (have 'aax:bbx:', expected 'aa:bb:')}}
51+
52+
labeledFunc(aax: 0, bbx: 1) // expected-error {{incorrect argument labels in call (have 'aax:bbx:', expected 'aa:bb:')}} {{15-18=aa}}
53+
54+
labeledFunc(aax: 0, bbx: 1) // expected-error {{incorrect argument labels in call (have 'aax:bbx:', expected 'aa:bb:')}} {{15-18=xx}}
55+
56+
labeledFunc(aax: 0, bbx: 1) // expected-error {{incorrect argument labels in call (have 'aax:bbx:', expected 'aa:bb:')}} {{15-18=aa}} {{23-26=bb}}
57+
58+
labeledFunc(aax: 0, bbx: 1) // expected-error {{incorrect argument labels in call (have 'aax:bbx:', expected 'aa:bb:')}} {{15-18=aa}} {{23-26=xx}}
59+
60+
labeledFunc(aax: 0, bbx: 1) // expected-error {{incorrect argument labels in call (have 'aax:bbx:', expected 'aa:bb:')}} {{none}}
61+
62+
labeledFunc(aax: 0, bbx: 1) // expected-error {{incorrect argument labels in call (have 'aax:bbx:', expected 'aa:bb:')}} {{15-18=aa}} {{none}}
63+
64+
labeledFunc(aax: 0, bbx: 1) // expected-error {{incorrect argument labels in call (have 'aax:bbx:', expected 'aa:bb:')}} {{15-18=aa}} {{23-26=bb}} {{none}}
65+
66+
labeledFunc(aax: 0, bbx: 1) // expected-error {{incorrect argument labels in call (have 'aax:bbx:', expected 'aa:bb:')}} {{15-18=aa}} {{23-26=xx}} {{none}}
67+
}

test/FixCode/verify-fixits.swift.result

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,63 @@
55
func f1() {
66
guard true { return } // expected-error {{expected 'else' after 'guard' condition}}
77

8-
guard true { return } // expected-error {{expected 'else' after 'guard' condition}} {{14-14=else }}
9-
}
8+
guard true { return } // expected-error {{expected 'else' after 'guard' condition}} {{14-14=else }} {{none}}
9+
}
10+
11+
func labeledFunc(aa: Int, bb: Int) {}
12+
13+
func test0Fixits() {
14+
undefinedFunc() // expected-error {{use of unresolved identifier 'undefinedFunc'}}
15+
16+
undefinedFunc() // expected-error {{use of unresolved identifier 'undefinedFunc'}}
17+
18+
undefinedFunc() // expected-error {{use of unresolved identifier 'undefinedFunc'}}
19+
20+
undefinedFunc() // expected-error {{use of unresolved identifier 'undefinedFunc'}} {{none}}
21+
22+
undefinedFunc() // expected-error {{use of unresolved identifier 'undefinedFunc'}} {{none}}
23+
24+
undefinedFunc() // expected-error {{use of unresolved identifier 'undefinedFunc'}} {{none}}
25+
}
26+
27+
func test1Fixits() {
28+
labeledFunc(aax: 0, bb: 1) // expected-error {{incorrect argument label in call (have 'aax:bb:', expected 'aa:bb:')}}
29+
30+
labeledFunc(aax: 0, bb: 1) // expected-error {{incorrect argument label in call (have 'aax:bb:', expected 'aa:bb:')}} {{15-18=aa}}
31+
32+
labeledFunc(aax: 0, bb: 1) // expected-error {{incorrect argument label in call (have 'aax:bb:', expected 'aa:bb:')}} {{15-18=aa}}
33+
34+
labeledFunc(aax: 0, bb: 1) // expected-error {{incorrect argument label in call (have 'aax:bb:', expected 'aa:bb:')}} {{15-18=aa}}
35+
36+
labeledFunc(aax: 0, bb: 1) // expected-error {{incorrect argument label in call (have 'aax:bb:', expected 'aa:bb:')}} {{15-18=aa}}
37+
38+
labeledFunc(aax: 0, bb: 1) // expected-error {{incorrect argument label in call (have 'aax:bb:', expected 'aa:bb:')}} {{15-18=aa}} {{none}}
39+
40+
labeledFunc(aax: 0, bb: 1) // expected-error {{incorrect argument label in call (have 'aax:bb:', expected 'aa:bb:')}} {{15-18=aa}} {{none}}
41+
42+
labeledFunc(aax: 0, bb: 1) // expected-error {{incorrect argument label in call (have 'aax:bb:', expected 'aa:bb:')}} {{15-18=aa}} {{none}}
43+
44+
labeledFunc(aax: 0, bb: 1) // expected-error {{incorrect argument label in call (have 'aax:bb:', expected 'aa:bb:')}} {{15-18=aa}} {{none}}
45+
46+
labeledFunc(aax: 0, bb: 1) // expected-error {{incorrect argument label in call (have 'aax:bb:', expected 'aa:bb:')}} {{15-18=aa}} {{none}}
47+
}
48+
49+
func test2Fixits() {
50+
labeledFunc(aax: 0, bbx: 1) // expected-error {{incorrect argument labels in call (have 'aax:bbx:', expected 'aa:bb:')}}
51+
52+
labeledFunc(aax: 0, bbx: 1) // expected-error {{incorrect argument labels in call (have 'aax:bbx:', expected 'aa:bb:')}} {{15-18=aa}}
53+
54+
labeledFunc(aax: 0, bbx: 1) // expected-error {{incorrect argument labels in call (have 'aax:bbx:', expected 'aa:bb:')}} {{15-18=aa}} {{23-26=bb}}
55+
56+
labeledFunc(aax: 0, bbx: 1) // expected-error {{incorrect argument labels in call (have 'aax:bbx:', expected 'aa:bb:')}} {{15-18=aa}} {{23-26=bb}}
57+
58+
labeledFunc(aax: 0, bbx: 1) // expected-error {{incorrect argument labels in call (have 'aax:bbx:', expected 'aa:bb:')}} {{15-18=aa}} {{23-26=bb}}
59+
60+
labeledFunc(aax: 0, bbx: 1) // expected-error {{incorrect argument labels in call (have 'aax:bbx:', expected 'aa:bb:')}} {{15-18=aa}} {{23-26=bb}} {{none}}
61+
62+
labeledFunc(aax: 0, bbx: 1) // expected-error {{incorrect argument labels in call (have 'aax:bbx:', expected 'aa:bb:')}} {{15-18=aa}} {{23-26=bb}} {{none}}
63+
64+
labeledFunc(aax: 0, bbx: 1) // expected-error {{incorrect argument labels in call (have 'aax:bbx:', expected 'aa:bb:')}} {{15-18=aa}} {{23-26=bb}} {{none}}
65+
66+
labeledFunc(aax: 0, bbx: 1) // expected-error {{incorrect argument labels in call (have 'aax:bbx:', expected 'aa:bb:')}} {{15-18=aa}} {{23-26=bb}} {{none}}
67+
}

0 commit comments

Comments
 (0)