From e3d92dbf0f3f898bbcf3f28bbffefe1c7ff64806 Mon Sep 17 00:00:00 2001 From: "Henrik G. Olsson" Date: Wed, 17 Sep 2025 14:58:30 -0700 Subject: [PATCH] [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}} }} */ ``` --- include/swift/Frontend/DiagnosticVerifier.h | 18 +- lib/Frontend/DiagnosticVerifier.cpp | 395 ++++++++++++------ .../Inputs/syntax_macro_definitions.swift | 4 +- .../Inputs/top_level_freestanding_other.swift | 1 + test/Macros/top_level_freestanding.swift | 19 +- 5 files changed, 298 insertions(+), 139 deletions(-) diff --git a/include/swift/Frontend/DiagnosticVerifier.h b/include/swift/Frontend/DiagnosticVerifier.h index 6eb5333eaa0..b2546924868 100644 --- a/include/swift/Frontend/DiagnosticVerifier.h +++ b/include/swift/Frontend/DiagnosticVerifier.h @@ -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 &CapturedDiagnostics) const; + std::vector 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 &Errors, unsigned &PrevExpectedContinuationLine, - ExpectedDiagnosticInfo &Expected) const; + ExpectedDiagnosticInfo &Expected); + void + verifyDiagnostics(std::vector &ExpectedDiagnostics, + unsigned BufferID); + void verifyRemaining(std::vector &ExpectedDiagnostics, + const char *FileStart); + void addError(const char *Loc, const Twine &message, + ArrayRef FixIts = {}); + + std::optional + parseExpectedFixItRange(StringRef &Str, unsigned DiagnosticLineNo); bool checkForFixIt(const std::vector &ExpectedAlts, const CapturedDiagnosticInfo &D, unsigned BufferID) const; @@ -149,6 +161,8 @@ private: std::string renderFixits(ArrayRef ActualFixIts, unsigned BufferID, unsigned DiagnosticLineNo) const; + llvm::DenseMap Expansions; + void printRemainingDiagnostics() const; }; diff --git a/lib/Frontend/DiagnosticVerifier.cpp b/lib/Frontend/DiagnosticVerifier.cpp index d234a95945e..3db79fa20a9 100644 --- a/lib/Frontend/DiagnosticVerifier.cpp +++ b/lib/Frontend/DiagnosticVerifier.cpp @@ -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 DocumentationFile; + std::vector 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::iterator, bool> findDiagnostic(std::vector &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 ActualFixIts, /// /// \param DiagnosticLineNo The line number of the associated expected /// diagnostic; used to turn line offsets into line numbers. -static std::optional parseExpectedFixItRange( - StringRef &Str, unsigned DiagnosticLineNo, - llvm::function_ref diagnoseError) { +std::optional +DiagnosticVerifier::parseExpectedFixItRange(StringRef &Str, + unsigned DiagnosticLineNo) { assert(!Str.empty()); struct ParsedLineAndColumn { @@ -497,10 +523,10 @@ static std::optional 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 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 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 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 &Errors, + unsigned BufferID, StringRef MatchStartIn, unsigned &PrevExpectedContinuationLine, - ExpectedDiagnosticInfo &Expected) const { - auto addError = [&](const char *Loc, const Twine &message, - ArrayRef 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 Errors; - - unsigned PrevExpectedContinuationLine = 0; - - std::vector ExpectedDiagnostics; - - auto addError = [&](const char *Loc, const Twine &message, - ArrayRef 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 &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 &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 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 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 &Expansions, + std::vector &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 BufferIDLists[2] = { BufferIDs, additionalBufferIDs }; for (ArrayRef BufferIDList : BufferIDLists) for (auto &BufferID : BufferIDList) { diff --git a/test/Macros/Inputs/syntax_macro_definitions.swift b/test/Macros/Inputs/syntax_macro_definitions.swift index 7eee73fb6ca..f4650238055 100644 --- a/test/Macros/Inputs/syntax_macro_definitions.swift +++ b/test/Macros/Inputs/syntax_macro_definitions.swift @@ -2051,7 +2051,7 @@ public struct DefineAnonymousTypesMacro: DeclarationMacro { results += [""" - struct \(context.makeUniqueName("name")) where T == Equatable { // expect error: need 'any' + struct \(context.makeUniqueName("name")) 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")) where T == Hashable { // expect error: need 'any' + struct \(context.makeUniqueName("name")) where T == Hashable { // expected-warning{{must be written 'any Hashable'}} } } """ diff --git a/test/Macros/Inputs/top_level_freestanding_other.swift b/test/Macros/Inputs/top_level_freestanding_other.swift index c4a296a34fe..e7539e3f6f0 100644 --- a/test/Macros/Inputs/top_level_freestanding_other.swift +++ b/test/Macros/Inputs/top_level_freestanding_other.swift @@ -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}} + diff --git a/test/Macros/top_level_freestanding.swift b/test/Macros/top_level_freestanding.swift index 1c5cd0d17bb..bfe678e7483 100644 --- a/test/Macros/top_level_freestanding.swift +++ b/test/Macros/top_level_freestanding.swift @@ -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) } +