[DiagnosticVerifier] implement expected-expansion

Since freestanding macro expansion buffer names include the line number
of their invocation, it can become quite fiddly to try to update a test
file with multiple macro expansions. This adds the option to use an
expected-expansion block and use a relative line number, nesting other
expected diagnostic statements inside this block.

Example syntax:
```swift
let myVar = #myMacro
/*
expected-expansion@-2:1{{
  expected-error@13:37{{I can't believe you've done this}}
  expected-note@14:38{{look at this and ponder your mistake}}
}}
*/
```
This commit is contained in:
Henrik G. Olsson
2025-09-17 14:58:30 -07:00
parent da56a52fbd
commit e3d92dbf0f
5 changed files with 298 additions and 139 deletions

View File

@@ -18,6 +18,7 @@
#ifndef SWIFT_FRONTEND_DIAGNOSTIC_VERIFIER_H
#define SWIFT_FRONTEND_DIAGNOSTIC_VERIFIER_H
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/SmallString.h"
#include "swift/AST/DiagnosticConsumer.h"
#include "swift/Basic/LLVM.h"
@@ -133,14 +134,25 @@ private:
bool
verifyUnknown(std::vector<CapturedDiagnosticInfo> &CapturedDiagnostics) const;
std::vector<llvm::SMDiagnostic> Errors;
/// verifyFile - 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.
Result verifyFile(unsigned BufferID);
unsigned parseExpectedDiagInfo(unsigned BufferID, StringRef MatchStart,
std::vector<llvm::SMDiagnostic> &Errors,
unsigned &PrevExpectedContinuationLine,
ExpectedDiagnosticInfo &Expected) const;
ExpectedDiagnosticInfo &Expected);
void
verifyDiagnostics(std::vector<ExpectedDiagnosticInfo> &ExpectedDiagnostics,
unsigned BufferID);
void verifyRemaining(std::vector<ExpectedDiagnosticInfo> &ExpectedDiagnostics,
const char *FileStart);
void addError(const char *Loc, const Twine &message,
ArrayRef<llvm::SMFixIt> FixIts = {});
std::optional<LineColumnRange>
parseExpectedFixItRange(StringRef &Str, unsigned DiagnosticLineNo);
bool checkForFixIt(const std::vector<ExpectedFixIt> &ExpectedAlts,
const CapturedDiagnosticInfo &D, unsigned BufferID) const;
@@ -149,6 +161,8 @@ private:
std::string renderFixits(ArrayRef<CapturedFixItInfo> ActualFixIts,
unsigned BufferID, unsigned DiagnosticLineNo) const;
llvm::DenseMap<SourceLoc, unsigned> Expansions;
void printRemainingDiagnostics() const;
};

View File

