Skip to content

Commit 191275f

Browse files
committed
Add support for experimental "custom availability domains" feature.
This PR updates our parsing of the `@available` attribute during expansion of the `@Test` and `@Suite` macros to support custom availability domains of the form: ```swift @available(Foo) @test func f() {} ``` This change means the macro expansion will include `if #available(Foo)` instead of `if #available(Foo, *)` in this case. Resolves rdar://160323829.
1 parent 75d5929 commit 191275f

File tree

3 files changed

+30
-9
lines changed

3 files changed

+30
-9
lines changed

Sources/TestingMacros/Support/Additions/WithAttributesSyntaxAdditions.swift

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,16 +61,17 @@ extension WithAttributesSyntax {
6161
}.first
6262

6363
var lastPlatformName: TokenSyntax? = nil
64-
var asteriskEncountered = false
64+
var wildcardEncountered = false
65+
let hasWildcard = entries.contains(where: \.isWildcard)
6566
return entries.compactMap { entry in
6667
switch entry {
6768
case let .availabilityVersionRestriction(restriction) where whenKeyword == .introduced:
68-
return Availability(attribute: attribute, platformName: restriction.platform, version: restriction.version, message: message)
69+
return Availability(attribute: attribute, platformName: restriction.platform, version: restriction.version, mayNeedTrailingWildcard: hasWildcard, message: message)
6970
case let .token(token):
7071
if case .identifier = token.tokenKind {
7172
lastPlatformName = token
72-
} else if case let .binaryOperator(op) = token.tokenKind, op == "*" {
73-
asteriskEncountered = true
73+
} else if entry.isWildcard {
74+
wildcardEncountered = true
7475
// It is syntactically valid to specify a platform name without a
7576
// version in an availability declaration, and it's used to resolve
7677
// a custom availability definition specified via the
@@ -81,7 +82,7 @@ extension WithAttributesSyntax {
8182
return Availability(attribute: attribute, platformName: lastPlatformName, version: nil, message: message)
8283
}
8384
} else if case let .keyword(keyword) = token.tokenKind, keyword == whenKeyword {
84-
if asteriskEncountered {
85+
if wildcardEncountered {
8586
// Match the "always this availability" construct, i.e.
8687
// `@available(*, deprecated)` and `@available(*, unavailable)`.
8788
return Availability(attribute: attribute, platformName: lastPlatformName, version: nil, message: message)
@@ -144,3 +145,13 @@ extension AttributeSyntax {
144145
.joined()
145146
}
146147
}
148+
149+
extension AvailabilityArgumentSyntax.Argument {
150+
var isWildcard: Bool {
151+
if case let .token(token) = self,
152+
case let .binaryOperator(op) = token.tokenKind, op == "*" {
153+
return true
154+
}
155+
return false
156+
}
157+
}

Sources/TestingMacros/Support/AvailabilityGuards.swift

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ struct Availability {
2424
/// The platform version, such as 1.2.3, if any.
2525
var version: VersionTupleSyntax?
2626

27+
/// Whether or not this availability attribute may need a trailing wildcard
28+
/// (`*`) when it is expanded into `@available()` or `#available()`.
29+
var mayNeedTrailingWildcard = true
30+
2731
/// The `message` argument to the attribute, if any.
2832
var message: SimpleStringLiteralExprSyntax?
2933

@@ -70,13 +74,14 @@ private func _createAvailabilityTraitExpr(
7074
"(\(literal: components.major), \(literal: components.minor), \(literal: components.patch))"
7175
} ?? "nil"
7276
let message = availability.message.map(\.trimmed).map(ExprSyntax.init) ?? "nil"
77+
let trailingWildcard = availability.mayNeedTrailingWildcard ? ", *" : ""
7378
let sourceLocationExpr = createSourceLocationExpr(of: availability.attribute, context: context)
7479

7580
switch (whenKeyword, availability.isSwift) {
7681
case (.introduced, false):
7782
return """
7883
.__available(\(literal: availability.platformName!.textWithoutBackticks), introduced: \(version), message: \(message), sourceLocation: \(sourceLocationExpr)) {
79-
if #available(\(availability.platformVersion!), *) {
84+
if #available(\(availability.platformVersion!)\(raw: trailingWildcard)) {
8085
return true
8186
}
8287
return false
@@ -207,8 +212,8 @@ func createSyntaxNode(
207212
do {
208213
let availableExprs: [ExprSyntax] = decl.availability(when: .introduced).lazy
209214
.filter { !$0.isSwift }
210-
.compactMap(\.platformVersion)
211-
.map { "#available(\($0), *)" }
215+
.compactMap { ($0.platformVersion, $0.mayNeedTrailingWildcard ? ", *" : "") }
216+
.map { "#available(\($0.0)\(raw: $0.1))" }
212217
if !availableExprs.isEmpty {
213218
let conditionList = ConditionElementListSyntax {
214219
for availableExpr in availableExprs {

Tests/TestingMacrosTests/TestDeclarationMacroTests.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,12 @@ struct TestDeclarationMacroTests {
415415
[
416416
#"#if os(moofOS)"#,
417417
#".__available("moofOS", obsoleted: nil, message: "Moof!", "#,
418-
]
418+
],
419+
#"@available(customAvailabilityDomain) @Test func f() {}"#:
420+
[
421+
#".__available("customAvailabilityDomain", introduced: nil, "#,
422+
#"guard #available (customAvailabilityDomain) else"#,
423+
],
419424
]
420425
)
421426
func availabilityAttributeCapture(input: String, expectedOutputs: [String]) throws {

0 commit comments

Comments
 (0)