Skip to content

DiagnosticVerifier: Support line offsets in fix-it verification ranges #41429

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 123 additions & 50 deletions lib/Frontend/DiagnosticVerifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,113 @@ DiagnosticVerifier::renderFixits(ArrayRef<CapturedFixItInfo> ActualFixIts,
return OS.str();
}

/// Parse the introductory line-column range of an expected fix-it by consuming
/// the given input string. The range format is \c ([+-]?N:)?N-([+-]?N:)?N
/// where \c 'N' is \c [0-9]+.
///
/// \param DiagnosticLineNo The line number of the associated expected
/// diagnostic; used to turn line offsets into line numbers.
static Optional<LineColumnRange> parseExpectedFixItRange(
StringRef &Str, unsigned DiagnosticLineNo,
llvm::function_ref<void(const char *, const Twine &)> diagnoseError) {
assert(!Str.empty());

const auto parseLineAndColumn =
[&]() -> Optional<std::pair<unsigned, unsigned>> {
enum class LineOffsetKind : uint8_t { None, Plus, Minus };

LineOffsetKind lineOffsetKind = LineOffsetKind::None;
if (!Str.empty()) {
switch (Str.front()) {
case '+':
lineOffsetKind = LineOffsetKind::Plus;
Str = Str.drop_front();
break;
case '-':
lineOffsetKind = LineOffsetKind::Minus;
Str = Str.drop_front();
break;
default:
break;
}
}

unsigned firstNumber = LineColumnRange::NoValue;
if (Str.consumeInteger(10, firstNumber)) {
if (lineOffsetKind > LineOffsetKind::None) {
diagnoseError(Str.data(),
"expected line offset after leading '+' or '-' in fix-it "
"verification");
} else {
diagnoseError(Str.data(),
"expected line or column number in fix-it verification");
}
return None;
}

unsigned secondNumber = LineColumnRange::NoValue;
if (!Str.empty() && Str.front() == ':') {
Str = Str.drop_front();

if (Str.consumeInteger(10, secondNumber)) {
diagnoseError(
Str.data(),
"expected column number after ':' in fix-it verification");
return None;
}
} else if (lineOffsetKind > LineOffsetKind::None) {
diagnoseError(Str.data(),
"expected colon-separated column number after line offset "
"in fix-it verification");
return None;
}

if (secondNumber == LineColumnRange::NoValue) {
// If only one value is specified, it's a column number;
return std::make_pair(LineColumnRange::NoValue, firstNumber);
}

unsigned lineNo = DiagnosticLineNo;
switch (lineOffsetKind) {
case LineOffsetKind::None:
lineNo = firstNumber;
break;
case LineOffsetKind::Plus:
lineNo += firstNumber;
break;
case LineOffsetKind::Minus:
lineNo -= firstNumber;
break;
}

return std::make_pair(lineNo, secondNumber);
};

LineColumnRange Range;

if (const auto lineAndCol = parseLineAndColumn()) {
std::tie(Range.StartLine, Range.StartCol) = lineAndCol.getValue();
} else {
return None;
}

if (!Str.empty() && Str.front() == '-') {
Str = Str.drop_front();
} else {
diagnoseError(Str.data(),
"expected '-' range separator in fix-it verification");
return None;
}

if (const auto lineAndCol = parseLineAndColumn()) {
std::tie(Range.EndLine, Range.EndCol) = lineAndCol.getValue();
} else {
return None;
}

return Range;
}

/// After the file has been processed, check to see if we got all of
/// the expected diagnostics and check to see if there were any unexpected
/// ones.
Expand Down Expand Up @@ -574,69 +681,35 @@ DiagnosticVerifier::Result DiagnosticVerifier::verifyFile(unsigned BufferID) {
break;
}

// Parse the pieces of the fix-it: (L:)?C-(L:)?C=text
const size_t MinusLoc = CheckStr.find('-');
if (MinusLoc == StringRef::npos) {
addError(CheckStr.data(), "expected '-' in fix-it verification");
continue;
}

