Skip to content

Commit 6f7688a

Browse files
authored
Add an attribute that emits a warning. (#895)
This PR adds an attribute (an overload of `@__testing()`) that emits a warning. This allows us to emit compile-time warnings from contexts where only attributes are semantically valid. We need this in particular for test content records because their section info is comprised of a big `IfConfigDecl` node and platforms that aren't covered (i.e. the `#else` clause) don't have a way to emit a diagnostic that says "we need to fix this platform's Swift Testing port!" like we can do in other contexts with `#warning()`: ```swift #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) || os(visionOS) @_section("__DATA_CONST,__swift5_tests") #elseif os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || os(WASI) @_section("swift5_tests") #elseif os(Windows) @_section(".sw5test$B") #else // ⚠️ #warning isn't valid here, what do we do!? #endif @_used private static let record: __TestContentRecord = (...) ``` ### Checklist: - [x] Code and documentation should follow the style of the [Style Guide](https://github.com/apple/swift-testing/blob/main/Documentation/StyleGuide.md). - [x] If public symbols are renamed or modified, DocC references should be updated.
1 parent c8998c7 commit 6f7688a

File tree

5 files changed

+47
-6
lines changed

5 files changed

+47
-6
lines changed

Sources/Testing/Test+Macro.swift

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -499,12 +499,24 @@ extension Test {
499499
/// - Note: This macro has compile-time effects _only_ and should not affect a
500500
/// compiled test target.
501501
///
502-
/// - Warning: This macro is used to implement other macros declared by the testing
503-
/// library. Do not use it directly.
502+
/// - Warning: This macro is used to implement other macros declared by the
503+
/// testing library. Do not use it directly.
504504
@attached(peer) public macro __testing(
505505
semantics arguments: _const String...
506506
) = #externalMacro(module: "TestingMacros", type: "PragmaMacro")
507507

508+
/// A macro used similarly to `#warning()` but in a position where only an
509+
/// attribute is valid.
510+
///
511+
/// - Parameters:
512+
/// - message: A string to emit as a warning.
513+
///
514+
/// - Warning: This macro is used to implement other macros declared by the
515+
/// testing library. Do not use it directly.
516+
@attached(peer) public macro __testing(
517+
warning message: _const String
518+
) = #externalMacro(module: "TestingMacros", type: "PragmaMacro")
519+
508520
// MARK: - Helper functions
509521

510522
/// A function that abstracts away whether or not the `try` keyword is needed on

Sources/TestingMacros/PragmaMacro.swift

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ public import SwiftSyntaxMacros
1717
///
1818
/// - `@__testing(semantics: "nomacrowarnings")`: suppress warning diagnostics
1919
/// generated by macros. (The implementation of this use case is held in trust
20-
/// at ``MacroExpansionContext/areWarningsSuppressed``.
20+
/// at ``MacroExpansionContext/areWarningsSuppressed``.)
21+
/// - `@__testing(warning: "...")`: emits `"..."` as a diagnostic message
22+
/// attributed to the node to which the attribute is attached.
2123
///
2224
/// This type is used to implement the `@__testing` attribute macro. Do not use
2325
/// it directly.
@@ -27,6 +29,17 @@ public struct PragmaMacro: PeerMacro, Sendable {
2729
providingPeersOf declaration: some DeclSyntaxProtocol,
2830
in context: some MacroExpansionContext
2931
) throws -> [DeclSyntax] {
32+
if case let .argumentList(arguments) = node.arguments,
33+
arguments.first?.label?.textWithoutBackticks == "warning" {
34+
let targetNode = Syntax(declaration)
35+
let messages = arguments
36+
.map(\.expression)
37+
.compactMap { $0.as(StringLiteralExprSyntax.self) }
38+
.compactMap(\.representedLiteralValue)
39+
.map { DiagnosticMessage(syntax: targetNode, message: $0, severity: .warning) }
40+
context.diagnose(messages)
41+
}
42+
3043
return []
3144
}
3245

Tests/TestingMacrosTests/PragmaMacroTests.swift

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,25 @@ import SwiftSyntax
1818
struct PragmaMacroTests {
1919
@Test func findSemantics() throws {
2020
let node = """
21-
@Testing.__testing(semantics: "abc123")
22-
@__testing(semantics: "def456")
23-
let x = 0
21+
@Testing.__testing(semantics: "abc123")
22+
@__testing(semantics: "def456")
23+
let x = 0
2424
""" as DeclSyntax
2525
let nodeWithAttributes = try #require(node.asProtocol((any WithAttributesSyntax).self))
2626
let semantics = semantics(of: nodeWithAttributes)
2727
#expect(semantics == ["abc123", "def456"])
2828
}
29+
30+
@Test func warningGenerated() throws {
31+
let sourceCode = """
32+
@__testing(warning: "abc123")
33+
let x = 0
34+
"""
35+
36+
let (_, diagnostics) = try parse(sourceCode)
37+
#expect(diagnostics.count == 1)
38+
#expect(diagnostics[0].message == "abc123")
39+
#expect(diagnostics[0].diagMessage.severity == .warning)
40+
#expect(diagnostics[0].node.is(VariableDeclSyntax.self))
41+
}
2942
}

Tests/TestingMacrosTests/TestSupport/Parse.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ fileprivate let allMacros: [String: any Macro.Type] = [
3030
"Suite": SuiteDeclarationMacro.self,
3131
"Test": TestDeclarationMacro.self,
3232
"Tag": TagMacro.self,
33+
"__testing": PragmaMacro.self,
3334
]
3435

3536
func parse(_ sourceCode: String, activeMacros activeMacroNames: [String] = [], removeWhitespace: Bool = false) throws -> (sourceCode: String, diagnostics: [Diagnostic]) {

Tests/TestingTests/MiscellaneousTests.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,8 @@ struct MiscellaneousTests {
598598
@_section("swift5_tests")
599599
#elseif os(Windows)
600600
@_section(".sw5test$B")
601+
#else
602+
@__testing(warning: "Platform-specific implementation missing: test content section name unavailable")
601603
#endif
602604
@_used
603605
private static let record: __TestContentRecord = (

0 commit comments

Comments
 (0)