Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions Sources/Testing/Test+Macro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,27 @@ extension Test {
}
}

// MARK: - Test pragmas

/// A macro used similarly to `#pragma` in C or `@_semantics` in the standard
/// library.
///
/// - Parameters:
/// - arguments: Zero or more context-specific arguments.
///
/// The use cases for this macro are subject to change over time as the needs of
/// the testing library change. The implementation of this macro in the
/// TestingMacros target determines how different arguments are handled.
///
/// - Note: This macro has compile-time effects _only_ and should not affect a
/// compiled test target.
///
/// - Warning: This macro is used to implement the `@Test` macro. Do not use it
/// directly.
@attached(peer) public macro __testing(
semantics arguments: _const String...
) = #externalMacro(module: "TestingMacros", type: "PragmaMacro")

// MARK: - Helper functions

/// A function that abstracts away whether or not the `try` keyword is needed on
Expand Down
1 change: 1 addition & 0 deletions Sources/TestingMacros/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ endif()

target_sources(TestingMacros PRIVATE
ConditionMacro.swift
PragmaMacro.swift
SourceLocationMacro.swift
SuiteDeclarationMacro.swift
Support/Additions/DeclGroupSyntaxAdditions.swift
Expand Down
79 changes: 79 additions & 0 deletions Sources/TestingMacros/PragmaMacro.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
//

public import SwiftSyntax
public import SwiftSyntaxMacros

/// A type describing the expansion of the `@__testing` attribute macro.
///
/// Supported uses:
///
/// - `@__testing(semantics: "nomacrowarnings")`: suppress warning diagnostics
/// generated by macros. (The implementation of this use case is held in trust
/// at ``MacroExpansionContext/areWarningsSuppressed``.
///
/// This type is used to implement the `@__testing` attribute macro. Do not use
/// it directly.
public struct PragmaMacro: PeerMacro, Sendable {
public static func expansion(
of node: AttributeSyntax,
providingPeersOf declaration: some DeclSyntaxProtocol,
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
return []
}

public static var formatMode: FormatMode {
.disabled
}
}

/// Get all pragma attributes (`@__testing`) associated with a syntax node.
///
/// - Parameters:
/// - node: The syntax node to inspect.
///
/// - Returns: The set of pragma attributes strings associated with `node`.
///
/// Attributes conditionally applied with `#if` are ignored.
func pragmas(on node: some WithAttributesSyntax) -> [AttributeSyntax] {
node.attributes
.compactMap { attribute in
if case let .attribute(attribute) = attribute {
return attribute
}
return nil
}.filter { attribute in
attribute.attributeNameText == "__testing"
}
}

/// Get all "semantics" attributed to a syntax node using the
/// `@__testing(semantics:)` attribute.
///
/// - Parameters:
/// - node: The syntax node to inspect.
///
/// - Returns: The set of "semantics" strings associated with `node`.
///
/// Attributes conditionally applied with `#if` are ignored.
func semantics(of node: some WithAttributesSyntax) -> [String] {
pragmas(on: node)
.compactMap { attribute in
if case let .argumentList(arguments) = attribute.arguments {
return arguments
}
return nil
}.filter { arguments in
arguments.first?.label?.textWithoutBackticks == "semantics"
}.flatMap { argument in
argument.compactMap { $0.expression.as(StringLiteralExprSyntax.self)?.representedLiteralValue }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,28 +84,20 @@ extension MacroExpansionContext {
/// lexical context.
///
/// The value of this property is `true` if the current lexical context
/// contains a node with the `@_semantics("testing.macros.nowarnings")`
/// attribute applied to it.
/// contains a node with the `@__testing(semantics: "nowarnings")` attribute
/// applied to it.
///
/// - Warning: This functionality is not part of the public interface of the
/// testing library. It may be modified or removed in a future update.
var areWarningsSuppressed: Bool {
#if DEBUG
for lexicalContext in self.lexicalContext {
guard let lexicalContext = lexicalContext.asProtocol((any WithAttributesSyntax).self) else {
continue
}
for attribute in lexicalContext.attributes {
if case let .attribute(attribute) = attribute,
attribute.attributeNameText == "_semantics",
case let .string(argument) = attribute.arguments,
argument.representedLiteralValue == "testing.macros.nowarnings" {
return true
}
}
}
#endif
return lexicalContext
.compactMap { $0.asProtocol((any WithAttributesSyntax).self) }
.flatMap { semantics(of: $0) }
.contains("nomacrowarnings")
#else
return false
#endif
}

/// Emit a diagnostic message.
Expand Down
1 change: 1 addition & 0 deletions Sources/TestingMacros/TestingMacrosMain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ struct TestingMacrosMain: CompilerPlugin {
ExitTestRequireMacro.self,
TagMacro.self,
SourceLocationMacro.self,
PragmaMacro.self,
]
}
}
Expand Down
29 changes: 29 additions & 0 deletions Tests/TestingMacrosTests/PragmaMacroTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
//

import Testing
@testable import TestingMacros

import SwiftParser
import SwiftSyntax

@Suite("PragmaMacro Tests")
struct PragmaMacroTests {
@Test func findSemantics() throws {
let node = """
@__testing(semantics: "abc123")
@__testing(semantics: "def456")
let x = 0
""" as DeclSyntax
let nodeWithAttributes = try #require(node.asProtocol((any WithAttributesSyntax).self))
let semantics = semantics(of: nodeWithAttributes)
#expect(semantics == ["abc123", "def456"])
}
}
4 changes: 2 additions & 2 deletions Tests/TestingTests/IssueTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -992,7 +992,7 @@ final class IssueTests: XCTestCase {
await fulfillment(of: [errorCaught, apiMisused, expectationFailed], timeout: 0.0)
}

@_semantics("testing.macros.nowarnings")
@__testing(semantics: "nomacrowarnings")
func testErrorCheckingWithRequire_ResultValueIsNever_VariousSyntaxes() throws {
// Basic expressions succeed and don't diagnose.
#expect(throws: Never.self) {}
Expand All @@ -1004,7 +1004,7 @@ final class IssueTests: XCTestCase {

// Casting to any Error throws an API misuse error because Never cannot be
// instantiated. NOTE: inner function needed for lexical context.
@_semantics("testing.macros.nowarnings")
@__testing(semantics: "nomacrowarnings")
func castToAnyError() throws {
let _: any Error = try #require(throws: Never.self) {}
}
Expand Down