Skip to content

Commit c727506

Browse files
committed
Treat @_unavailableInEmbedded like other availability attributes during macro expansion.
This PR adds support for mapping `@_unavailableInEmbedded` to a condition trait like we do with `@available()` etc.
1 parent 992cd27 commit c727506

File tree

4 files changed

+87
-0
lines changed

4 files changed

+87
-0
lines changed

Sources/Testing/Traits/ConditionTrait+Macro.swift

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

Sources/TestingMacros/Support/Additions/WithAttributesSyntaxAdditions.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,17 @@ extension WithAttributesSyntax {
114114
}.first { $0.attributeNameText == "_unavailableFromAsync" }
115115
}
116116

117+
/// The first `@_unavailableInEmbedded` attribute on this instance, if any.
118+
var noembeddedAttribute: AttributeSyntax? {
119+
availability(when: .noasync).first?.attribute ?? attributes.lazy
120+
.compactMap { attribute in
121+
if case let .attribute(attribute) = attribute {
122+
return attribute
123+
}
124+
return nil
125+
}.first { $0.attributeNameText == "_unavailableInEmbedded" }
126+
}
127+
117128
/// Find all attributes on this node, if any, with the given name.
118129
///
119130
/// - Parameters:

Sources/TestingMacros/Support/AvailabilityGuards.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,24 @@ private func _createAvailabilityTraitExpr(
140140
}
141141
}
142142

143+
/// Create an expression that contains a test trait for symbols that are
144+
/// unavailable in Embedded Swift.
145+
///
146+
/// - Parameters:
147+
/// - attribute: The `@_unavailableInEmbedded` attribute.
148+
/// - context: The macro context in which the expression is being parsed.
149+
///
150+
/// - Returns: An instance of `ExprSyntax` representing an instance of
151+
/// ``Trait`` that can be used to prevent a test from running in Embedded
152+
/// Swift.
153+
private func _createNoEmbeddedAvailabilityTraitExpr(
154+
from attribute: AttributeSyntax,
155+
in context: some MacroExpansionContext
156+
) -> ExprSyntax {
157+
let sourceLocationExpr = createSourceLocationExpr(of: attribute, context: context)
158+
return ".__unavailableInEmbedded(sourceLocation: \(sourceLocationExpr))"
159+
}
160+
143161
/// Create an expression that contains test traits for availability (i.e.
144162
/// `.enabled(if: ...)`).
145163
///
@@ -169,6 +187,10 @@ func createAvailabilityTraitExprs(
169187
_createAvailabilityTraitExpr(from: availability, when: .obsoleted, in: context)
170188
}
171189

190+
if let noembeddedAttribute = decl.noembeddedAttribute {
191+
result += [_createNoEmbeddedAvailabilityTraitExpr(from: noembeddedAttribute, in: context)]
192+
}
193+
172194
return result
173195
}
174196

@@ -290,5 +312,16 @@ func createSyntaxNode(
290312
}
291313
}
292314

315+
// Handle Embedded Swift.
316+
if decl.noembeddedAttribute != nil {
317+
result = """
318+
#if !hasFeature(Embedded)
319+
\(result)
320+
#else
321+
\(exitStatement)
322+
#endif
323+
"""
324+
}
325+
293326
return result
294327
}

Tests/TestingTests/RunnerTests.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -819,6 +819,25 @@ 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+
testStarted.expectedFulfillmentCount = 3
831+
var configuration = Configuration()
832+
configuration.eventHandler = { event, _ in
833+
if case .testStarted = event.kind {
834+
testStarted.fulfill()
835+
}
836+
}
837+
await runTest(for: UnavailableInEmbeddedTests.self, configuration: configuration)
838+
await fulfillment(of: [testStarted], timeout: 0.0)
839+
}
840+
822841
#if !SWT_NO_GLOBAL_ACTORS
823842
@TaskLocal static var isMainActorIsolationEnforced = false
824843

0 commit comments

Comments
 (0)