const size_t EqualLoc = CheckStr.find('=', /*From=*/MinusLoc + 1);
if (EqualLoc == StringRef::npos) {
addError(CheckStr.data(),
"expected '=' after '-' in fix-it verification");
if (CheckStr.empty()) {
addError(CheckStr.data(), Twine("expected fix-it verification within "
"braces; example: '1-2=text' or '") +
fixitExpectationNoneString + Twine("'"));
continue;
}

const size_t FirstColonLoc = CheckStr.take_front(MinusLoc).find(':');
const size_t SecondColonLoc =
CheckStr.take_front(EqualLoc).find(':', /*From=*/MinusLoc + 1);

// Parse the pieces of the fix-it.
ExpectedFixIt FixIt;
FixIt.StartLoc = OpenLoc;
FixIt.EndLoc = CloseLoc;
if (FirstColonLoc != StringRef::npos) {
const StringRef StartLineStr = CheckStr.take_front(FirstColonLoc);
if (StartLineStr.getAsInteger(10, FixIt.Range.StartLine)) {
addError(StartLineStr.data(),
"invalid line number in fix-it verification");
continue;
}
}

const StringRef StartColStr =
(FirstColonLoc != StringRef::npos)
? CheckStr.slice(FirstColonLoc + 1, MinusLoc)
: CheckStr.take_front(MinusLoc);
if (StartColStr.getAsInteger(10, FixIt.Range.StartCol)) {
addError(StartColStr.data(),
"invalid column number in fix-it verification");
if (const auto range =
parseExpectedFixItRange(CheckStr, Expected.LineNo, addError)) {
FixIt.Range = range.getValue();
} else {
continue;
}

if (SecondColonLoc != StringRef::npos) {
const StringRef EndLineStr =
CheckStr.slice(MinusLoc + 1, SecondColonLoc);
if (EndLineStr.getAsInteger(10, FixIt.Range.EndLine)) {
addError(EndLineStr.data(),
"invalid line number in fix-it verification");
continue;
}
}

const StringRef EndColStr =
(SecondColonLoc != StringRef::npos)
? CheckStr.slice(SecondColonLoc + 1, EqualLoc)
: CheckStr.slice(MinusLoc + 1, EqualLoc);
if (EndColStr.getAsInteger(10, FixIt.Range.EndCol)) {
addError(EndColStr.data(),
"invalid column number in fix-it verification");
if (!CheckStr.empty() && CheckStr.front() == '=') {
CheckStr = CheckStr.drop_front();
} else {
addError(CheckStr.data(),
"expected '=' after range in fix-it verification");
continue;
}

// Translate literal "\\n" into '\n', inefficiently.
const StringRef fixItText = CheckStr.substr(EqualLoc + 1);
for (const char *current = fixItText.begin(), *end = fixItText.end();
for (const char *current = CheckStr.begin(), *end = CheckStr.end();
current != end; /* in loop */) {
if (*current == '\\' && current + 1 < end) {
if (current[1] == 'n') {
Expand Down
145 changes: 133 additions & 12 deletions test/Frontend/verify-fixits.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,36 +15,129 @@ func testNoneMarkerCheck() {
func test0Fixits() {
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}}

// CHECK: [[@LINE+1]]:80: error: expected fix-it verification within braces; example: '1-2=text' or 'none'
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{}}

// CHECK: [[@LINE+1]]:81: error: expected line offset after leading '+' or '-' in fix-it verification
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{+}}

// CHECK: [[@LINE+1]]:81: error: expected line offset after leading '+' or '-' in fix-it verification
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{-}}

// CHECK: [[@LINE+1]]:81: error: expected '-' range separator in fix-it verification
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{1}}

// CHECK: [[@LINE+1]]:82: error: expected colon-separated column number after line offset in fix-it verification
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{-1}}

// CHECK: [[@LINE+1]]:82: error: expected colon-separated column number after line offset in fix-it verification
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{+1}}

// CHECK: [[@LINE+1]]:82: error: expected column number after ':' in fix-it verification
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{1:}}

// CHECK: [[@LINE+1]]:83: error: expected column number after ':' in fix-it verification
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{+1:}}

// CHECK: [[@LINE+1]]:83: error: expected '-' range separator in fix-it verification
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{1:1}}

// CHECK: [[@LINE+1]]:84: error: expected '-' range separator in fix-it verification
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{-1:1}}

// CHECK: [[@LINE+1]]:83: error: expected column number after ':' in fix-it verification
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{+1:-1}}

// CHECK: [[@LINE+1]]:82: error: expected line or column number in fix-it verification
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{1-}}

// CHECK: [[@LINE+1]]:84: error: expected line or column number in fix-it verification
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{1:1-}}

// CHECK: [[@LINE+1]]:85: error: expected line or column number in fix-it verification
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{-1:1-}}

// CHECK: [[@LINE+1]]:83: error: expected line offset after leading '+' or '-' in fix-it verification
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{1--}}