@@ -28,6 +28,8 @@
using namespace swift;
const DiagnosticKind DiagnosticKindExpansion = DiagnosticKind((int)DiagnosticKind::Note + 1);
namespace {
struct ExpectedCheckMatchStartParser {
@@ -67,6 +69,13 @@ struct ExpectedCheckMatchStartParser {
return true;
}
if (MatchStart.starts_with("expansion")) {
ClassificationStartLoc = MatchStart.data();
ExpectedClassification = DiagnosticKindExpansion;
MatchStart = MatchStart.substr(strlen("expansion"));
return true;
}
return false;
}
@@ -221,6 +230,8 @@ struct ExpectedDiagnosticInfo {
};
std::optional<ExpectedDocumentationFile> DocumentationFile;
std::vector<ExpectedDiagnosticInfo> NestedDiags = {};
ExpectedDiagnosticInfo(const char *ExpectedStart,
const char *ClassificationStart,
const char *ClassificationEnd,
@@ -239,6 +250,8 @@ static std::string getDiagKindString(DiagnosticKind Kind) {
return "note";
case DiagnosticKind::Remark:
return "remark";
case DiagnosticKindExpansion:
return "expansion";
}
llvm_unreachable("Unhandled DiagKind in switch.");
@@ -259,7 +272,7 @@ renderDocumentationFile(const std::string &documentationFile) {
/// Otherwise return \c CapturedDiagnostics.end() with \c false.
static std::tuple<std::vector<CapturedDiagnosticInfo>::iterator, bool>
findDiagnostic(std::vector<CapturedDiagnosticInfo> &CapturedDiagnostics,
const ExpectedDiagnosticInfo &Expected, unsigned BufferID) {
const ExpectedDiagnosticInfo &Expected, unsigned BufferID, SourceManager &SM) {
auto fallbackI = CapturedDiagnostics.end();
for (auto I = CapturedDiagnostics.begin(), E = CapturedDiagnostics.end();
@@ -418,7 +431,20 @@ void DiagnosticVerifier::printDiagnostic(const llvm::SMDiagnostic &Diag) const {
raw_ostream &stream = llvm::errs();
ColoredStream coloredStream{stream};
raw_ostream &out = UseColor ? coloredStream : stream;
SM.getLLVMSourceMgr().PrintMessage(out, Diag);
llvm::SourceMgr &Underlying = SM.getLLVMSourceMgr();
Underlying.PrintMessage(out, Diag);
SourceLoc Loc = SourceLoc::getFromPointer(Diag.getLoc().getPointer());
if (Loc.isInvalid())
return;
unsigned BufferID = SM.findBufferContainingLoc(Loc);
if (const GeneratedSourceInfo *GSI = SM.getGeneratedSourceInfo(BufferID)) {
SourceLoc ParentLoc = GSI->originalSourceRange.getStart();
if (ParentLoc.isInvalid())
return;
printDiagnostic(SM.GetMessage(ParentLoc, llvm::SourceMgr::DK_Note,
"in expansion from here", {}, {}));
}
}
std::string
@@ -465,9 +491,9 @@ DiagnosticVerifier::renderFixits(ArrayRef<CapturedFixItInfo> ActualFixIts,
///
/// \param DiagnosticLineNo The line number of the associated expected
/// diagnostic; used to turn line offsets into line numbers.
static std::optional<LineColumnRange> parseExpectedFixItRange(
StringRef &Str, unsigned DiagnosticLineNo,
llvm::function_ref<void(const char *, const Twine &)> diagnoseError) {
std::optional<LineColumnRange>
DiagnosticVerifier::parseExpectedFixItRange(StringRef &Str,
unsigned DiagnosticLineNo) {
assert(!Str.empty());
struct ParsedLineAndColumn {
@@ -497,10 +523,10 @@ static std::optional<LineColumnRange> parseExpectedFixItRange(
unsigned FirstVal = 0;
if (Str.consumeInteger(10, FirstVal)) {
if (LineOffsetKind == OffsetKind::None) {
diagnoseError(Str.data(),
addError(Str.data(),
"expected line or column number in fix-it verification");
} else {
diagnoseError(Str.data(),
addError(Str.data(),
"expected line offset after leading '+' or '-' in fix-it "
"verification");
}
@@ -514,7 +540,7 @@ static std::optional<LineColumnRange> parseExpectedFixItRange(
return ParsedLineAndColumn{std::nullopt, FirstVal};
}
diagnoseError(Str.data(),
addError(Str.data(),
"expected colon-separated column number after line offset "
"in fix-it verification");
return std::nullopt;
@@ -523,7 +549,7 @@ static std::optional<LineColumnRange> parseExpectedFixItRange(
unsigned Column = 0;
Str = Str.drop_front();
if (Str.consumeInteger(10, Column)) {
diagnoseError(Str.data(),
addError(Str.data(),
"expected column number after ':' in fix-it verification");
return std::nullopt;
}
@@ -556,7 +582,7 @@ static std::optional<LineColumnRange> parseExpectedFixItRange(
if (!Str.empty() && Str.front() == '-') {
Str = Str.drop_front();
} else {
diagnoseError(Str.data(),
addError(Str.data(),
"expected '-' range separator in fix-it verification");
return std::nullopt;
}
@@ -607,20 +633,13 @@ static bool parseTargetBufferName(StringRef &MatchStart, StringRef &Out, size_t
}
unsigned DiagnosticVerifier::parseExpectedDiagInfo(
unsigned BufferID, StringRef MatchStart,
std::vector<llvm::SMDiagnostic> &Errors,
unsigned BufferID, StringRef MatchStartIn,
unsigned &PrevExpectedContinuationLine,
ExpectedDiagnosticInfo &Expected) const {
auto addError = [&](const char *Loc, const Twine &message,
ArrayRef<llvm::SMFixIt> FixIts = {}) {
auto loc = SourceLoc::getFromPointer(Loc);
auto diag = SM.GetMessage(loc, llvm::SourceMgr::DK_Error, message,
{}, FixIts);
Errors.push_back(diag);
};
ExpectedDiagnosticInfo &Expected) {
const SourceLoc BufferStartLoc = SM.getLocForBufferStart(BufferID);
StringRef InputFile = SM.getEntireTextForBuffer(BufferID);
StringRef MatchStart = MatchStartIn;
const char *DiagnosticLoc = MatchStart.data();
MatchStart = MatchStart.substr(strlen("expected-"));
@@ -654,9 +673,10 @@ unsigned DiagnosticVerifier::parseExpectedDiagInfo(
/*ClassificationEndLoc=*/MatchStart.data(),
*ExpectedClassification);
int LineOffset = 0;
bool AbsoluteLine = false;
if (TextStartIdx > 0 && MatchStart[0] == '@') {
if (MatchStart[1] != '+' && MatchStart[1] != '-' && MatchStart[1] != ':') {
if (MatchStart[1] != '+' && MatchStart[1] != '-' && MatchStart[1] != ':' && (MatchStart[1] < '0' || MatchStart[1] > '9')) {
StringRef TargetBufferName;
if (!parseTargetBufferName(MatchStart, TargetBufferName, TextStartIdx)) {
addError(MatchStart.data(), "expected '+'/'-' for line offset, ':' "
@@ -669,12 +689,20 @@ unsigned DiagnosticVerifier::parseExpectedDiagInfo(
"no buffer with name '" + TargetBufferName + "' found");
return 0;
}
if (MatchStart[0] != ':' || MatchStart[1] < '0' || MatchStart[1] > '9') {
addError(MatchStart.data(),
"expected absolute line number for diagnostic in other buffer");
return 0;
}
}
StringRef Offs;
if (MatchStart[1] == '+')
Offs = MatchStart.slice(2, TextStartIdx).rtrim();
else
else {
Offs = MatchStart.slice(1, TextStartIdx).rtrim();
if (Offs[0] != '-')
AbsoluteLine = true;
}
size_t SpaceIndex = Offs.find(' ');
if (SpaceIndex != StringRef::npos && SpaceIndex < TextStartIdx) {
@@ -709,6 +737,11 @@ unsigned DiagnosticVerifier::parseExpectedDiagInfo(
}
}
if (Expected.Classification == DiagnosticKindExpansion && !Expected.ColumnNo.has_value()) {
addError(DiagnosticLoc, "expected-expansion requires column location");
return 0;
}
unsigned Count = 1;
if (TextStartIdx > 0) {
StringRef CountStr = MatchStart.substr(0, TextStartIdx).trim(" \t");
@@ -730,19 +763,62 @@ unsigned DiagnosticVerifier::parseExpectedDiagInfo(
MatchStart = MatchStart.substr(TextStartIdx);
}
size_t End = MatchStart.find("}}");
if (End == StringRef::npos) {
addError(
MatchStart.data(),
"didn't find '}}' to match '{{' in expected-warning/note/error line");
return 0;
size_t End = StringRef::npos;
if (Expected.Classification == DiagnosticKindExpansion) {
size_t NestedMatch = MatchStart.find("expected-");
// Scan the memory buffer looking for expected-note/warning/error.
while (NestedMatch != StringRef::npos) {
StringRef NestedMatchStart = MatchStart.substr(NestedMatch);
ExpectedDiagnosticInfo NestedExpected(nullptr, nullptr, nullptr,
DiagnosticKind(-1));
unsigned NestedCount =
parseExpectedDiagInfo(BufferID, NestedMatchStart,
PrevExpectedContinuationLine, NestedExpected);
size_t PrevMatchEnd = NestedMatch + 1;
if (NestedCount > 0) {
// Add the diagnostic the expected number of times.
for (; NestedCount; --NestedCount)
Expected.NestedDiags.push_back(NestedExpected);
size_t NestedMatchEnd =
NestedExpected.ExpectedEnd - NestedMatchStart.data();
assert(NestedMatchEnd > 0);
PrevMatchEnd = NestedMatch + NestedMatchEnd;
}
size_t NextEnd = MatchStart.find("}}", PrevMatchEnd);
NestedMatch = MatchStart.find("expected-", PrevMatchEnd);
if (NextEnd < NestedMatch) {
End = NextEnd;
break;
}
}
if (End == StringRef::npos) {
addError(
DiagnosticLoc,
"didn't find '}}' to match '{{' in expected-expansion");
return 0;
}
if (Expected.NestedDiags.size() == 0) {
addError(DiagnosticLoc, "expected-expansion block is empty");
// Keep going
}
} else {
End = MatchStart.find("}}");
if (End == StringRef::npos) {
addError(
MatchStart.data(),
"didn't find '}}' to match '{{' in expected-warning/note/error line");
return 0;
}
}
llvm::SmallString<256> Buf;
Expected.MessageRange = MatchStart.slice(2, End);
Expected.MessageStr =
Lexer::getEncodedStringSegment(Expected.MessageRange, Buf).str();
if (Expected.TargetBufferID)
if (AbsoluteLine)
Expected.LineNo = 0;
else if (PrevExpectedContinuationLine)
Expected.LineNo = PrevExpectedContinuationLine;
@@ -861,7 +937,7 @@ unsigned DiagnosticVerifier::parseExpectedDiagInfo(
FixIt.EndLoc = CloseLoc;
if (const auto range =
parseExpectedFixItRange(CheckStr, Expected.LineNo, addError)) {
parseExpectedFixItRange(CheckStr, Expected.LineNo)) {
FixIt.Range = range.value();
} else {
return 0;
@@ -893,6 +969,9 @@ unsigned DiagnosticVerifier::parseExpectedDiagInfo(
}
}
if (Expected.Classification == DiagnosticKindExpansion) {
addError(OpenLoc, "expected-expansion cannot have fixits");
}
Expected.Fixits.back().push_back(FixIt);
}
@@ -909,50 +988,7 @@ unsigned DiagnosticVerifier::parseExpectedDiagInfo(
return Count;
}
/// 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.
DiagnosticVerifier::Result DiagnosticVerifier::verifyFile(unsigned BufferID) {
using llvm::SMLoc;
StringRef InputFile = SM.getEntireTextForBuffer(BufferID);
// Queue up all of the diagnostics, allowing us to sort them and emit them in
// file order.
std::vector<llvm::SMDiagnostic> Errors;
unsigned PrevExpectedContinuationLine = 0;
std::vector<ExpectedDiagnosticInfo> ExpectedDiagnostics;
auto addError = [&](const char *Loc, const Twine &message,
ArrayRef<llvm::SMFixIt> FixIts = {}) {
auto loc = SourceLoc::getFromPointer(Loc);
auto diag = SM.GetMessage(loc, llvm::SourceMgr::DK_Error, message,
{}, FixIts);
Errors.push_back(diag);
};
// Validate that earlier prefixes are not prefixes of alter
// prefixes... otherwise, we will never pattern match the later prefix.
validatePrefixList(AdditionalExpectedPrefixes);
// Scan the memory buffer looking for expected-note/warning/error.
for (size_t Match = InputFile.find("expected-");
Match != StringRef::npos; Match = InputFile.find("expected-", Match+1)) {
// Process this potential match. If we fail to process it, just move on to
// the next match.
StringRef MatchStart = InputFile.substr(Match);
ExpectedDiagnosticInfo Expected(nullptr, nullptr, nullptr, DiagnosticKind(-1));
unsigned Count = parseExpectedDiagInfo(BufferID, MatchStart, Errors, PrevExpectedContinuationLine, Expected);
if (Count < 1)
continue;
// Add the diagnostic the expected number of times.
for (; Count; --Count)
ExpectedDiagnostics.push_back(Expected);
}
void DiagnosticVerifier::verifyDiagnostics(std::vector<ExpectedDiagnosticInfo> &ExpectedDiagnostics, unsigned BufferID) {
// Make sure all the expected diagnostics appeared.
std::reverse(ExpectedDiagnostics.begin(), ExpectedDiagnostics.end());
@@ -962,8 +998,22 @@ DiagnosticVerifier::Result DiagnosticVerifier::verifyFile(unsigned BufferID) {
unsigned ID = expected.TargetBufferID.value_or(BufferID);
// Check to see if we had this expected diagnostic.
if (expected.Classification == DiagnosticKindExpansion) {
SourceLoc Loc = SM.getLocForLineCol(BufferID, expected.LineNo, *expected.ColumnNo);
if (Expansions.count(Loc) == 0) {
addError(expected.ExpectedStart,
"no expansion with diagnostics starting at " +
std::to_string(expected.LineNo) + ":" + std::to_string(*expected.ColumnNo));
continue;
}
unsigned ExpansionBufferID = Expansions[Loc];
verifyDiagnostics(expected.NestedDiags, ExpansionBufferID);
if (expected.NestedDiags.empty())
ExpectedDiagnostics.erase(ExpectedDiagnostics.begin()+i);
continue;
}
auto FoundDiagnosticInfo =
findDiagnostic(CapturedDiagnostics, expected, ID);
findDiagnostic(CapturedDiagnostics, expected, ID, SM);
auto FoundDiagnosticIter = std::get<0>(FoundDiagnosticInfo);
if (FoundDiagnosticIter == CapturedDiagnostics.end()) {
// Diagnostic didn't exist. If this is a 'mayAppear' diagnostic, then
@@ -976,8 +1026,8 @@ DiagnosticVerifier::Result DiagnosticVerifier::verifyFile(unsigned BufferID) {
auto emitFixItsError = [&](const char *location, const Twine &message,
const char *replStartLoc, const char *replEndLoc,
const std::string &replStr) {
llvm::SMFixIt fix(llvm::SMRange(SMLoc::getFromPointer(replStartLoc),
SMLoc::getFromPointer(replEndLoc)),
llvm::SMFixIt fix(llvm::SMRange(llvm::SMLoc::getFromPointer(replStartLoc),
llvm::SMLoc::getFromPointer(replEndLoc)),
replStr);
addError(location, message, fix);
};
@@ -1118,8 +1168,8 @@ DiagnosticVerifier::Result DiagnosticVerifier::verifyFile(unsigned BufferID) {
// produce a fixit of our own.
auto actual =
renderDocumentationFile(FoundDiagnostic.CategoryDocFile);
auto replStartLoc = SMLoc::getFromPointer(expectedDocFile->StartLoc);
auto replEndLoc = SMLoc::getFromPointer(expectedDocFile->EndLoc);
auto replStartLoc = llvm::SMLoc::getFromPointer(expectedDocFile->StartLoc);
auto replEndLoc = llvm::SMLoc::getFromPointer(expectedDocFile->EndLoc);
llvm::SMFixIt fix(llvm::SMRange(replStartLoc, replEndLoc), actual);
addError(expectedDocFile->StartLoc,
@@ -1141,7 +1191,115 @@ DiagnosticVerifier::Result DiagnosticVerifier::verifyFile(unsigned BufferID) {
else
ExpectedDiagnostics.erase(ExpectedDiagnostics.begin()+i);
}
}
void DiagnosticVerifier::verifyRemaining(
std::vector<ExpectedDiagnosticInfo> &ExpectedDiagnostics,
const char *FileStart) {
std::reverse(ExpectedDiagnostics.begin(), ExpectedDiagnostics.end());
for (auto &expected : ExpectedDiagnostics) {
if (expected.Classification == DiagnosticKindExpansion) {
verifyRemaining(expected.NestedDiags, FileStart);
continue;
}
std::string message = "expected "+getDiagKindString(expected.Classification)
+ " not produced";
// Get the range of the expected-foo{{}} diagnostic specifier.
auto StartLoc = expected.ExpectedStart;
auto EndLoc = expected.ExpectedEnd;
// A very common case if for the specifier to be the last thing on the line.
// In this case, eat any trailing whitespace.
while (isspace(*EndLoc) && *EndLoc != '\n' && *EndLoc != '\r')
++EndLoc;
// If we found the end of the line, we can do great things. Otherwise,
// avoid nuking whitespace that might be zapped through other means.
if (*EndLoc != '\n' && *EndLoc != '\r') {
EndLoc = expected.ExpectedEnd;
} else {
// If we hit the end of line, then zap whitespace leading up to it.
while (StartLoc-1 != FileStart && isspace(StartLoc[-1]) &&
StartLoc[-1] != '\n' && StartLoc[-1] != '\r')
--StartLoc;
// If we got to the end of the line, and the thing before this diagnostic
// is a "//" then we can remove it too.
if (StartLoc-2 >= FileStart && StartLoc[-1] == '/' && StartLoc[-2] == '/')
StartLoc -= 2;
// Perform another round of general whitespace nuking to cleanup
// whitespace before the //.
while (StartLoc-1 != FileStart && isspace(StartLoc[-1]) &&
StartLoc[-1] != '\n' && StartLoc[-1] != '\r')
--StartLoc;
// If we found a \n, then we can nuke the entire line.
if (StartLoc-1 != FileStart &&
(StartLoc[-1] == '\n' || StartLoc[-1] == '\r'))
--StartLoc;
}
// Remove the expected-foo{{}} as a fixit.
llvm::SMFixIt fixIt(llvm::SMRange{
llvm::SMLoc::getFromPointer(StartLoc),
llvm::SMLoc::getFromPointer(EndLoc)
}, "");
addError(expected.ExpectedStart, message, fixIt);
}
}
void DiagnosticVerifier::addError(const char *Loc, const Twine &message,
ArrayRef<llvm::SMFixIt> FixIts) {
auto loc = SourceLoc::getFromPointer(Loc);
auto diag =
SM.GetMessage(loc, llvm::SourceMgr::DK_Error, message, {}, FixIts);
Errors.push_back(diag);
}
/// 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.
DiagnosticVerifier::Result DiagnosticVerifier::verifyFile(unsigned BufferID) {
Errors.clear();
using llvm::SMLoc;
StringRef InputFile = SM.getEntireTextForBuffer(BufferID);
// Queue up all of the diagnostics, allowing us to sort them and emit them in
// file order.
unsigned PrevExpectedContinuationLine = 0;
std::vector<ExpectedDiagnosticInfo> ExpectedDiagnostics;
// Validate that earlier prefixes are not prefixes of alter
// prefixes... otherwise, we will never pattern match the later prefix.
validatePrefixList(AdditionalExpectedPrefixes);
const char *PrevMatchEnd = InputFile.data();
// Scan the memory buffer looking for expected-note/warning/error.
for (size_t Match = InputFile.find("expected-");
Match != StringRef::npos; Match = InputFile.find("expected-", Match+1)) {
// Process this potential match. If we fail to process it, just move on to
// the next match.
StringRef MatchStart = InputFile.substr(Match);
if (MatchStart.data() < PrevMatchEnd)
continue;
ExpectedDiagnosticInfo Expected(nullptr, nullptr, nullptr, DiagnosticKind(-1));
unsigned Count = parseExpectedDiagInfo(BufferID, MatchStart, PrevExpectedContinuationLine, Expected);
if (Count < 1)
continue;
// Add the diagnostic the expected number of times.
for (; Count; --Count)
ExpectedDiagnostics.push_back(Expected);
PrevMatchEnd = Expected.ExpectedEnd;
}
verifyDiagnostics(ExpectedDiagnostics, BufferID);
// Check to see if we have any incorrect diagnostics. If so, diagnose them as
// such.
auto expectedDiagIter = ExpectedDiagnostics.begin();
@@ -1154,7 +1312,7 @@ DiagnosticVerifier::Result DiagnosticVerifier::verifyFile(unsigned BufferID) {
if (I->Line != expectedDiagIter->LineNo || I->SourceBufferID != BufferID
|| I->Classification != expectedDiagIter->Classification)
continue;
// Otherwise, we found it, break out.
break;
}
@@ -1186,55 +1344,7 @@ DiagnosticVerifier::Result DiagnosticVerifier::verifyFile(unsigned BufferID) {
}
// Diagnose expected diagnostics that didn't appear.
std::reverse(ExpectedDiagnostics.begin(), ExpectedDiagnostics.end());
for (auto const &expected : ExpectedDiagnostics) {
std::string message = "expected "+getDiagKindString(expected.Classification)
+ " not produced";
// Get the range of the expected-foo{{}} diagnostic specifier.
auto StartLoc = expected.ExpectedStart;
auto EndLoc = expected.ExpectedEnd;
// A very common case if for the specifier to be the last thing on the line.
// In this case, eat any trailing whitespace.
while (isspace(*EndLoc) && *EndLoc != '\n' && *EndLoc != '\r')
++EndLoc;
// If we found the end of the line, we can do great things. Otherwise,
// avoid nuking whitespace that might be zapped through other means.
if (*EndLoc != '\n' && *EndLoc != '\r') {
EndLoc = expected.ExpectedEnd;
} else {
// If we hit the end of line, then zap whitespace leading up to it.
auto FileStart = InputFile.data();
while (StartLoc-1 != FileStart && isspace(StartLoc[-1]) &&
StartLoc[-1] != '\n' && StartLoc[-1] != '\r')
--StartLoc;
// If we got to the end of the line, and the thing before this diagnostic
// is a "//" then we can remove it too.
if (StartLoc-2 >= FileStart && StartLoc[-1] == '/' && StartLoc[-2] == '/')
StartLoc -= 2;
// Perform another round of general whitespace nuking to cleanup
// whitespace before the //.
while (StartLoc-1 != FileStart && isspace(StartLoc[-1]) &&
StartLoc[-1] != '\n' && StartLoc[-1] != '\r')
--StartLoc;
// If we found a \n, then we can nuke the entire line.
if (StartLoc-1 != FileStart &&
(StartLoc[-1] == '\n' || StartLoc[-1] == '\r'))
--StartLoc;
}
// Remove the expected-foo{{}} as a fixit.
llvm::SMFixIt fixIt(llvm::SMRange{
SMLoc::getFromPointer(StartLoc),
SMLoc::getFromPointer(EndLoc)
}, "");
addError(expected.ExpectedStart, message, fixIt);
}
verifyRemaining(ExpectedDiagnostics, InputFile.data());
// Verify that there are no diagnostics (in MemoryBuffer) left in the list.
bool HadUnexpectedDiag = false;
@@ -1250,7 +1360,7 @@ DiagnosticVerifier::Result DiagnosticVerifier::verifyFile(unsigned BufferID) {
// buffer also count as part of this buffer for this purpose.
const GeneratedSourceInfo *GSI =
SM.getGeneratedSourceInfo(CapturedDiagIter->SourceBufferID.value());
if (!GSI || !llvm::find(GSI->ancestors, BufferID)) {
if (!GSI || llvm::find(GSI->ancestors, BufferID) == GSI->ancestors.end()) {
++CapturedDiagIter;
continue;
}
@@ -1314,6 +1424,27 @@ void DiagnosticVerifier::printRemainingDiagnostics() const {
}
}
static void
processExpansions(SourceManager &SM, llvm::DenseMap<SourceLoc, unsigned> &Expansions,
std::vector<CapturedDiagnosticInfo> &CapturedDiagnostics) {
for (auto &diag : CapturedDiagnostics) {
if (!diag.SourceBufferID.has_value())
continue;
const GeneratedSourceInfo *GSI =
SM.getGeneratedSourceInfo(diag.SourceBufferID.value());
if (!GSI)
continue;
SourceLoc ExpansionStart = GSI->originalSourceRange.getStart();
if (ExpansionStart.isInvalid())
continue;
if (Expansions.count(ExpansionStart)) {
ASSERT(Expansions[ExpansionStart] == diag.SourceBufferID.value());
continue;
}
Expansions.insert(std::make_pair(ExpansionStart, diag.SourceBufferID.value()));
}
}
//===----------------------------------------------------------------------===//
// Main entrypoints
//===----------------------------------------------------------------------===//
@@ -1368,6 +1499,8 @@ bool DiagnosticVerifier::finishProcessing() {
}
}
processExpansions(SM, Expansions, CapturedDiagnostics);
ArrayRef<unsigned> BufferIDLists[2] = { BufferIDs, additionalBufferIDs };
for (ArrayRef<unsigned> BufferIDList : BufferIDLists)
for (auto &BufferID : BufferIDList) {

View File

@@ -2051,7 +2051,7 @@ public struct DefineAnonymousTypesMacro: DeclarationMacro {
results += ["""
struct \(context.makeUniqueName("name"))<T> where T == Equatable { // expect error: need 'any'
struct \(context.makeUniqueName("name"))<T> where T == Equatable { // expected-warning{{must be written 'any Hashable'}}
#introduceTypeCheckingErrors // make sure we get nested errors
}
"""]
@@ -2070,7 +2070,7 @@ public struct IntroduceTypeCheckingErrorsMacro: DeclarationMacro {
"""
struct \(context.makeUniqueName("name")) {
struct \(context.makeUniqueName("name"))<T> where T == Hashable { // expect error: need 'any'
struct \(context.makeUniqueName("name"))<T> where T == Hashable { // expected-warning{{must be written 'any Hashable'}}
}
}
"""

View File

@@ -17,3 +17,4 @@ var globalVar3 = #stringify({ deprecated() })
var globalVar4 = #stringify({ deprecated() })
// expected-note@-1 {{in expansion of macro 'stringify' here}}
// expected-warning@-2{{'deprecated()' is deprecated}}

View File

@@ -94,8 +94,8 @@ func testArbitraryAtGlobal() {
}
#endif
// DIAG_BUFFERS-DAG: @__swiftmacro_9MacroUser0039top_level_freestanding_otherswift_jrGEmfMX12_17_33_7FDB3F9D78D0279543373AD342C3C331Ll9stringifyfMf1{{.*}}: warning: 'deprecated()' is deprecated
// DIAG_BUFFERS-DAG: @__swiftmacro_9MacroUser0039top_level_freestanding_otherswift_jrGEmfMX16_17_33_7FDB3F9D78D0279543373AD342C3C331Ll9stringifyfMf2{{.*}}: warning: 'deprecated()' is deprecated
// expected-warning@@__swiftmacro_9MacroUser0039top_level_freestanding_otherswift_jrGEmfMX12_17_33_7FDB3F9D78D0279543373AD342C3C331Ll9stringifyfMf1_.swift:2:9{{'deprecated()' is deprecated}}
// expected-warning@@__swiftmacro_9MacroUser0039top_level_freestanding_otherswift_jrGEmfMX16_17_33_7FDB3F9D78D0279543373AD342C3C331Ll9stringifyfMf2_.swift:2:9{{'deprecated()' is deprecated}}
#varValue
@@ -106,10 +106,20 @@ func testGlobalVariable() {
#if TEST_DIAGNOSTICS
// expected-note @+1 6 {{in expansion of macro 'anonymousTypes' here}}
// expected-note@@__swiftmacro_9MacroUser0033top_level_freestandingswift_DbGHjfMX109_0_33_082AE7CFEFA6960C804A9FE7366EB5A0Ll14anonymousTypesfMf0_.swift:24:3 3{{in expansion of macro 'introduceTypeCheckingErrors' here}}
#anonymousTypes(causeErrors: true) { "foo" }
// DIAG_BUFFERS-DAG: @__swiftmacro_9MacroUser0033top_level_freestandingswift_DbGHjfMX108_0_33_082AE7CFEFA6960C804A9FE7366EB5A0Ll14anonymousTypesfMf0_{{.*}}: warning: use of protocol 'Equatable' as a type must be written 'any Equatable'
// DIAG_BUFFERS-DAG: @__swiftmacro_9MacroUser00142___swiftmacro_9MacroUser0033top_level_freestandingswift_DbGHjfMX108_0_33_082AE7CFEFA6960C804A9FE7366EB5A0Ll14anonymousTypesfMf0_swift_DAIABdjIbfMX23_2_33_082AE7CFEFA6960C804A9FE7366EB5A0Ll27introduceTypeCheckingErrorsfMf_{{.*}}: warning: use of protocol 'Hashable' as a type must be written 'any Hashable'
// expected-expansion@-1 {{
// expected-warning@23:8{{same-type requirement makes generic parameter 'T' non-generic; this is an error in the Swift 6 language mode}}
// expected-note@23:135{{'T' previously declared here}}
// expected-warning@23:149{{use of protocol 'Equatable' as a type must be written 'any Equatable'}}
// expected-warning@5:16{{use of protocol 'Equatable' as a type must be written 'any Equatable'}}
// expected-warning@20:16{{use of protocol 'Equatable' as a type must be written 'any Equatable'}}
// expected-warning@2:273{{use of protocol 'Hashable' as a type must be written 'any Hashable'}}
// expected-warning@2:10{{same-type requirement makes generic parameter 'T' non-generic; this is an error in the Swift 6 language mode}}
// expected-warning@2:259{{generic parameter 'T' shadows generic parameter from outer scope with the same name; this is an error in the Swift 6 language mode}}
// }}
// expected-note @+1 2 {{in expansion of macro 'anonymousTypes' here}}
#anonymousTypes { () -> String in
// expected-warning @+1 {{use of protocol 'Equatable' as a type must be written 'any Equatable'}}
@@ -165,3 +175,4 @@ func testFunctionCallWithInoutParam() {
#functionCallWithTwoInoutParams(&a, &b)
#functionCallWithInoutParamPlusOthers(string: "", double: 1.0, &a)
}