From e3d4870dc4b1cf32216728f6e321ae25f33b9c53 Mon Sep 17 00:00:00 2001 From: "Henrik G. Olsson" Date: Tue, 26 Aug 2025 22:04:28 -0700 Subject: [PATCH 01/10] [Frontend] Add support for named target buffer in -verify expected lines Clang, which heavily inspired Swift's -verify flag, supports naming other files like so: ``` // expected-error@some-header.h:11:22{{some error}} ``` Swift hasn't had the same need for this, because of the lack of textual header inclusion (where the same header file can emit different diagnostics depending on where it's included). The lack of this functionality has made it impossible to use -verify in cases where diagnostics are emitted in a macro however, since the expected-lines can't be placed inside the generated macro buffer. Now the main Swift file can refer to diagnostics inside these generated buffers by naming the buffers. The generated names aren't pretty, but the identifier is stable, unique and it works. Here is an example of what it can look like: ``` // expected-error@@__swiftmacro_4main3bar.swift:10:15{{no exact matches in call to initializer}} // expected-note@+1{{in expansion of macro 'foo' on global function 'bar' here}} @foo func bar() {} ``` The double "@" is a result of the buffer name starting with an @, which is unfortunate but is how the mangling scheme works for macro buffer names. --- lib/Frontend/DiagnosticVerifier.cpp | 34 ++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/lib/Frontend/DiagnosticVerifier.cpp b/lib/Frontend/DiagnosticVerifier.cpp index 2cf53f226cf9a..bdbc500517afa 100644 --- a/lib/Frontend/DiagnosticVerifier.cpp +++ b/lib/Frontend/DiagnosticVerifier.cpp @@ -202,6 +202,7 @@ struct ExpectedDiagnosticInfo { std::string MessageStr; unsigned LineNo = ~0U; std::optional ColumnNo; + std::optional TargetBufferID; using AlternativeExpectedFixIts = std::vector; std::vector Fixits = {}; @@ -593,6 +594,18 @@ static void validatePrefixList(ArrayRef prefixes) { } } +bool parseTargetBufferName(StringRef &MatchStart, StringRef &Out, size_t &TextStartIdx) { + StringRef Offs = MatchStart.slice(0, TextStartIdx); + + size_t LineIndex = Offs.find(':'); + if (LineIndex == 0 || LineIndex == StringRef::npos) + return false; + Out = Offs.slice(1, LineIndex); + MatchStart = MatchStart.substr(LineIndex); + TextStartIdx -= LineIndex; + return true; +} + /// 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. @@ -665,9 +678,17 @@ DiagnosticVerifier::Result DiagnosticVerifier::verifyFile(unsigned BufferID) { if (TextStartIdx > 0 && MatchStart[0] == '@') { if (MatchStart[1] != '+' && MatchStart[1] != '-' && MatchStart[1] != ':') { - addError(MatchStart.data(), - "expected '+'/'-' for line offset, or ':' for column"); - continue; + StringRef TargetBufferName; + if (!parseTargetBufferName(MatchStart, TargetBufferName, TextStartIdx)) { + addError(MatchStart.data(), "expected '+'/'-' for line offset, ':' " + "for column, or a buffer name"); + continue; + } + Expected.TargetBufferID = SM.getIDForBufferIdentifier(TargetBufferName); + if (!Expected.TargetBufferID) { + addError(MatchStart.data(), "no buffer with name '" + TargetBufferName + "' found"); + continue; + } } StringRef Offs; if (MatchStart[1] == '+') @@ -740,7 +761,9 @@ DiagnosticVerifier::Result DiagnosticVerifier::verifyFile(unsigned BufferID) { Expected.MessageRange = MatchStart.slice(2, End); Expected.MessageStr = Lexer::getEncodedStringSegment(Expected.MessageRange, Buf).str(); - if (PrevExpectedContinuationLine) + if (Expected.TargetBufferID) + Expected.LineNo = 0; + else if (PrevExpectedContinuationLine) Expected.LineNo = PrevExpectedContinuationLine; else Expected.LineNo = SM.getLineAndColumnInBuffer( @@ -913,9 +936,10 @@ DiagnosticVerifier::Result DiagnosticVerifier::verifyFile(unsigned BufferID) { --i; auto &expected = ExpectedDiagnostics[i]; + unsigned ID = expected.TargetBufferID.value_or(BufferID); // Check to see if we had this expected diagnostic. auto FoundDiagnosticInfo = - findDiagnostic(CapturedDiagnostics, expected, BufferID); + findDiagnostic(CapturedDiagnostics, expected, ID); auto FoundDiagnosticIter = std::get<0>(FoundDiagnosticInfo); if (FoundDiagnosticIter == CapturedDiagnostics.end()) { // Diagnostic didn't exist. If this is a 'mayAppear' diagnostic, then From 687449bfb1f98afdc8b9602ed2827c6198d4b701 Mon Sep 17 00:00:00 2001 From: "Henrik G. Olsson" Date: Wed, 27 Aug 2025 15:26:09 -0700 Subject: [PATCH 02/10] [Frontend] Check diagnostics in generated sources The previous commit added support for checking diagnostics in other buffers, enabling diagnostic verification for e.g. macro expansions. Because the diagnostics are in a different buffer, there is no error for not marking them as "expected". This commit expands the scope to include generated sources originating in code in any checked buffer, to help make sure that errors aren't accidentally missed. --- lib/Frontend/DiagnosticVerifier.cpp | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/lib/Frontend/DiagnosticVerifier.cpp b/lib/Frontend/DiagnosticVerifier.cpp index bdbc500517afa..9c21897818fe6 100644 --- a/lib/Frontend/DiagnosticVerifier.cpp +++ b/lib/Frontend/DiagnosticVerifier.cpp @@ -594,7 +594,7 @@ static void validatePrefixList(ArrayRef prefixes) { } } -bool parseTargetBufferName(StringRef &MatchStart, StringRef &Out, size_t &TextStartIdx) { +static bool parseTargetBufferName(StringRef &MatchStart, StringRef &Out, size_t &TextStartIdx) { StringRef Offs = MatchStart.slice(0, TextStartIdx); size_t LineIndex = Offs.find(':'); @@ -1217,8 +1217,19 @@ DiagnosticVerifier::Result DiagnosticVerifier::verifyFile(unsigned BufferID) { auto CapturedDiagIter = CapturedDiagnostics.begin(); while (CapturedDiagIter != CapturedDiagnostics.end()) { if (CapturedDiagIter->SourceBufferID != BufferID) { - ++CapturedDiagIter; - continue; + if (!CapturedDiagIter->SourceBufferID) { + ++CapturedDiagIter; + continue; + } + + // Diagnostics attached to generated sources originating in this + // 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)) { + ++CapturedDiagIter; + continue; + } } HadUnexpectedDiag = true; From 70ec916d176e071e55a753ca19c97d531d4a55a9 Mon Sep 17 00:00:00 2001 From: "Henrik G. Olsson" Date: Wed, 27 Aug 2025 15:33:24 -0700 Subject: [PATCH 03/10] [Swiftify] Remove XFAIL from UnexpectedPointerType.swift test Now that the DiagnosticsVerifier supports verifying diagnostics in macro expansions it's time to enable this test case. --- .../MacroErrors/UnexpectedCountType.swift | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/test/Macros/SwiftifyImport/MacroErrors/UnexpectedCountType.swift b/test/Macros/SwiftifyImport/MacroErrors/UnexpectedCountType.swift index 5042895b4b2a3..0975709f52f97 100644 --- a/test/Macros/SwiftifyImport/MacroErrors/UnexpectedCountType.swift +++ b/test/Macros/SwiftifyImport/MacroErrors/UnexpectedCountType.swift @@ -1,17 +1,25 @@ -// REQUIRES: swift_swift_parser - -// XFAIL: * - -// RUN: not %target-swift-frontend %s -swift-version 5 -module-name main -disable-availability-checking -typecheck -plugin-path %swift-plugin-dir -dump-macro-expansions 2>&1 | %FileCheck --match-full-lines %s -// RUN: %target-typecheck-verify-swift %s -swift-version 5 -module-name main -disable-availability-checking -typecheck -plugin-path %swift-plugin-dir -verify - +//--- test.swift @_SwiftifyImport(.countedBy(pointer: .param(1), count: "len")) func myFunc(_ ptr: UnsafePointer, _ len: String) { } +// expected-note@-3 2{{in expansion of macro '_SwiftifyImport' on global function 'myFunc' here}} -// CHECK: @_alwaysEmitIntoClient @_disfavoredOverload -// CHECK-NEXT: func myFunc(_ ptr: UnsafeBufferPointer) { -// CHECK-NEXT: myFunc(ptr.baseAddress!, String(exactly: ptr.count)!) -// CHECK-NEXT: } +// expected-error@@__swiftmacro_4main6myFunc15_SwiftifyImportfMp_.swift:4:15{{no exact matches in call to initializer}} +// expected-error@@__swiftmacro_4main6myFunc15_SwiftifyImportfMp_.swift:4:48{{cannot force unwrap value of non-optional type 'String'}} -// expected-error@_SwiftifyImport:2{{no exact matches in call to initializer}} +// REQUIRES: swift_swift_parser +// RUN: %empty-directory(%t) +// RUN: split-file %s %t +// RUN: %target-swift-frontend %t/test.swift -swift-version 5 -module-name main -disable-availability-checking -typecheck -plugin-path %swift-plugin-dir -dump-macro-expansions -verify 2> %t/dump.txt +// RUN: diff %t/myFunc.expected %t/dump.txt + +//--- myFunc.expected +@__swiftmacro_4main6myFunc15_SwiftifyImportfMp_.swift +------------------------------ +/// This is an auto-generated wrapper for safer interop +@_alwaysEmitIntoClient @_disfavoredOverload +func myFunc(_ ptr: UnsafeBufferPointer) { + let len = String(exactly: unsafe ptr.count)! + return unsafe myFunc(ptr.baseAddress!, len) +} +------------------------------ From da56a52fbd910d17935d9f3fa0132734f2b8004b Mon Sep 17 00:00:00 2001 From: "Henrik G. Olsson" Date: Wed, 17 Sep 2025 12:48:18 -0700 Subject: [PATCH 04/10] [DiagnosticVerifier] Extract parseExpectedDiagInfo (NFCI) This refactors the parsing of "expected-*" lines to a separate function, to enable recursive parsing in the next commit. --- include/swift/Frontend/DiagnosticVerifier.h | 8 + lib/Frontend/DiagnosticVerifier.cpp | 560 ++++++++++---------- 2 files changed, 300 insertions(+), 268 deletions(-) diff --git a/include/swift/Frontend/DiagnosticVerifier.h b/include/swift/Frontend/DiagnosticVerifier.h index 5eb293f793137..6eb5333eaa004 100644 --- a/include/swift/Frontend/DiagnosticVerifier.h +++ b/include/swift/Frontend/DiagnosticVerifier.h @@ -22,6 +22,10 @@ #include "swift/AST/DiagnosticConsumer.h" #include "swift/Basic/LLVM.h" +namespace { +struct ExpectedDiagnosticInfo; +} + namespace swift { class DependencyTracker; class FileUnit; @@ -133,6 +137,10 @@ class DiagnosticVerifier : public DiagnosticConsumer { /// 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; bool checkForFixIt(const std::vector &ExpectedAlts, const CapturedDiagnosticInfo &D, unsigned BufferID) const; diff --git a/lib/Frontend/DiagnosticVerifier.cpp b/lib/Frontend/DiagnosticVerifier.cpp index 9c21897818fe6..d234a95945ed8 100644 --- a/lib/Frontend/DiagnosticVerifier.cpp +++ b/lib/Frontend/DiagnosticVerifier.cpp @@ -606,23 +606,11 @@ static bool parseTargetBufferName(StringRef &MatchStart, StringRef &Out, size_t return true; } -/// 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; - - const SourceLoc BufferStartLoc = SM.getLocForBufferStart(BufferID); - 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; - +unsigned DiagnosticVerifier::parseExpectedDiagInfo( + unsigned BufferID, StringRef MatchStart, + std::vector &Errors, + unsigned &PrevExpectedContinuationLine, + ExpectedDiagnosticInfo &Expected) const { auto addError = [&](const char *Loc, const Twine &message, ArrayRef FixIts = {}) { auto loc = SourceLoc::getFromPointer(Loc); @@ -630,299 +618,335 @@ DiagnosticVerifier::Result DiagnosticVerifier::verifyFile(unsigned BufferID) { {}, FixIts); Errors.push_back(diag); }; + const SourceLoc BufferStartLoc = SM.getLocForBufferStart(BufferID); + StringRef InputFile = SM.getEntireTextForBuffer(BufferID); - // Validate that earlier prefixes are not prefixes of alter - // prefixes... otherwise, we will never pattern match the later prefix. - validatePrefixList(AdditionalExpectedPrefixes); + const char *DiagnosticLoc = MatchStart.data(); + MatchStart = MatchStart.substr(strlen("expected-")); - // 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); - const char *DiagnosticLoc = MatchStart.data(); - MatchStart = MatchStart.substr(strlen("expected-")); - - const char *ClassificationStartLoc = nullptr; - std::optional ExpectedClassification; - { - ExpectedCheckMatchStartParser parser(MatchStart); - // If we fail to parse... continue. - if (!parser.parse(AdditionalExpectedPrefixes)) { - continue; + const char *ClassificationStartLoc = nullptr; + std::optional ExpectedClassification; + { + ExpectedCheckMatchStartParser parser(MatchStart); + // If we fail to parse... continue. + if (!parser.parse(AdditionalExpectedPrefixes)) { + return 0; + } + MatchStart = parser.MatchStart; + ClassificationStartLoc = parser.ClassificationStartLoc; + ExpectedClassification = parser.ExpectedClassification; + } + assert(ClassificationStartLoc); + assert(bool(ExpectedClassification)); + + // Skip any whitespace before the {{. + MatchStart = MatchStart.substr(MatchStart.find_first_not_of(" \t")); + + size_t TextStartIdx = MatchStart.find("{{"); + if (TextStartIdx >= + MatchStart.find("\n")) { // Either not found, or found beyond next \n + addError(MatchStart.data(), + "expected {{ in expected-warning/note/error/expansion line"); + return 0; + } + + Expected = ExpectedDiagnosticInfo(DiagnosticLoc, ClassificationStartLoc, + /*ClassificationEndLoc=*/MatchStart.data(), + *ExpectedClassification); + int LineOffset = 0; + + if (TextStartIdx > 0 && MatchStart[0] == '@') { + if (MatchStart[1] != '+' && MatchStart[1] != '-' && MatchStart[1] != ':') { + StringRef TargetBufferName; + if (!parseTargetBufferName(MatchStart, TargetBufferName, TextStartIdx)) { + addError(MatchStart.data(), "expected '+'/'-' for line offset, ':' " + "for column, or a buffer name"); + return 0; + } + Expected.TargetBufferID = SM.getIDForBufferIdentifier(TargetBufferName); + if (!Expected.TargetBufferID) { + addError(MatchStart.data(), + "no buffer with name '" + TargetBufferName + "' found"); + return 0; } - MatchStart = parser.MatchStart; - ClassificationStartLoc = parser.ClassificationStartLoc; - ExpectedClassification = parser.ExpectedClassification; } - assert(ClassificationStartLoc); - assert(bool(ExpectedClassification)); + StringRef Offs; + if (MatchStart[1] == '+') + Offs = MatchStart.slice(2, TextStartIdx).rtrim(); + else + Offs = MatchStart.slice(1, TextStartIdx).rtrim(); + + size_t SpaceIndex = Offs.find(' '); + if (SpaceIndex != StringRef::npos && SpaceIndex < TextStartIdx) { + size_t Delta = Offs.size() - SpaceIndex; + MatchStart = MatchStart.substr(TextStartIdx - Delta); + TextStartIdx = Delta; + Offs = Offs.slice(0, SpaceIndex); + } else { + MatchStart = MatchStart.substr(TextStartIdx); + TextStartIdx = 0; + } - // Skip any whitespace before the {{. - MatchStart = MatchStart.substr(MatchStart.find_first_not_of(" \t")); + size_t ColonIndex = Offs.find(':'); + // Check whether a line offset was provided + if (ColonIndex != 0) { + StringRef LineOffs = Offs.slice(0, ColonIndex); + if (LineOffs.getAsInteger(10, LineOffset)) { + addError(MatchStart.data(), "expected line offset before '{{'"); + return 0; + } + } - size_t TextStartIdx = MatchStart.find("{{"); - if (TextStartIdx >= - MatchStart.find("\n")) { // Either not found, or found beyond next \n - addError(MatchStart.data(), - "expected {{ in expected-warning/note/error line"); - continue; + // Check whether a column was provided + if (ColonIndex != StringRef::npos) { + Offs = Offs.slice(ColonIndex + 1, Offs.size()); + int Column = 0; + if (Offs.getAsInteger(10, Column)) { + addError(MatchStart.data(), "expected column before '{{'"); + return 0; + } + Expected.ColumnNo = Column; } + } - ExpectedDiagnosticInfo Expected(DiagnosticLoc, ClassificationStartLoc, - /*ClassificationEndLoc=*/MatchStart.data(), - *ExpectedClassification); - int LineOffset = 0; - - if (TextStartIdx > 0 && MatchStart[0] == '@') { - if (MatchStart[1] != '+' && MatchStart[1] != '-' && - MatchStart[1] != ':') { - StringRef TargetBufferName; - if (!parseTargetBufferName(MatchStart, TargetBufferName, TextStartIdx)) { - addError(MatchStart.data(), "expected '+'/'-' for line offset, ':' " - "for column, or a buffer name"); - continue; - } - Expected.TargetBufferID = SM.getIDForBufferIdentifier(TargetBufferName); - if (!Expected.TargetBufferID) { - addError(MatchStart.data(), "no buffer with name '" + TargetBufferName + "' found"); - continue; - } + unsigned Count = 1; + if (TextStartIdx > 0) { + StringRef CountStr = MatchStart.substr(0, TextStartIdx).trim(" \t"); + if (CountStr == "*") { + Expected.mayAppear = true; + } else { + if (CountStr.getAsInteger(10, Count)) { + addError(MatchStart.data(), "expected match count before '{{'"); + return 0; } - StringRef Offs; - if (MatchStart[1] == '+') - Offs = MatchStart.slice(2, TextStartIdx).rtrim(); - else - Offs = MatchStart.slice(1, TextStartIdx).rtrim(); - - size_t SpaceIndex = Offs.find(' '); - if (SpaceIndex != StringRef::npos && SpaceIndex < TextStartIdx) { - size_t Delta = Offs.size() - SpaceIndex; - MatchStart = MatchStart.substr(TextStartIdx - Delta); - TextStartIdx = Delta; - Offs = Offs.slice(0, SpaceIndex); - } else { - MatchStart = MatchStart.substr(TextStartIdx); - TextStartIdx = 0; + if (Count == 0) { + addError(MatchStart.data(), + "expected positive match count before '{{'"); + return 0; } + } - size_t ColonIndex = Offs.find(':'); - // Check whether a line offset was provided - if (ColonIndex != 0) { - StringRef LineOffs = Offs.slice(0, ColonIndex); - if (LineOffs.getAsInteger(10, LineOffset)) { - addError(MatchStart.data(), "expected line offset before '{{'"); - continue; - } - } + // Resync up to the '{{'. + MatchStart = MatchStart.substr(TextStartIdx); + } - // Check whether a column was provided - if (ColonIndex != StringRef::npos) { - Offs = Offs.slice(ColonIndex + 1, Offs.size()); - int Column = 0; - if (Offs.getAsInteger(10, Column)) { - addError(MatchStart.data(), "expected column before '{{'"); - continue; - } - Expected.ColumnNo = Column; - } + 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; + } + + llvm::SmallString<256> Buf; + Expected.MessageRange = MatchStart.slice(2, End); + Expected.MessageStr = + Lexer::getEncodedStringSegment(Expected.MessageRange, Buf).str(); + if (Expected.TargetBufferID) + Expected.LineNo = 0; + else if (PrevExpectedContinuationLine) + Expected.LineNo = PrevExpectedContinuationLine; + else + Expected.LineNo = + SM.getLineAndColumnInBuffer(BufferStartLoc.getAdvancedLoc( + MatchStart.data() - InputFile.data()), + BufferID) + .first; + Expected.LineNo += LineOffset; + + // Check if the next expected diagnostic should be in the same line. + StringRef AfterEnd = MatchStart.substr(End + strlen("}}")); + AfterEnd = AfterEnd.substr(AfterEnd.find_first_not_of(" \t")); + if (AfterEnd.starts_with("\\")) + PrevExpectedContinuationLine = Expected.LineNo; + else + PrevExpectedContinuationLine = 0; + + // Scan for fix-its: {{10-14=replacement text}} + bool startNewAlternatives = true; + StringRef ExtraChecks = MatchStart.substr(End + 2).ltrim(" \t"); + while (ExtraChecks.starts_with("{{")) { + // First make sure we have a closing "}}". + size_t EndIndex = ExtraChecks.find("}}"); + if (EndIndex == StringRef::npos) { + addError(ExtraChecks.data(), + "didn't find '}}' to match '{{' in diagnostic verification"); + break; } - unsigned Count = 1; - if (TextStartIdx > 0) { - StringRef CountStr = MatchStart.substr(0, TextStartIdx).trim(" \t"); - if (CountStr == "*") { - Expected.mayAppear = true; - } else { - if (CountStr.getAsInteger(10, Count)) { - addError(MatchStart.data(), "expected match count before '{{'"); - continue; - } - if (Count == 0) { - addError(MatchStart.data(), - "expected positive match count before '{{'"); - continue; - } - } + // Allow for close braces to appear in the replacement text. + while (EndIndex + 2 < ExtraChecks.size() && + ExtraChecks[EndIndex + 2] == '}') + ++EndIndex; - // Resync up to the '{{'. - MatchStart = MatchStart.substr(TextStartIdx); + const char *OpenLoc = ExtraChecks.data(); // Beginning of opening '{{'. + const char *CloseLoc = + ExtraChecks.data() + EndIndex + 2; // End of closing '}}'. + + StringRef CheckStr = ExtraChecks.slice(2, EndIndex); + // Check for matching a later "}}" on a different line. + if (CheckStr.find_first_of("\r\n") != StringRef::npos) { + addError(ExtraChecks.data(), "didn't find '}}' to match '{{' in " + "diagnostic verification"); + break; } - size_t End = MatchStart.find("}}"); - if (End == StringRef::npos) { - addError(MatchStart.data(), - "didn't find '}}' to match '{{' in expected-warning/note/error line"); - continue; + // Prepare for the next round of checks. + ExtraChecks = ExtraChecks.substr(EndIndex + 2).ltrim(" \t"); + + // Handle fix-it alternation. + // If two fix-its are separated by `||`, we can match either of the two. + // This is represented by putting them in the same subarray of `Fixits`. + // If they are not separated by `||`, we must match both of them. + // This is represented by putting them in separate subarrays of `Fixits`. + if (startNewAlternatives && + (Expected.Fixits.empty() || !Expected.Fixits.back().empty())) + Expected.Fixits.push_back({}); + + if (ExtraChecks.starts_with("||")) { + startNewAlternatives = false; + ExtraChecks = ExtraChecks.substr(2).ltrim(" \t"); + } else { + startNewAlternatives = true; } - llvm::SmallString<256> Buf; - Expected.MessageRange = MatchStart.slice(2, End); - Expected.MessageStr = - Lexer::getEncodedStringSegment(Expected.MessageRange, Buf).str(); - if (Expected.TargetBufferID) - Expected.LineNo = 0; - else if (PrevExpectedContinuationLine) - Expected.LineNo = PrevExpectedContinuationLine; - else - Expected.LineNo = SM.getLineAndColumnInBuffer( - BufferStartLoc.getAdvancedLoc(MatchStart.data() - - InputFile.data()), - BufferID) - .first; - Expected.LineNo += LineOffset; - - // Check if the next expected diagnostic should be in the same line. - StringRef AfterEnd = MatchStart.substr(End + strlen("}}")); - AfterEnd = AfterEnd.substr(AfterEnd.find_first_not_of(" \t")); - if (AfterEnd.starts_with("\\")) - PrevExpectedContinuationLine = Expected.LineNo; - else - PrevExpectedContinuationLine = 0; + // If this check starts with 'documentation-file=', check for a + // documentation file name instead of a fix-it. + if (CheckStr.starts_with(categoryDocFileSpecifier)) { + if (Expected.DocumentationFile.has_value()) { + addError(CheckStr.data(), + "each verified diagnostic may only have one " + "{{documentation-file=<#notes#>}} declaration"); + return 0; + } - - // Scan for fix-its: {{10-14=replacement text}} - bool startNewAlternatives = true; - StringRef ExtraChecks = MatchStart.substr(End+2).ltrim(" \t"); - while (ExtraChecks.starts_with("{{")) { - // First make sure we have a closing "}}". - size_t EndIndex = ExtraChecks.find("}}"); - if (EndIndex == StringRef::npos) { - addError(ExtraChecks.data(), - "didn't find '}}' to match '{{' in diagnostic verification"); + // Trim 'documentation-file='. + StringRef name = CheckStr.substr(categoryDocFileSpecifier.size()); + Expected.DocumentationFile = {OpenLoc, CloseLoc, name}; + return 0; + } + + // This wasn't a documentation file specifier, so it must be a fix-it. + // Special case for specifying no fixits should appear. + if (CheckStr == fixitExpectationNoneString) { + if (Expected.noneMarkerStartLoc) { + addError( + CheckStr.data() - 2, + Twine("A second {{") + fixitExpectationNoneString + + "}} was found. It may only appear once in an expectation."); break; } - // Allow for close braces to appear in the replacement text. - while (EndIndex + 2 < ExtraChecks.size() && - ExtraChecks[EndIndex + 2] == '}') - ++EndIndex; + Expected.noneMarkerStartLoc = CheckStr.data() - 2; + return 0; + } - const char *OpenLoc = ExtraChecks.data(); // Beginning of opening '{{'. - const char *CloseLoc = - ExtraChecks.data() + EndIndex + 2; // End of closing '}}'. + if (Expected.noneMarkerStartLoc) { + addError(Expected.noneMarkerStartLoc, Twine("{{") + + fixitExpectationNoneString + + "}} must be at the end."); + break; + } - StringRef CheckStr = ExtraChecks.slice(2, EndIndex); - // Check for matching a later "}}" on a different line. - if (CheckStr.find_first_of("\r\n") != StringRef::npos) { - addError(ExtraChecks.data(), "didn't find '}}' to match '{{' in " - "diagnostic verification"); - break; - } + if (CheckStr.empty()) { + addError(CheckStr.data(), Twine("expected fix-it verification within " + "braces; example: '1-2=text' or '") + + fixitExpectationNoneString + Twine("'")); + return 0; + } - // Prepare for the next round of checks. - ExtraChecks = ExtraChecks.substr(EndIndex + 2).ltrim(" \t"); - - // Handle fix-it alternation. - // If two fix-its are separated by `||`, we can match either of the two. - // This is represented by putting them in the same subarray of `Fixits`. - // If they are not separated by `||`, we must match both of them. - // This is represented by putting them in separate subarrays of `Fixits`. - if (startNewAlternatives && - (Expected.Fixits.empty() || !Expected.Fixits.back().empty())) - Expected.Fixits.push_back({}); - - if (ExtraChecks.starts_with("||")) { - startNewAlternatives = false; - ExtraChecks = ExtraChecks.substr(2).ltrim(" \t"); - } else { - startNewAlternatives = true; - } + // Parse the pieces of the fix-it. + ExpectedFixIt FixIt; + FixIt.StartLoc = OpenLoc; + FixIt.EndLoc = CloseLoc; - // If this check starts with 'documentation-file=', check for a - // documentation file name instead of a fix-it. - if (CheckStr.starts_with(categoryDocFileSpecifier)) { - if (Expected.DocumentationFile.has_value()) { - addError(CheckStr.data(), - "each verified diagnostic may only have one " - "{{documentation-file=<#notes#>}} declaration"); - continue; - } + if (const auto range = + parseExpectedFixItRange(CheckStr, Expected.LineNo, addError)) { + FixIt.Range = range.value(); + } else { + return 0; + } - // Trim 'documentation-file='. - StringRef name = CheckStr.substr(categoryDocFileSpecifier.size()); - Expected.DocumentationFile = { OpenLoc, CloseLoc, name }; - continue; - } + if (!CheckStr.empty() && CheckStr.front() == '=') { + CheckStr = CheckStr.drop_front(); + } else { + addError(CheckStr.data(), + "expected '=' after range in fix-it verification"); + return 0; + } - // This wasn't a documentation file specifier, so it must be a fix-it. - // Special case for specifying no fixits should appear. - if (CheckStr == fixitExpectationNoneString) { - if (Expected.noneMarkerStartLoc) { - addError(CheckStr.data() - 2, - Twine("A second {{") + fixitExpectationNoneString + - "}} was found. It may only appear once in an expectation."); - break; + // Translate literal "\\n" into '\n', inefficiently. + for (const char *current = CheckStr.begin(), *end = CheckStr.end(); + current != end; + /* in loop */) { + if (*current == '\\' && current + 1 < end) { + if (current[1] == 'n') { + FixIt.Text += '\n'; + current += 2; + } else { // Handle \}, \\, etc. + FixIt.Text += current[1]; + current += 2; } - Expected.noneMarkerStartLoc = CheckStr.data() - 2; - continue; + } else { + FixIt.Text += *current++; } + } - if (Expected.noneMarkerStartLoc) { - addError(Expected.noneMarkerStartLoc, Twine("{{") + - fixitExpectationNoneString + - "}} must be at the end."); - break; - } + Expected.Fixits.back().push_back(FixIt); + } - if (CheckStr.empty()) { - addError(CheckStr.data(), Twine("expected fix-it verification within " - "braces; example: '1-2=text' or '") + - fixitExpectationNoneString + Twine("'")); - continue; - } + // If there's a trailing empty alternation, remove it. + if (!Expected.Fixits.empty() && Expected.Fixits.back().empty()) + Expected.Fixits.pop_back(); - // Parse the pieces of the fix-it. - ExpectedFixIt FixIt; - FixIt.StartLoc = OpenLoc; - FixIt.EndLoc = CloseLoc; + Expected.ExpectedEnd = ExtraChecks.data(); - if (const auto range = - parseExpectedFixItRange(CheckStr, Expected.LineNo, addError)) { - FixIt.Range = range.value(); - } else { - continue; - } + // Don't include trailing whitespace in the expected-foo{{}} range. + while (isspace(Expected.ExpectedEnd[-1])) + --Expected.ExpectedEnd; - if (!CheckStr.empty() && CheckStr.front() == '=') { - CheckStr = CheckStr.drop_front(); - } else { - addError(CheckStr.data(), - "expected '=' after range in fix-it verification"); - continue; - } + return Count; +} - // Translate literal "\\n" into '\n', inefficiently. - for (const char *current = CheckStr.begin(), *end = CheckStr.end(); - current != end; /* in loop */) { - if (*current == '\\' && current + 1 < end) { - if (current[1] == 'n') { - FixIt.Text += '\n'; - current += 2; - } else { // Handle \}, \\, etc. - FixIt.Text += current[1]; - current += 2; - } +/// 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); - } else { - FixIt.Text += *current++; - } - } + // Queue up all of the diagnostics, allowing us to sort them and emit them in + // file order. + std::vector Errors; - Expected.Fixits.back().push_back(FixIt); - } + unsigned PrevExpectedContinuationLine = 0; - // If there's a trailing empty alternation, remove it. - if (!Expected.Fixits.empty() && Expected.Fixits.back().empty()) - Expected.Fixits.pop_back(); + std::vector ExpectedDiagnostics; - Expected.ExpectedEnd = ExtraChecks.data(); - - // Don't include trailing whitespace in the expected-foo{{}} range. - while (isspace(Expected.ExpectedEnd[-1])) - --Expected.ExpectedEnd; + 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) From e3d92dbf0f3f898bbcf3f28bbffefe1c7ff64806 Mon Sep 17 00:00:00 2001 From: "Henrik G. Olsson" Date: Wed, 17 Sep 2025 14:58:30 -0700 Subject: [PATCH 05/10] [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 | 387 ++++++++++++------ .../Inputs/syntax_macro_definitions.swift | 4 +- .../Inputs/top_level_freestanding_other.swift | 1 + test/Macros/top_level_freestanding.swift | 21 +- 5 files changed, 295 insertions(+), 136 deletions(-) diff --git a/include/swift/Frontend/DiagnosticVerifier.h b/include/swift/Frontend/DiagnosticVerifier.h index 6eb5333eaa004..b254692486880 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 @@ class DiagnosticVerifier : public DiagnosticConsumer { 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 @@ class DiagnosticVerifier : public DiagnosticConsumer { 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 d234a95945ed8..3db79fa20a9bc 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,53 +1191,17 @@ DiagnosticVerifier::Result DiagnosticVerifier::verifyFile(unsigned BufferID) { else ExpectedDiagnostics.erase(ExpectedDiagnostics.begin()+i); } - - // Check to see if we have any incorrect diagnostics. If so, diagnose them as - // such. - auto expectedDiagIter = ExpectedDiagnostics.begin(); - while (expectedDiagIter != ExpectedDiagnostics.end()) { - // Check to see if any found diagnostics have the right line and - // classification, but the wrong text. - auto I = CapturedDiagnostics.begin(); - for (auto E = CapturedDiagnostics.end(); I != E; ++I) { - // Verify the file and line of the diagnostic. - if (I->Line != expectedDiagIter->LineNo || I->SourceBufferID != BufferID - || I->Classification != expectedDiagIter->Classification) - continue; - - // Otherwise, we found it, break out. - break; - } +} - if (I == CapturedDiagnostics.end()) { - ++expectedDiagIter; +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; } - - if (I->Message.find(expectedDiagIter->MessageStr) == StringRef::npos) { - auto StartLoc = - SMLoc::getFromPointer(expectedDiagIter->MessageRange.begin()); - auto EndLoc = SMLoc::getFromPointer(expectedDiagIter->MessageRange.end()); - - llvm::SMFixIt fixIt(llvm::SMRange{StartLoc, EndLoc}, I->Message); - addError(expectedDiagIter->MessageRange.begin(), - "incorrect message found", fixIt); - } else if (I->Column != *expectedDiagIter->ColumnNo) { - // The difference must be only in the column - addError(expectedDiagIter->MessageRange.begin(), - llvm::formatv("message found at column {0} but was expected to " - "appear at column {1}", - I->Column, *expectedDiagIter->ColumnNo)); - } else { - llvm_unreachable("unhandled difference from expected diagnostic"); - } - CapturedDiagnostics.erase(I); - expectedDiagIter = ExpectedDiagnostics.erase(expectedDiagIter); - } - - // 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"; @@ -1206,7 +1220,6 @@ DiagnosticVerifier::Result DiagnosticVerifier::verifyFile(unsigned BufferID) { 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; @@ -1230,11 +1243,108 @@ DiagnosticVerifier::Result DiagnosticVerifier::verifyFile(unsigned BufferID) { // Remove the expected-foo{{}} as a fixit. llvm::SMFixIt fixIt(llvm::SMRange{ - SMLoc::getFromPointer(StartLoc), - SMLoc::getFromPointer(EndLoc) + 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(); + while (expectedDiagIter != ExpectedDiagnostics.end()) { + // Check to see if any found diagnostics have the right line and + // classification, but the wrong text. + auto I = CapturedDiagnostics.begin(); + for (auto E = CapturedDiagnostics.end(); I != E; ++I) { + // Verify the file and line of the diagnostic. + if (I->Line != expectedDiagIter->LineNo || I->SourceBufferID != BufferID + || I->Classification != expectedDiagIter->Classification) + continue; + + // Otherwise, we found it, break out. + break; + } + + if (I == CapturedDiagnostics.end()) { + ++expectedDiagIter; + continue; + } + + if (I->Message.find(expectedDiagIter->MessageStr) == StringRef::npos) { + auto StartLoc = + SMLoc::getFromPointer(expectedDiagIter->MessageRange.begin()); + auto EndLoc = SMLoc::getFromPointer(expectedDiagIter->MessageRange.end()); + + llvm::SMFixIt fixIt(llvm::SMRange{StartLoc, EndLoc}, I->Message); + addError(expectedDiagIter->MessageRange.begin(), + "incorrect message found", fixIt); + } else if (I->Column != *expectedDiagIter->ColumnNo) { + // The difference must be only in the column + addError(expectedDiagIter->MessageRange.begin(), + llvm::formatv("message found at column {0} but was expected to " + "appear at column {1}", + I->Column, *expectedDiagIter->ColumnNo)); + } else { + llvm_unreachable("unhandled difference from expected diagnostic"); + } + CapturedDiagnostics.erase(I); + expectedDiagIter = ExpectedDiagnostics.erase(expectedDiagIter); + } + + // Diagnose expected diagnostics that didn't appear. + 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 7eee73fb6ca2b..f465023805591 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 c4a296a34fee5..e7539e3f6f0e8 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 1c5cd0d17bbce..bfe678e748312 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) } + From b2069a3e6619d101e59ada1d1a584db28895569d Mon Sep 17 00:00:00 2001 From: "Henrik G. Olsson" Date: Thu, 18 Sep 2025 16:41:07 -0700 Subject: [PATCH 06/10] Update tests with diagnostics in macro expansions --- test/Concurrency/task_local.swift | 10 ++ ...expansion_DistributedProtocol_errors.swift | 14 ++ .../Inputs/top_level_freestanding_other.swift | 6 + test/Macros/accessor_macros.swift | 11 ++ test/Macros/macro_expand.swift | 157 +++++++++++++----- test/Macros/macro_expand_extensions.swift | 37 +++-- test/Macros/macro_expand_other.swift | 10 +- test/Macros/macro_expand_peers.swift | 23 ++- .../macro_expand_synthesized_members.swift | 7 +- test/Macros/top_level_freestanding.swift | 40 ++--- 10 files changed, 232 insertions(+), 83 deletions(-) diff --git a/test/Concurrency/task_local.swift b/test/Concurrency/task_local.swift index d7818271ef4a5..12b6765632f6f 100644 --- a/test/Concurrency/task_local.swift +++ b/test/Concurrency/task_local.swift @@ -7,6 +7,11 @@ struct TL { @TaskLocal // expected-note{{in expansion of macro 'TaskLocal' on static property 'number' here}} static var number: Int = 0 + /* + expected-expansion@-2:29{{ + expected-note@1:8{{change 'let' to 'var' to make it mutable}} + }} + */ @TaskLocal static var someNil: Int? @@ -14,6 +19,11 @@ struct TL { // expected-note@+1{{in expansion of macro 'TaskLocal' on static property 'noValue' here}} @TaskLocal // expected-error{{@TaskLocal' property must have default value, or be optional}} static var noValue: Int // expected-note{{'noValue' declared here}} + /* + expected-expansion@-2:26{{ + expected-error@3:9{{cannot find '$noValue' in scope; did you mean 'noValue'?}} + }} + */ @TaskLocal // expected-error{{'@TaskLocal' can only be applied to 'static' property}} var notStatic: String? diff --git a/test/Distributed/Macros/distributed_macro_expansion_DistributedProtocol_errors.swift b/test/Distributed/Macros/distributed_macro_expansion_DistributedProtocol_errors.swift index 9d776369a5c47..8759d4c906d67 100644 --- a/test/Distributed/Macros/distributed_macro_expansion_DistributedProtocol_errors.swift +++ b/test/Distributed/Macros/distributed_macro_expansion_DistributedProtocol_errors.swift @@ -30,6 +30,14 @@ distributed actor Caplin { protocol Fail: DistributedActor { distributed func method() -> String } +/* +expected-expansion@-2:2{{ + expected-error@1:19{{distributed actor '$Fail' does not declare ActorSystem it can be used with}} + expected-note@1:13{{you can provide a module-wide default actor system by declaring:}} + expected-error@1:19{{type '$Fail' does not conform to protocol 'DistributedActor'}} + expected-note@1:19{{add stubs for conformance}} +}} +*/ @Resolvable // expected-note2{{in expansion of macro 'Resolvable' on protocol 'SomeRoot' here}} public protocol SomeRoot: DistributedActor, Sendable @@ -39,3 +47,9 @@ public protocol SomeRoot: DistributedActor, Sendable static var staticValue: String { get } var value: String { get } } +/* +expected-expansion@-2:2{{ + expected-error@1:27{{type '$SomeRoot' does not conform to protocol 'SomeRoot'}} + expected-note@1:27{{add stubs for conformance}} +}} +*/ diff --git a/test/Macros/Inputs/top_level_freestanding_other.swift b/test/Macros/Inputs/top_level_freestanding_other.swift index e7539e3f6f0e8..7c3ac0214f21c 100644 --- a/test/Macros/Inputs/top_level_freestanding_other.swift +++ b/test/Macros/Inputs/top_level_freestanding_other.swift @@ -13,8 +13,14 @@ func deprecated() -> Int { 0 } var globalVar3 = #stringify({ deprecated() }) // expected-note@-1 {{in expansion of macro 'stringify' here}} // expected-warning@-2{{'deprecated()' is deprecated}} +// expected-expansion@-3:18{{ +// expected-warning@2:9{{'deprecated()' is deprecated}} +// }} var globalVar4 = #stringify({ deprecated() }) // expected-note@-1 {{in expansion of macro 'stringify' here}} // expected-warning@-2{{'deprecated()' is deprecated}} +// expected-expansion@-3:18{{ +// expected-warning@2:9{{'deprecated()' is deprecated}} +// }} diff --git a/test/Macros/accessor_macros.swift b/test/Macros/accessor_macros.swift index e9da34f84fa0e..df722c8d535d8 100644 --- a/test/Macros/accessor_macros.swift +++ b/test/Macros/accessor_macros.swift @@ -99,6 +99,11 @@ struct MyBrokenStruct { // expected-note@+1 2{{in expansion of macro 'myPropertyWrapper' on property 'birthDate' here}} @myPropertyWrapper var birthDate: Date? { + /* + expected-expansion@-2:25{{ + expected-error@1:1{{variable already has a getter}} + }} + */ // CHECK-DIAGS: variable already has a getter // CHECK-DIAGS: in expansion of macro // CHECK-DIAGS: previous definition of getter here @@ -153,6 +158,12 @@ struct HasStoredTests { // expected-error@-1{{expansion of macro 'MakeComputedSneakily()' produced an unexpected getter}} // expected-note@-2 2{{in expansion of macro}} // expected-note@-3 2{{'z' declared here}} + /* + expected-expansion@-5:36{{ + expected-error@3:9{{cannot find '_z' in scope; did you mean 'z'?}} + expected-error@6:9{{cannot find '_z' in scope; did you mean 'z'?}} + }} + */ #endif } diff --git a/test/Macros/macro_expand.swift b/test/Macros/macro_expand.swift index 074fcb5a6bd54..5480b735b035d 100644 --- a/test/Macros/macro_expand.swift +++ b/test/Macros/macro_expand.swift @@ -73,11 +73,11 @@ macro NotCovered() = #externalMacro(module: "MacroDefinition", type: "InvalidMac struct MemberNotCovered { #NotCovered // expected-note@-1 {{in expansion of macro 'NotCovered' here}} - - // CHECK-DIAGS: error: declaration name 'value' is not covered by macro 'NotCovered' - // CHECK-DIAGS: CONTENTS OF FILE @__swiftmacro_9MacroUser0023macro_expandswift_elFCffMX[[@LINE-5]]_2_33_4361AD9339943F52AE6186DD51E04E91Ll10NotCoveredfMf_.swift - // CHECK-DIAGS: var value: Int - // CHECK-DIAGS: END CONTENTS OF FILE + /* + expected-expansion@-3:3 {{ + expected-error@1:5{{declaration name 'value' is not covered by macro 'NotCovered'}} + }} + */ } @attached(peer) @@ -86,24 +86,29 @@ macro Invalid() = #externalMacro(module: "MacroDefinition", type: "InvalidMacro" @Invalid struct Bad {} // expected-note@-2 18 {{in expansion of macro 'Invalid' on struct 'Bad' here}} +/* +expected-expansion@-3:14 {{ + expected-error@1:8{{macro expansion cannot introduce import}} + expected-error@3:17{{macro expansion cannot introduce precedence group}} + expected-error@6:25{{macro 'myMacro()' requires a definition}} + expected-error@6:25{{macro expansion cannot introduce macro}} + expected-error@8:1{{macro expansion cannot introduce extension}} + expected-error@11:1{{macro expansion cannot introduce '@main' type}} + expected-error@12:8{{declaration name 'MyMain' is not covered by macro 'Invalid'}} + expected-error@17:11{{declaration name 'Array' is not covered by macro 'Invalid'}} + expected-error@19:11{{declaration name 'Dictionary' is not covered by macro 'Invalid'}} + expected-error@21:11{{macro expansion cannot introduce default literal type 'BooleanLiteralType'}} + expected-error@23:11{{macro expansion cannot introduce default literal type 'ExtendedGraphemeClusterType'}} + expected-error@25:11{{macro expansion cannot introduce default literal type 'FloatLiteralType'}} + expected-error@27:11{{macro expansion cannot introduce default literal type 'IntegerLiteralType'}} + expected-error@29:11{{macro expansion cannot introduce default literal type 'StringLiteralType'}} + expected-error@31:11{{macro expansion cannot introduce default literal type 'UnicodeScalarType'}} + expected-error@33:11{{macro expansion cannot introduce default literal type '_ColorLiteralType'}} + expected-error@35:11{{macro expansion cannot introduce default literal type '_ImageLiteralType'}} + expected-error@37:11{{macro expansion cannot introduce default literal type '_FileReferenceLiteralType'}} +}} +*/ -// CHECK-DIAGS: error: macro expansion cannot introduce import -// CHECK-DIAGS: error: macro expansion cannot introduce precedence group -// CHECK-DIAGS: error: macro expansion cannot introduce macro -// CHECK-DIAGS: error: macro expansion cannot introduce extension -// CHECK-DIAGS: error: macro expansion cannot introduce '@main' type -// CHECK-DIAGS: error: declaration name 'MyMain' is not covered by macro 'Invalid' -// CHECK-DIAGS: error: declaration name 'Array' is not covered by macro 'Invalid' -// CHECK-DIAGS: error: declaration name 'Dictionary' is not covered by macro 'Invalid' -// CHECK-DIAGS: error: macro expansion cannot introduce default literal type 'BooleanLiteralType' -// CHECK-DIAGS: error: macro expansion cannot introduce default literal type 'ExtendedGraphemeClusterType' -// CHECK-DIAGS: error: macro expansion cannot introduce default literal type 'FloatLiteralType' -// CHECK-DIAGS: error: macro expansion cannot introduce default literal type 'IntegerLiteralType' -// CHECK-DIAGS: error: macro expansion cannot introduce default literal type 'StringLiteralType' -// CHECK-DIAGS: error: macro expansion cannot introduce default literal type 'UnicodeScalarType' -// CHECK-DIAGS: error: macro expansion cannot introduce default literal type '_ColorLiteralType' -// CHECK-DIAGS: error: macro expansion cannot introduce default literal type '_ImageLiteralType' -// CHECK-DIAGS: error: macro expansion cannot introduce default literal type '_FileReferenceLiteralType' // CHECK-DIAGS: CONTENTS OF FILE @__swiftmacro_9MacroUser3Bad7InvalidfMp_.swift // CHECK-DIAGS: import Swift @@ -132,21 +137,28 @@ struct Bad {} class HasStoredPropertyClassInvalid { #AddStoredProperty((Self.self, 0).1) // expected-note {{in expansion of macro 'AddStoredProperty' here}} - // CHECK-DIAGS: @__swiftmacro_9MacroUser0023macro_expandswift_elFCffMX[[@LINE-2]]_2_33_{{.*}}AddStoredPropertyfMf_.swift:1:22: error: covariant 'Self' type cannot be referenced from a stored property initializer + /* + expected-expansion@-2:3 {{ + expected-error@1:22{{covariant 'Self' type cannot be referenced from a stored property initializer}} + }} + */ } // Redeclaration checking should behave as though expansions are part of the // source file. struct RedeclChecking { #varValue + /* + expected-expansion@-2:3 {{ + expected-note@1:5{{'value' previously declared here}} + }} + */ // expected-error@+1 {{invalid redeclaration of 'value'}} var value: Int { 0 } } -// CHECK-DIAGS: macro_expand.swift:[[@LINE-3]]:7: error: invalid redeclaration of 'value' -// CHECK-DIAGS: @__swiftmacro_9MacroUser0023macro_expandswift_elFCffMX[[@LINE-8]]_2_33_4361AD9339943F52AE6186DD51E04E91Ll8varValuefMf_.swift:1:5: note: 'value' previously declared here -// CHECK-DIAGS: CONTENTS OF FILE @__swiftmacro_9MacroUser0023macro_expandswift_elFCffMX[[@LINE-9]]_2_33_4361AD9339943F52AE6186DD51E04E91Ll8varValuefMf_.swift: +// CHECK-DIAGS: CONTENTS OF FILE @__swiftmacro_9MacroUser0023macro_expandswift_elFCffMX[[@LINE-12]]_2_33_4361AD9339943F52AE6186DD51E04E91Ll8varValuefMf_.swift: // CHECK-DIAGS: var value: Int { // CHECK-DIAGS: 1 // CHECK-DIAGS: } @@ -159,11 +171,19 @@ public macro ThrowCancellation() = #externalMacro(module: "MacroDefinition", typ // error mismatch. @ThrowCancellation // expected-note {{in expansion of macro 'ThrowCancellation' on global function 'issue79039()' here}} func issue79039() throws(DecodingError) -// CHECK-DIAGS: @__swiftmacro_9MacroUser10issue7903917ThrowCancellationfMb_.swift:2:11: error: thrown expression type 'CancellationError' cannot be converted to error type 'DecodingError' +/* +expected-expansion@-2:39 {{ + expected-error@2:11{{thrown expression type 'CancellationError' cannot be converted to error type 'DecodingError'}} +}} +*/ @ThrowCancellation // expected-note {{in expansion of macro 'ThrowCancellation' on global function 'issue79039_2()' here}} func issue79039_2() throws(DecodingError) {} -// CHECK-DIAGS: @__swiftmacro_9MacroUser12issue79039_217ThrowCancellationfMb_.swift:2:11: error: thrown expression type 'CancellationError' cannot be converted to error type 'DecodingError' +/* +expected-expansion@-2:43 {{ + expected-error@2:11{{thrown expression type 'CancellationError' cannot be converted to error type 'DecodingError'}} +}} +*/ #endif @freestanding(declaration) @@ -176,16 +196,28 @@ macro AccidentalCodeItem() = #externalMacro(module: "MacroDefinition", type: "Fa func invalidDeclarationMacro() { #accidentalCodeItem // expected-note@-1 {{in expansion of macro 'accidentalCodeItem' here}} - // CHECK-DIAGS: @__swiftmacro_9MacroUser0023macro_expandswift_elFCffMX[[@LINE-3]]_2_18accidentalCodeItemfMf_.swift:1:1: error: expected macro expansion to produce a declaration + /* + expected-expansion@-3:3 {{ + expected-error@1:1{{expected macro expansion to produce a declaration}} + }} + */ @AccidentalCodeItem struct S {} // expected-note@-1 {{in expansion of macro 'AccidentalCodeItem' on struct 'S' here}} - // CHECK-DIAGS: @__swiftmacro_9MacroUser018invalidDeclarationA0yyF5S_$l018AccidentalCodeItemfMp_.swift:1:1: error: expected macro expansion to produce a declaration + /* + expected-expansion@-3:34 {{ + expected-error@1:1{{expected macro expansion to produce a declaration}} + }} + */ do { @AccidentalCodeItem struct S {} // expected-note@-1 {{in expansion of macro 'AccidentalCodeItem' on struct 'S' here}} - // CHECK-DIAGS: @__swiftmacro_9MacroUser018invalidDeclarationA0yyF5S_$l118AccidentalCodeItemfMp_.swift:1:1: error: expected macro expansion to produce a declaration + /* + expected-expansion@-3:36 {{ + expected-error@1:1{{expected macro expansion to produce a declaration}} + }} + */ } } #endif @@ -323,9 +355,14 @@ func testNested() { struct Nested { } _ = #stringify(#assertAny(Nested())) // expected-note@-1 {{in expansion of macro 'stringify' here}} -// CHECK-DIAGS-NOT: error: cannot convert value of type 'Nested' to expected argument type 'Bool' -// CHECK-DIAGS: @__swiftmacro_9MacroUser0023macro_expandswift_elFCffMX{{.*}}_9stringifyfMf_9assertAnyfMf_.swift:1:8: error: cannot convert value of type 'Nested' to expected argument type 'Bool' -// CHECK-DIAGS-NOT: error: cannot convert value of type 'Nested' to expected argument type 'Bool' + /* + expected-expansion@-3:7 {{ + expected-note@1:2{{in expansion of macro 'assertAny' here}} + expected-expansion@1:2{{ + expected-error@1:8{{cannot convert value of type 'Nested' to expected argument type 'Bool'}} + }} + }} + */ // PRETTY-DIAGS: 1:8: error: cannot convert value of type 'Nested' to expected argument type 'Bool' // PRETTY-DIAGS: macro_expand.swift:{{.*}}:39: note: expanded code originates here @@ -344,8 +381,15 @@ func testStringifyWithThrows() throws { #if TEST_DIAGNOSTICS // FIXME: Lots of duplicate notes here _ = #stringify(maybeThrowing()) // expected-note 4{{in expansion of macro 'stringify' here}} + /* + expected-expansion@-2:7 {{ + expected-error@1:2{{call can throw but is not marked with 'try'}} + expected-note@1:2{{did you mean to disable error propagation?}} + expected-note@1:2{{did you mean to handle error as optional value?}} + expected-note@1:2{{did you mean to use 'try'?}} + }} + */ - // CHECK-DIAGS: @__swiftmacro_9MacroUser0023macro_expandswift_elFCffMX{{.*}}_9stringifyfMf1_.swift:1:2: error: call can throw but is not marked with 'try' #endif // The macro adds the 'try' for us. @@ -385,14 +429,18 @@ func testAddBlocker(a: Int, b: Int, c: Int, oa: OnlyAdds) { _ = #addBlocker(oa + oa) // expected-error{{blocked an add; did you mean to subtract? (from macro 'addBlocker')}} // expected-note@-1{{in expansion of macro 'addBlocker' here}} // expected-note@-2{{use '-'}}{{22-23=-}} - - // CHECK-DIAGS: @__swiftmacro_9MacroUser0023macro_expandswift_elFCffMX{{.*}}_10addBlockerfMf1_.swift:1:4: error: binary operator '-' cannot be applied to two 'OnlyAdds' operands [] [] - // CHECK-DIAGS: CONTENTS OF FILE @__swiftmacro_9MacroUser0023macro_expandswift_elFCffMX{{.*}}_10addBlockerfMf1_.swift: - // CHECK-DIAGS-NEXT: Original source range: {{.*}}macro_expand.swift:[[@LINE-6]]:7 - {{.*}}macro_expand.swift:[[@LINE-6]]:27 - // CHECK-DIAGS-NEXT: oa - oa - // CHECK-DIAGS-NEXT: END CONTENTS OF FILE + /* + expected-expansion@-4:7 {{ + expected-error@1:4{{binary operator '-' cannot be applied to two 'OnlyAdds' operands}} + }} + */ _ = #addBlocker({ // expected-note{{in expansion of macro 'addBlocker' here}} + /* + expected-expansion@-2:7 {{ + expected-error@9:16{{referencing operator function '-' on 'FloatingPoint' requires that 'OnlyAdds' conform to 'FloatingPoint'}} + }} + */ print("hello") print(oa + oa) // expected-error{{blocked an add; did you mean to subtract? (from macro 'addBlocker')}} @@ -403,7 +451,13 @@ func testAddBlocker(a: Int, b: Int, c: Int, oa: OnlyAdds) { // Check recursion. #recurse(false) // okay - #recurse(true) // expected-note{{in expansion of macro 'recurse' here}} + #recurse(true) + /* + expected-expansion@-2:3 {{ + expected-error@1:1{{recursive expansion of macro 'recurse'}} + }} + expected-note@-5{{in expansion of macro 'recurse' here}} + */ #endif } @@ -494,6 +548,14 @@ func testFreestandingMacroExpansion() { struct Foo3 { #bitwidthNumberedStructs("BUG", blah: false) // expected-note@-1 4{{in expansion of macro 'bitwidthNumberedStructs' here}} + /* + expected-expansion@-3:5 {{ + expected-error@3:14{{unexpected non-void return value in void function}} + expected-note@3:14{{did you mean to add a return type?}} + expected-error@6:14{{unexpected non-void return value in void function}} + expected-note@6:14{{did you mean to add a return type?}} + }} + */ // CHECK-DIAGS: CONTENTS OF FILE @__swiftmacro_9MacroUser0023macro_expandswift_elFCffMX{{.*}}_23bitwidthNumberedStructsfMf_.swift // CHECK-DIAGS: struct BUG { // CHECK-DIAGS: func $s9MacroUser0023macro_expandswift_elFCffMX{{.*}}_23bitwidthNumberedStructsfMf_6methodfMu_() @@ -727,6 +789,11 @@ struct ABIAttrWithFreestandingMacro1 { @abi(#varValue) #varValue // expected-note@-1 {{in expansion of macro 'varValue' here}} + /* + expected-expansion@-3:3 {{ + expected-error@2:6{{cannot use pound literal in '@abi'}} + }} + */ } struct ABIAttrWithFreestandingMacro2 { @@ -759,7 +826,11 @@ func invalidDeclarationMacro2() { func f() { #accidentalCodeItem // expected-note@-1 {{in expansion of macro 'accidentalCodeItem' here}} - // CHECK-DIAGS: @__swiftmacro_9MacroUser0023macro_expandswift_elFCffMX[[@LINE-3]]_6_18accidentalCodeItemfMf_.swift:1:1: error: expected macro expansion to produce a declaration + /* + expected-expansion@-3:7 {{ + expected-error@1:1{{expected macro expansion to produce a declaration}} + }} + */ } } } diff --git a/test/Macros/macro_expand_extensions.swift b/test/Macros/macro_expand_extensions.swift index 7196eea12465a..0ee4e20d0e1d9 100644 --- a/test/Macros/macro_expand_extensions.swift +++ b/test/Macros/macro_expand_extensions.swift @@ -10,9 +10,6 @@ // RUN: %FileCheck -check-prefix=CHECK-DUMP %s < %t/expansions-dump.txt // RUN: %target-typecheck-verify-swift -swift-version 5 -load-plugin-library %t/%target-library-name(MacroDefinition) -module-name MacroUser -DTEST_DIAGNOSTICS -swift-version 5 -I %t -// RUN: not %target-swift-frontend -swift-version 5 -typecheck -load-plugin-library %t/%target-library-name(MacroDefinition) -module-name MacroUser -DTEST_DIAGNOSTICS -serialize-diagnostics-path %t/macro_expand.dia %s -emit-macro-expansion-files no-diagnostics -// RUN: c-index-test -read-diagnostics %t/macro_expand.dia 2>&1 | %FileCheck -check-prefix CHECK-DIAGS %s - // Ensure that we can serialize this file as a module. // RUN: %target-swift-frontend -swift-version 5 -load-plugin-library %t/%target-library-name(MacroDefinition) %s -I %t -disable-availability-checking -emit-module -o %t/MyModule.swiftmodule -enable-testing @@ -137,8 +134,11 @@ macro UndocumentedNamesInExtension() = #externalMacro(module: "MacroDefinition", @UndocumentedNamesInExtension struct S {} // expected-note@-2 {{in expansion of macro 'UndocumentedNamesInExtension' on generic struct 'S' here}} - -// CHECK-DIAGS: error: declaration name 'requirement()' is not covered by macro 'UndocumentedNamesInExtension' +/* +expected-expansion@-3:21 {{ + expected-error@2:15{{declaration name 'requirement()' is not covered by macro 'UndocumentedNamesInExtension'}} +}} +*/ @attached(extension, names: named(requirement)) macro UndocumentedConformanceInExtension() = #externalMacro(module: "MacroDefinition", type: "AlwaysAddConformance") @@ -146,8 +146,11 @@ macro UndocumentedConformanceInExtension() = #externalMacro(module: "MacroDefini @UndocumentedConformanceInExtension struct InvalidConformance {} // expected-note@-2 {{in expansion of macro 'UndocumentedConformanceInExtension' on generic struct 'InvalidConformance' here}} - -// CHECK-DIAGS: error: conformance to 'P' is not covered by macro 'UndocumentedConformanceInExtension' +/* +expected-expansion@-3:38 {{ + expected-error@1:1{{conformance to 'P' is not covered by macro 'UndocumentedConformanceInExtension'}} +}} +*/ @attached(extension) macro UndocumentedCodable() = #externalMacro(module: "MacroDefinition", type: "AlwaysAddCodable") @@ -155,8 +158,11 @@ macro UndocumentedCodable() = #externalMacro(module: "MacroDefinition", type: "A @UndocumentedCodable struct TestUndocumentedCodable {} // expected-note@-2 {{in expansion of macro 'UndocumentedCodable' on struct 'TestUndocumentedCodable' here}} - -// CHECK-DIAGS: error: conformance to 'Codable' (aka 'Decodable & Encodable') is not covered by macro 'UndocumentedCodable' +/* +expected-expansion@-3:34 {{ + expected-error@1:1{{conformance to 'Codable' (aka 'Decodable & Encodable') is not covered by macro 'UndocumentedCodable'}} +}} +*/ @attached(extension, conformances: Decodable) macro UndocumentedEncodable() = #externalMacro(module: "MacroDefinition", type: "AlwaysAddCodable") @@ -164,8 +170,11 @@ macro UndocumentedEncodable() = #externalMacro(module: "MacroDefinition", type: @UndocumentedEncodable struct TestUndocumentedEncodable {} // expected-note@-2 {{in expansion of macro 'UndocumentedEncodable' on struct 'TestUndocumentedEncodable' here}} - -// CHECK-DIAGS: error: conformance to 'Codable' (aka 'Decodable & Encodable') is not covered by macro 'UndocumentedEncodable' +/* +expected-expansion@-3:36 {{ + expected-error@1:1{{conformance to 'Codable' (aka 'Decodable & Encodable') is not covered by macro 'UndocumentedEncodable'}} +}} +*/ @attached(extension) macro BadExtension() = #externalMacro(module: "MacroDefinition", type: "BadExtensionMacro") @@ -176,7 +185,11 @@ struct HasSomeNestedType { @BadExtension // expected-note {{in expansion of macro 'BadExtension' on struct 'SomeNestedType' here}} struct SomeNestedType {} } -// CHECK-DIAGS: error: cannot find type 'SomeNestedType' in scope +/* +expected-expansion@-2:2 {{ + expected-error@1:11{{cannot find type 'SomeNestedType' in scope}} +}} +*/ #endif diff --git a/test/Macros/macro_expand_other.swift b/test/Macros/macro_expand_other.swift index 4d210deb76d8c..b215e1d2effe3 100644 --- a/test/Macros/macro_expand_other.swift +++ b/test/Macros/macro_expand_other.swift @@ -32,7 +32,15 @@ func testFreestandingExpansionOfOther() { #if TEST_DIAGNOSTICS #recurseThrough(true) - // expected-note@-1 {{in expansion of macro 'recurseThrough' here}} + /* + expected-expansion@-2:1{{ + expected-expansion@1:1{{ + expected-error@1:1{{recursive expansion of macro 'recurse'}} + }} + expected-note@1:1{{in expansion of macro 'recurse' here}} + }} + expected-note@-8 {{in expansion of macro 'recurseThrough' here}} + */ #endif } diff --git a/test/Macros/macro_expand_peers.swift b/test/Macros/macro_expand_peers.swift index dfd8a3cc0897a..6e90bf8061f67 100644 --- a/test/Macros/macro_expand_peers.swift +++ b/test/Macros/macro_expand_peers.swift @@ -317,9 +317,15 @@ struct ABIAttrWithAttachedMacro { // expected-error@+1 {{macro 'addCompletionHandler()' cannot be expanded in '@abi' attribute}} @abi(@addCompletionHandler func fn1() async) @addCompletionHandler func fn1() async {} - // From diagnostics in the expansion: - // expected-note@-2 3{{in expansion of macro 'addCompletionHandler' on instance method 'fn1()' here}} - // expected-note@-4 {{'fn1()' previously declared here}} + /* + expected-expansion@-2:44{{ + expected-error@2:8{{macro 'addCompletionHandler()' cannot be expanded in '@abi' attribute}} + expected-error@2:35{{invalid redeclaration of 'fn1()'}} + expected-error@4:23{{argument passed to call that takes no arguments}} + }} + expected-note@-7 3{{in expansion of macro 'addCompletionHandler' on instance method 'fn1()' here}} + expected-note@-9 {{'fn1()' previously declared here}} + */ // expected-error@+1 {{macro 'addCompletionHandler()' cannot be expanded in '@abi' attribute}} @abi(@addCompletionHandler func fn2() async) @@ -327,8 +333,13 @@ struct ABIAttrWithAttachedMacro { @abi(func fn3() async) @addCompletionHandler func fn3() async {} - // From diagnostics in the expansion: - // expected-note@-2 2{{in expansion of macro 'addCompletionHandler' on instance method 'fn3()' here}} - // expected-note@-4 {{'fn3()' previously declared here}} + /* + expected-expansion@-2:44{{ + expected-error@1:11{{invalid redeclaration of 'fn3()'}} + expected-error@3:23{{argument passed to call that takes no arguments}} + }} + expected-note@-6 2{{in expansion of macro 'addCompletionHandler' on instance method 'fn3()' here}} + expected-note@-8 {{'fn3()' previously declared here}} + */ } #endif diff --git a/test/Macros/macro_expand_synthesized_members.swift b/test/Macros/macro_expand_synthesized_members.swift index 5001f1ab9b689..08443d83bad87 100644 --- a/test/Macros/macro_expand_synthesized_members.swift +++ b/test/Macros/macro_expand_synthesized_members.swift @@ -105,12 +105,17 @@ print(ElementType.paper.unknown()) #if TEST_DIAGNOSTICS @addMembersQuotedInit struct S2 { -// expected-note@-2 {{in expansion of macro 'addMembersQuotedInit' on struct 'S2' here}} func useSynthesized() { S.method() print(type(of: getStorage())) } } +/* +expected-expansion@-2:1{{ + expected-error@14:1{{declaration name 'init()' is not covered by macro 'addMembersQuotedInit'}} +}} +expected-note@-11 {{in expansion of macro 'addMembersQuotedInit' on struct 'S2' here}} +*/ #endif @attached( diff --git a/test/Macros/top_level_freestanding.swift b/test/Macros/top_level_freestanding.swift index bfe678e748312..4be717f1c6a08 100644 --- a/test/Macros/top_level_freestanding.swift +++ b/test/Macros/top_level_freestanding.swift @@ -12,10 +12,6 @@ // RUN: %target-typecheck-verify-swift -swift-version 5 -parse-as-library -load-plugin-library %t/%target-library-name(MacroDefinition) -module-name MacroUser -DTEST_DIAGNOSTICS -DIMPORT_MACRO_LIBRARY -swift-version 5 %S/Inputs/top_level_freestanding_other.swift -I %t -// Check diagnostic buffer names -// RUN: not %target-swift-frontend -typecheck -swift-version 5 -parse-as-library -load-plugin-library %t/%target-library-name(MacroDefinition) -module-name MacroUser -DTEST_DIAGNOSTICS -swift-version 5 %s %S/Inputs/top_level_freestanding_other.swift -diagnostic-style llvm 2> %t.diags -// RUN: %FileCheck -check-prefix DIAG_BUFFERS %s < %t.diags - // Execution testing // RUN: %target-build-swift -g -swift-version 5 -parse-as-library -load-plugin-library %t/%target-library-name(MacroDefinition) %s %S/Inputs/top_level_freestanding_other.swift -o %t/main -module-name MacroUser -swift-version 5 // RUN: %target-codesign %t/main @@ -60,7 +56,7 @@ func lookupGlobalFreestandingExpansion() { #anonymousTypes(public: true) { "hello" } -// CHECK-SIL: sil @$s9MacroUser03$s9A115User0033top_level_freestandingswift_DbGHjfMX60_0_33_082AE7CFEFA6960C804A9FE7366EB5A0Ll14anonymousTypesfMf_4namefMu_C5helloSSyF +// CHECK-SIL: sil @$s9MacroUser03$s9A115User0033top_level_freestandingswift_DbGHjfMX56_0_33_082AE7CFEFA6960C804A9FE7366EB5A0Ll14anonymousTypesfMf_4namefMu_C5helloSSyF @main struct Main { @@ -94,8 +90,6 @@ func testArbitraryAtGlobal() { } #endif -// 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,26 +100,32 @@ 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" } -// 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-expansion@-2: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'; this will be an error in a future Swift language mode}} + expected-note@24:3 3{{in expansion of macro 'introduceTypeCheckingErrors' here}} + expected-expansion@24:3 {{ + 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-warning@2:273{{use of protocol 'Hashable' as a type must be written 'any Hashable'; this will be an error in a future Swift 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'}} _ = 0 as Equatable return "foo" } +/* +expected-expansion@-6:1{{ + expected-warning@5:16{{use of protocol 'Equatable' as a type must be written 'any Equatable'; this will be an error in a future Swift language mode}} + expected-warning@20:16{{use of protocol 'Equatable' as a type must be written 'any Equatable'; this will be an error in a future Swift language mode}} +}} +*/ #endif From 5422c8d284b089815cdc15bd88598e6c5f67081f Mon Sep 17 00:00:00 2001 From: "Henrik G. Olsson" Date: Fri, 19 Sep 2025 12:43:07 -0700 Subject: [PATCH 07/10] Fix bug where column is specified for own line When the syntax `expected-error@:42{{}}` was used, this would accidentally trigger "absolute line" mode, despite not specifying a line, resulting in the target line always being line 0. --- lib/Frontend/DiagnosticVerifier.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Frontend/DiagnosticVerifier.cpp b/lib/Frontend/DiagnosticVerifier.cpp index 3db79fa20a9bc..ef46367e6b0c7 100644 --- a/lib/Frontend/DiagnosticVerifier.cpp +++ b/lib/Frontend/DiagnosticVerifier.cpp @@ -700,7 +700,7 @@ unsigned DiagnosticVerifier::parseExpectedDiagInfo( Offs = MatchStart.slice(2, TextStartIdx).rtrim(); else { Offs = MatchStart.slice(1, TextStartIdx).rtrim(); - if (Offs[0] != '-') + if (Offs[0] >= '0' && Offs[0] <= '9') AbsoluteLine = true; } From c5256ff3415ab733d3000519d61e7b43e2175f62 Mon Sep 17 00:00:00 2001 From: "Henrik G. Olsson" Date: Fri, 19 Sep 2025 14:20:04 -0700 Subject: [PATCH 08/10] Fix `continue` statements incorrectly changed to `return`s When this function was extracted from a for loop, continue was mapped to return, but these nested continues should remain. --- lib/Frontend/DiagnosticVerifier.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/Frontend/DiagnosticVerifier.cpp b/lib/Frontend/DiagnosticVerifier.cpp index ef46367e6b0c7..41ea1e37c0269 100644 --- a/lib/Frontend/DiagnosticVerifier.cpp +++ b/lib/Frontend/DiagnosticVerifier.cpp @@ -893,13 +893,13 @@ unsigned DiagnosticVerifier::parseExpectedDiagInfo( addError(CheckStr.data(), "each verified diagnostic may only have one " "{{documentation-file=<#notes#>}} declaration"); - return 0; + continue; } // Trim 'documentation-file='. StringRef name = CheckStr.substr(categoryDocFileSpecifier.size()); Expected.DocumentationFile = {OpenLoc, CloseLoc, name}; - return 0; + continue; } // This wasn't a documentation file specifier, so it must be a fix-it. @@ -914,7 +914,7 @@ unsigned DiagnosticVerifier::parseExpectedDiagInfo( } Expected.noneMarkerStartLoc = CheckStr.data() - 2; - return 0; + continue; } if (Expected.noneMarkerStartLoc) { @@ -928,7 +928,7 @@ unsigned DiagnosticVerifier::parseExpectedDiagInfo( addError(CheckStr.data(), Twine("expected fix-it verification within " "braces; example: '1-2=text' or '") + fixitExpectationNoneString + Twine("'")); - return 0; + continue; } // Parse the pieces of the fix-it. @@ -940,7 +940,7 @@ unsigned DiagnosticVerifier::parseExpectedDiagInfo( parseExpectedFixItRange(CheckStr, Expected.LineNo)) { FixIt.Range = range.value(); } else { - return 0; + continue; } if (!CheckStr.empty() && CheckStr.front() == '=') { @@ -948,7 +948,7 @@ unsigned DiagnosticVerifier::parseExpectedDiagInfo( } else { addError(CheckStr.data(), "expected '=' after range in fix-it verification"); - return 0; + continue; } // Translate literal "\\n" into '\n', inefficiently. From 4668a0c70ffcf9b5455750047680521a1fd67516 Mon Sep 17 00:00:00 2001 From: "Henrik G. Olsson" Date: Fri, 19 Sep 2025 14:36:36 -0700 Subject: [PATCH 09/10] update test/Macros/macro_attribute_expansiondecl.swift --- test/Macros/macro_attribute_expansiondecl.swift | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/test/Macros/macro_attribute_expansiondecl.swift b/test/Macros/macro_attribute_expansiondecl.swift index 1c07a032c7d06..132f155e136d2 100644 --- a/test/Macros/macro_attribute_expansiondecl.swift +++ b/test/Macros/macro_attribute_expansiondecl.swift @@ -160,10 +160,19 @@ struct S1 { // FIXME: Diagnostics could be better. struct S2 { // expected-note 4 {{add '@available' attribute to enclosing struct}} - // expected-note@+3 6 {{in expansion of macro 'funcFromClosureMacro' here}} // expected-error@+2 {{'APIFrom99()' is only available in macOS 99 or newer}} - // expected-error@+2 {{'APIFrom99()' is only available in macOS 99 or newer}} expected-note@+2 {{add 'if #available' version check}} + // expected-error@+12 {{'APIFrom99()' is only available in macOS 99 or newer}} expected-note@+12 {{add 'if #available' version check}} #funcFromClosureMacro(APIFrom99()) { + /* + expected-note@-2 6 {{in expansion of macro 'funcFromClosureMacro' here}} + expected-expansion@-3:3{{ + expected-note@1:6 2{{add '@available' attribute to enclosing instance method}} + expected-error@2:9{{'APIFrom99()' is only available in macOS 99 or newer}} + expected-note@2:9{{add 'if #available' version check}} + expected-error@14:11{{'APIFrom99()' is only available in macOS 99 or newer}} + expected-note@14:11{{add 'if #available' version check}} + }} + */ _ = APIFrom99() if #available(macOS 999, *) { _ = APIFrom99() From e443cf142a53a8e28b04184961db0ce5a93e2f8c Mon Sep 17 00:00:00 2001 From: "Henrik G. Olsson" Date: Fri, 19 Sep 2025 18:27:00 -0700 Subject: [PATCH 10/10] Fix UnexpectedCountType.swift This test case failed in CI when using a strict diff, because the output also contained the text "swift runtime: unknown backtracing setting 'warnings'". Switch to using FileCheck to ignore this output. --- .../MacroErrors/UnexpectedCountType.swift | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/test/Macros/SwiftifyImport/MacroErrors/UnexpectedCountType.swift b/test/Macros/SwiftifyImport/MacroErrors/UnexpectedCountType.swift index 0975709f52f97..1b37bf45bd7cb 100644 --- a/test/Macros/SwiftifyImport/MacroErrors/UnexpectedCountType.swift +++ b/test/Macros/SwiftifyImport/MacroErrors/UnexpectedCountType.swift @@ -10,16 +10,14 @@ func myFunc(_ ptr: UnsafePointer, _ len: String) { // REQUIRES: swift_swift_parser // RUN: %empty-directory(%t) // RUN: split-file %s %t -// RUN: %target-swift-frontend %t/test.swift -swift-version 5 -module-name main -disable-availability-checking -typecheck -plugin-path %swift-plugin-dir -dump-macro-expansions -verify 2> %t/dump.txt -// RUN: diff %t/myFunc.expected %t/dump.txt +// RUN: %target-swift-frontend %t/test.swift -swift-version 5 -module-name main -disable-availability-checking -typecheck -plugin-path %swift-plugin-dir -dump-macro-expansions -verify 2>&1 | %FileCheck %s --match-full-lines --strict-whitespace -//--- myFunc.expected -@__swiftmacro_4main6myFunc15_SwiftifyImportfMp_.swift ------------------------------- -/// This is an auto-generated wrapper for safer interop -@_alwaysEmitIntoClient @_disfavoredOverload -func myFunc(_ ptr: UnsafeBufferPointer) { - let len = String(exactly: unsafe ptr.count)! - return unsafe myFunc(ptr.baseAddress!, len) -} ------------------------------- +// CHECK:@__swiftmacro_4main6myFunc15_SwiftifyImportfMp_.swift +// CHECK-NEXT:------------------------------ +// CHECK-NEXT:/// This is an auto-generated wrapper for safer interop +// CHECK-NEXT:@_alwaysEmitIntoClient @_disfavoredOverload +// CHECK-NEXT:func myFunc(_ ptr: UnsafeBufferPointer) { +// CHECK-NEXT: let len = String(exactly: unsafe ptr.count)! +// CHECK-NEXT: return unsafe myFunc(ptr.baseAddress!, len) +// CHECK-NEXT:} +// CHECK-NEXT:------------------------------