Skip to content

Commit 0b47e51

Browse files
authored
Treat @_unavailableInEmbedded like other availability attributes during macro expansion. (#1342)
This PR adds support for mapping `@_unavailableInEmbedded` to a condition trait like we do with `@available()` etc. Example: ```swift @_unavailableInEmbedded @test func `stuff that doesn't work in Embedded Swift`() { ... } ``` Which expands to include a trait of the form `.__unavailableInEmbedded(sourceLocation: ...)` of type `ConditionTrait`. ### 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 3d5dd84 commit 0b47e51

File tree

5 files changed

+71
-15
lines changed

5 files changed

+71
-15
lines changed

Sources/Testing/Traits/ConditionTrait+Macro.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,4 +124,27 @@ extension Trait where Self == ConditionTrait {
124124
sourceLocation: sourceLocation
125125
)
126126
}
127+
128+
/// Create a trait controlling availability of a test based on an
129+
/// `@_unavailableInEmbedded` attribute applied to it.
130+
///
131+
/// - Parameters:
132+
/// - sourceLocation: The source location of the test.
133+
///
134+
/// - Returns: A trait.
135+
///
136+
/// - Warning: This function is used to implement the `@Test` macro. Do not
137+
/// call it directly.
138+
public static func __unavailableInEmbedded(sourceLocation: SourceLocation) -> Self {
139+
#if hasFeature(Embedded)
140+
let isEmbedded = true
141+
#else
142+
let isEmbedded = false
143+
#endif
144+
return Self(
145+
kind: .unconditional(!isEmbedded),
146+
comments: ["Marked @_unavailableInEmbedded"],
147+
sourceLocation: sourceLocation
148+
)
149+
}
127150
}

Sources/TestingMacros/Support/Additions/FunctionDeclSyntaxAdditions.swift

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -87,14 +87,8 @@ extension FunctionDeclSyntax {
8787
var xcTestCompatibleSelector: ObjCSelectorPieceListSyntax? {
8888
// First, look for an @objc attribute with an explicit selector, and use
8989
// that if found.
90-
let objcAttribute = attributes.lazy
91-
.compactMap {
92-
if case let .attribute(attribute) = $0 {
93-
return attribute
94-
}
95-
return nil
96-
}.first { $0.attributeNameText == "objc" }
97-
if let objcAttribute, case let .objCName(objCName) = objcAttribute.arguments {
90+
if let objcAttribute = attributes(named: "objc", inModuleNamed: "Swift").first,
91+
case let .objCName(objCName) = objcAttribute.arguments {
9892
if true == objCName.first?.name?.textWithoutBackticks.hasPrefix("test") {
9993
return objCName
10094
}

Sources/TestingMacros/Support/Additions/WithAttributesSyntaxAdditions.swift

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,13 @@ extension WithAttributesSyntax {
105105
/// The first `@available(*, noasync)` or `@_unavailableFromAsync` attribute
106106
/// on this instance, if any.
107107
var noasyncAttribute: AttributeSyntax? {
108-
availability(when: .noasync).first?.attribute ?? attributes.lazy
109-
.compactMap { attribute in
110-
if case let .attribute(attribute) = attribute {
111-
return attribute
112-
}
113-
return nil
114-
}.first { $0.attributeNameText == "_unavailableFromAsync" }
108+
availability(when: .noasync).first?.attribute
109+
?? attributes(named: "_unavailableFromAsync", inModuleNamed: "Swift").first
110+
}
111+
112+
/// The first `@_unavailableInEmbedded` attribute on this instance, if any.
113+
var unavailableInEmbeddedAttribute: AttributeSyntax? {
114+
attributes(named: "_unavailableInEmbedded", inModuleNamed: "Swift").first
115115
}
116116

117117
/// Find all attributes on this node, if any, with the given name.

Sources/TestingMacros/Support/AvailabilityGuards.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,11 @@ func createAvailabilityTraitExprs(
169169
_createAvailabilityTraitExpr(from: availability, when: .obsoleted, in: context)
170170
}
171171

172+
if let attribute = decl.unavailableInEmbeddedAttribute {
173+
let sourceLocationExpr = createSourceLocationExpr(of: attribute, context: context)
174+
result += [".__unavailableInEmbedded(sourceLocation: \(sourceLocationExpr))"]
175+
}
176+
172177
return result
173178
}
174179

@@ -290,5 +295,16 @@ func createSyntaxNode(
290295
}
291296
}
292297

298+
// Handle Embedded Swift.
299+
if decl.unavailableInEmbeddedAttribute != nil {
300+
result = """
301+
#if !hasFeature(Embedded)
302+
\(result)
303+
#else
304+
\(exitStatement)
305+
#endif
306+
"""
307+
}
308+
293309
return result
294310
}

Tests/TestingTests/RunnerTests.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -819,6 +819,29 @@ final class RunnerTests: XCTestCase {
819819
await fulfillment(of: [testStarted], timeout: 0.0)
820820
}
821821

822+
@Suite(.hidden) struct UnavailableInEmbeddedTests {
823+
@Test(.hidden)
824+
@_unavailableInEmbedded
825+
func embedded() {}
826+
}
827+
828+
func testUnavailableInEmbeddedAttribute() async throws {
829+
let testStarted = expectation(description: "Test started")
830+
#if !hasFeature(Embedded)
831+
testStarted.expectedFulfillmentCount = 3
832+
#else
833+
testStarted.isInverted = true
834+
#endif
835+
var configuration = Configuration()
836+
configuration.eventHandler = { event, _ in
837+
if case .testStarted = event.kind {
838+
testStarted.fulfill()
839+
}
840+
}
841+
await runTest(for: UnavailableInEmbeddedTests.self, configuration: configuration)
842+
await fulfillment(of: [testStarted], timeout: 0.0)
843+
}
844+
822845
#if !SWT_NO_GLOBAL_ACTORS
823846
@TaskLocal static var isMainActorIsolationEnforced = false
824847

0 commit comments

Comments
 (0)