// CHECK: [[@LINE+1]]:83: error: expected '=' after range in fix-it verification
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{1-1}}

// CHECK: [[@LINE+1]]:85: error: expected '=' after range in fix-it verification
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{1:1-1}}

// CHECK: [[@LINE+1]]:86: error: expected '=' after range in fix-it verification
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{+1:1-1}}

// CHECK: [[@LINE+1]]:83: error: expected line offset after leading '+' or '-' in fix-it verification
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{1--:}}

// CHECK: [[@LINE+1]]:84: error: expected column number after ':' in fix-it verification
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{1-1:}}

// CHECK: [[@LINE+1]]:86: error: expected column number after ':' in fix-it verification
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{1:1-1:}}

// CHECK: [[@LINE+1]]:87: error: expected column number after ':' in fix-it verification
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{+1:1-1:}}

// CHECK: [[@LINE+1]]:85: error: expected '=' after range in fix-it verification
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{1-1:1}}

// CHECK: [[@LINE+1]]:86: error: expected '=' after range in fix-it verification
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{1-+1:1}}

// CHECK: [[@LINE+1]]:87: error: expected '=' after range in fix-it verification
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{1:1-1:1}}

// CHECK: [[@LINE+1]]:89: error: expected '=' after range in fix-it verification
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{+1:1--1:1}}

// CHECK: [[@LINE+1]]:78: error: expected fix-it not seen
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{1-1=}}

// CHECK: [[@LINE+1]]:78: error: expected fix-it not seen
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{1-1=a}}

// CHECK: [[@LINE+1]]:80: error: invalid column number in fix-it verification
// CHECK: [[@LINE+1]]:80: error: expected line or column number in fix-it verification
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{x-1=a}}

// CHECK: [[@LINE+1]]:82: error: invalid column number in fix-it verification
// CHECK: [[@LINE+1]]:82: error: expected line or column number in fix-it verification
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{1-x=a}}

// CHECK: [[@LINE+1]]:82: error: invalid column number in fix-it verification
// CHECK: [[@LINE+1]]:82: error: expected column number after ':' in fix-it verification
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{1:x-1=a}}

// CHECK: [[@LINE+1]]:80: error: invalid line number in fix-it verification
// CHECK: [[@LINE+1]]:80: error: expected line or column number in fix-it verification
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{x:1-1=a}}

// CHECK: [[@LINE+1]]:84: error: invalid column number in fix-it verification
// CHECK: [[@LINE+1]]:81: error: expected line offset after leading '+' or '-' in fix-it verification
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{+x:1-1=a}}

// CHECK: [[@LINE+1]]:84: error: expected column number after ':' in fix-it verification
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{1-1:x=a}}

// CHECK: [[@LINE+1]]:82: error: invalid line number in fix-it verification
// CHECK: [[@LINE+1]]:82: error: expected line or column number in fix-it verification
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{1-x:1=a}}

// CHECK: [[@LINE+1]]:82: error: invalid column number in fix-it verification
// CHECK: [[@LINE+1]]:83: error: expected line offset after leading '+' or '-' in fix-it verification
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{1-+x:1=a}}

// CHECK: [[@LINE+1]]:82: error: expected column number after ':' in fix-it verification
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{1:x-1:x=a}}

// CHECK: [[@LINE+1]]:80: error: invalid line number in fix-it verification
// CHECK: [[@LINE+1]]:80: error: expected line or column number in fix-it verification
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{x:1-1:x=a}}

// CHECK: [[@LINE+1]]:82: error: invalid column number in fix-it verification
// CHECK: [[@LINE+1]]:81: error: expected line offset after leading '+' or '-' in fix-it verification
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{+x:1-1:x=a}}

// CHECK: [[@LINE+1]]:82: error: expected column number after ':' in fix-it verification
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{1:x-x:1=a}}

// CHECK: [[@LINE+1]]:82: error: expected column number after ':' in fix-it verification
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{1:x--x:1=a}}

// CHECK: [[@LINE+1]]:78: error: expected fix-it not seen
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{1:1-1:1=a}}

Expand All @@ -54,6 +147,15 @@ func test0Fixits() {
// CHECK: [[@LINE+1]]:78: error: expected fix-it not seen
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{1:1-1=a}}

// CHECK: [[@LINE+1]]:78: error: expected fix-it not seen
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{-1:1-1=a}}

// CHECK: [[@LINE+1]]:78: error: expected fix-it not seen
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{1-+1:1=a}}

// CHECK: [[@LINE+1]]:78: error: expected fix-it not seen
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{+1:1-+1:1=a}}

// CHECK: [[@LINE+1]]:78: error: expected fix-it not seen
undefinedFunc() // expected-error {{cannot find 'undefinedFunc' in scope}} {{1-1=a}} {{2-2=b}}

Expand Down Expand Up @@ -95,13 +197,32 @@ func test1Fixits() {
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}}

// CHECK-NOT: [[@LINE+1]]:{{[0-9]+}}: error:
labeledFunc(aax: 0, bb: 1) // expected-error {{incorrect argument label in call (have 'aax:bb:', expected 'aa:bb:')}} {{98:15-98:18=aa}}
labeledFunc(aax: 0, bb: 1) // expected-error {{incorrect argument label in call (have 'aax:bb:', expected 'aa:bb:')}} {{200:15-200:18=aa}}
// CHECK-NOT: [[@LINE+1]]:{{[0-9]+}}: error:
labeledFunc(aax: 0, bb: 1) // expected-error {{incorrect argument label in call (have 'aax:bb:', expected 'aa:bb:')}} {{202:15-18=aa}}
// CHECK-NOT: [[@LINE+1]]:{{[0-9]+}}: error:
labeledFunc(aax: 0, bb: 1) // expected-error {{incorrect argument label in call (have 'aax:bb:', expected 'aa:bb:')}} {{15-204:18=aa}}
// CHECK-NOT: [[@LINE+1]]:{{[0-9]+}}: error:
labeledFunc(aax: 0, bb: 1) // expected-error {{incorrect argument label in call (have 'aax:bb:', expected 'aa:bb:')}} {{-0:15-+0:18=aa}}
// CHECK-NOT: [[@LINE+1]]:{{[0-9]+}}: error:
labeledFunc(aax: 0, bb: 1) // expected-error {{incorrect argument label in call (have 'aax:bb:', expected 'aa:bb:')}} {{15--0:18=aa}}
// CHECK-NOT: [[@LINE+1]]:{{[0-9]+}}: error:
labeledFunc(aax: 0, bb: 1) // expected-error {{incorrect argument label in call (have 'aax:bb:', expected 'aa:bb:')}} {{100:15-18=aa}}
labeledFunc(aax: 0, bb: 1) // expected-error {{incorrect argument label in call (have 'aax:bb:', expected 'aa:bb:')}} {+0:15-210:18=aa}}
// CHECK-NOT: [[@LINE+1]]:{{[0-9]+}}: error:
labeledFunc(aax: 0, bb: 1) // expected-error {{incorrect argument label in call (have 'aax:bb:', expected 'aa:bb:')}} {{15-102:18=aa}}
labeledFunc(aa: 0, // expected-error {{incorrect argument label in call (have 'aa:bbx:', expected 'aa:bb:')}} {{+1:15-+1:18=bb}}
bbx: 1)
// CHECK-NOT: [[@LINE+1]]:{{[0-9]+}}: error:
labeledFunc(aa: 0, // expected-error {{incorrect argument label in call (have 'aa:bbx:', expected 'aa:bb:')}} {{216:15-+1:18=bb}}
bbx: 1)

// CHECK: [[@LINE+1]]:121: error: expected fix-it not seen; actual fix-it seen: {{{{}}15-18=aa}}
labeledFunc(aax: 0, bb: 1) // expected-error {{incorrect argument label in call (have 'aax:bb:', expected 'aa:bb:')}} {{61:15-18=aa}}
// CHECK: [[@LINE+1]]:121: error: expected fix-it not seen; actual fix-it seen: {{{{}}15-18=aa}}
labeledFunc(aax: 0, bb: 1) // expected-error {{incorrect argument label in call (have 'aax:bb:', expected 'aa:bb:')}} {{-1:15-18=aa}}
// CHECK: [[@LINE+1]]:121: error: expected fix-it not seen; actual fix-it seen: {{{{}}15-18=aa}}
labeledFunc(aax: 0, bb: 1) // expected-error {{incorrect argument label in call (have 'aax:bb:', expected 'aa:bb:')}} {{+0:15--1:18=aa}}
// CHECK: [[@LINE+1]]:121: error: expected fix-it not seen; actual fix-it seen: {{{{}}15-18=aa}}
labeledFunc(aax: 0, bb: 1) // expected-error {{incorrect argument label in call (have 'aax:bb:', expected 'aa:bb:')}} {{61:15-+1:18=aa}}
}

func test2Fixits() {
Expand Down
Loading