Skip to content

Commit ca81dff

Browse files
authored
Acknowledge unsafe API usages in code expanded from testing library macros (#1134)
Acknowledge unsafe API usages from various testing library macros such as `@Test`, `@Suite`, and `#expect(processExitsWith:)` which are revealed in modules which enable the new opt-in strict memory safety feature in Swift 6.2. ### Motivation: This fix allows clients of the testing library to enable [SE-0458: Opt-in Strict Memory Safety Checking](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0458-strict-memory-safety.md) if they wish and avoid diagnostics from the testing library macros in their modules. These warnings generally looked like this, before this fix: ``` ⚠️ @__swiftmacro_22MemorySafeTestingTests19exampleTestFunction33_F2EA1AA3013574E5644E5A4339F05086LL0F0fMp_.swift:23:14: warning: expression uses unsafe constructs but is not marked with 'unsafe' Testing.Test.__store($s22MemorySafeTestingTests19exampleTestFunction33_F2EA1AA3013574E5644E5A4339F05086LL0F0fMp_25generator1e3470c498e8fe35fMu_, into: outValue, asTypeAt: type) ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ``` ### Modifications: - Add test file to reproduce the new diagnostics. It's in a new module which opts-in to strict memory safety, and marked as a dependency of `TestingTests`. - Add `unsafe` keyword in the appropriate places in our macros to acknowledge the existing unsafe usages. ### 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. Fixes: rdar://151238560
1 parent d611120 commit ca81dff

File tree

9 files changed

+87
-17
lines changed

9 files changed

+87
-17
lines changed

Package.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,10 +127,25 @@ let package = Package(
127127
"Testing",
128128
"_Testing_CoreGraphics",
129129
"_Testing_Foundation",
130+
"MemorySafeTestingTests",
130131
],
131132
swiftSettings: .packageSettings
132133
),
133134

135+
// Use a plain `.target` instead of a `.testTarget` to avoid the unnecessary
136+
// overhead of having a separate test target for this module. Conceptually,
137+
// the content in this module is no different than content which would
138+
// typically be placed in the `TestingTests` target, except this content
139+
// needs the (module-wide) strict memory safety feature to be enabled.
140+
.target(
141+
name: "MemorySafeTestingTests",
142+
dependencies: [
143+
"Testing",
144+
],
145+
path: "Tests/_MemorySafeTestingTests",
146+
swiftSettings: .packageSettings + .strictMemorySafety
147+
),
148+
134149
.macro(
135150
name: "TestingMacros",
136151
dependencies: [
@@ -355,6 +370,18 @@ extension Array where Element == PackageDescription.SwiftSetting {
355370

356371
return result
357372
}
373+
374+
/// Settings necessary to enable Strict Memory Safety, introduced in
375+
/// [SE-0458: Opt-in Strict Memory Safety Checking](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0458-strict-memory-safety.md#swiftpm-integration).
376+
static var strictMemorySafety: Self {
377+
#if compiler(>=6.2)
378+
// FIXME: Adopt official `.strictMemorySafety()` condition once the minimum
379+
// supported toolchain is 6.2.
380+
[.unsafeFlags(["-strict-memory-safety"])]
381+
#else
382+
[]
383+
#endif
384+
}
358385
}
359386

360387
extension Array where Element == PackageDescription.CXXSetting {

Sources/Testing/ExitTests/ExitTest.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,9 @@ extension ExitTest {
327327
///
328328
/// - Warning: This function is used to implement the
329329
/// `#expect(processExitsWith:)` macro. Do not use it directly.
330+
#if compiler(>=6.2)
331+
@safe
332+
#endif
330333
public static func __store<each T>(
331334
_ id: (UInt64, UInt64, UInt64, UInt64),
332335
_ body: @escaping @Sendable (repeat each T) async throws -> Void,

Sources/Testing/Test+Discovery.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ extension Test {
3939
///
4040
/// - Warning: This function is used to implement the `@Test` macro. Do not
4141
/// use it directly.
42+
#if compiler(>=6.2)
43+
@safe
44+
#endif
4245
public static func __store(
4346
_ generator: @escaping @Sendable () async -> Test,
4447
into outValue: UnsafeMutableRawPointer,

Sources/TestingMacros/ConditionMacro.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -496,10 +496,11 @@ extension ExitTestConditionMacro {
496496
var recordDecl: DeclSyntax?
497497
#if !SWT_NO_LEGACY_TEST_DISCOVERY
498498
let legacyEnumName = context.makeUniqueName("__🟡$")
499+
let unsafeKeyword: TokenSyntax? = isUnsafeKeywordSupported ? .keyword(.unsafe, trailingTrivia: .space) : nil
499500
recordDecl = """
500501
enum \(legacyEnumName): Testing.__TestContentRecordContainer {
501502
nonisolated static var __testContentRecord: Testing.__TestContentRecord {
502-
\(enumName).testContentRecord
503+
\(unsafeKeyword)\(enumName).testContentRecord
503504
}
504505
}
505506
"""

Sources/TestingMacros/SuiteDeclarationMacro.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,12 +169,13 @@ public struct SuiteDeclarationMacro: MemberMacro, PeerMacro, Sendable {
169169
#if !SWT_NO_LEGACY_TEST_DISCOVERY
170170
// Emit a type that contains a reference to the test content record.
171171
let enumName = context.makeUniqueName("__🟡$")
172+
let unsafeKeyword: TokenSyntax? = isUnsafeKeywordSupported ? .keyword(.unsafe, trailingTrivia: .space) : nil
172173
result.append(
173174
"""
174175
@available(*, deprecated, message: "This type is an implementation detail of the testing library. Do not use it directly.")
175176
enum \(enumName): Testing.__TestContentRecordContainer {
176177
nonisolated static var __testContentRecord: Testing.__TestContentRecord {
177-
\(testContentRecordName)
178+
\(unsafeKeyword)\(testContentRecordName)
178179
}
179180
}
180181
"""

Sources/TestingMacros/Support/EffectfulExpressionHandling.swift

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,17 @@ extension BidirectionalCollection<Syntax> {
8686

8787
// MARK: - Inserting effect keywords/thunks
8888

89+
/// Whether or not the `unsafe` expression keyword is supported.
90+
var isUnsafeKeywordSupported: Bool {
91+
// The 'unsafe' keyword was introduced in 6.2 as part of SE-0458. Older
92+
// toolchains are not aware of it.
93+
#if compiler(>=6.2)
94+
true
95+
#else
96+
false
97+
#endif
98+
}
99+
89100
/// Make a function call expression to an effectful thunk function provided by
90101
/// the testing library.
91102
///
@@ -127,12 +138,7 @@ func applyEffectfulKeywords(_ effectfulKeywords: Set<Keyword>, to expr: some Exp
127138
let needAwait = effectfulKeywords.contains(.await) && !expr.is(AwaitExprSyntax.self)
128139
let needTry = effectfulKeywords.contains(.try) && !expr.is(TryExprSyntax.self)
129140

130-
// The 'unsafe' keyword was introduced in 6.2 as part of SE-0458. Older
131-
// toolchains are not aware of it, so avoid emitting expressions involving
132-
// that keyword when the macro has been built using an older toolchain.
133-
#if compiler(>=6.2)
134-
let needUnsafe = effectfulKeywords.contains(.unsafe) && !expr.is(UnsafeExprSyntax.self)
135-
#endif
141+
let needUnsafe = isUnsafeKeywordSupported && effectfulKeywords.contains(.unsafe) && !expr.is(UnsafeExprSyntax.self)
136142

137143
// First, add thunk function calls.
138144
if needAwait {
@@ -141,41 +147,37 @@ func applyEffectfulKeywords(_ effectfulKeywords: Set<Keyword>, to expr: some Exp
141147
if needTry {
142148
expr = _makeCallToEffectfulThunk(.identifier("__requiringTry"), passing: expr)
143149
}
144-
#if compiler(>=6.2)
145150
if needUnsafe {
146151
expr = _makeCallToEffectfulThunk(.identifier("__requiringUnsafe"), passing: expr)
147152
}
148-
#endif
149153

150154
// Then add keyword expressions. (We do this separately so we end up writing
151155
// `try await __r(__r(self))` instead of `try __r(await __r(self))` which is
152156
// less accepted by the compiler.)
153157
if needAwait {
154158
expr = ExprSyntax(
155159
AwaitExprSyntax(
156-
awaitKeyword: .keyword(.await).with(\.trailingTrivia, .space),
160+
awaitKeyword: .keyword(.await, trailingTrivia: .space),
157161
expression: expr
158162
)
159163
)
160164
}
161165
if needTry {
162166
expr = ExprSyntax(
163167
TryExprSyntax(
164-
tryKeyword: .keyword(.try).with(\.trailingTrivia, .space),
168+
tryKeyword: .keyword(.try, trailingTrivia: .space),
165169
expression: expr
166170
)
167171
)
168172
}
169-
#if compiler(>=6.2)
170173
if needUnsafe {
171174
expr = ExprSyntax(
172175
UnsafeExprSyntax(
173-
unsafeKeyword: .keyword(.unsafe).with(\.trailingTrivia, .space),
176+
unsafeKeyword: .keyword(.unsafe, trailingTrivia: .space),
174177
expression: expr
175178
)
176179
)
177180
}
178-
#endif
179181

180182
expr.leadingTrivia = originalExpr.leadingTrivia
181183
expr.trailingTrivia = originalExpr.trailingTrivia

Sources/TestingMacros/Support/TestContentGeneration.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,13 @@ func makeTestContentRecordDecl(named name: TokenSyntax, in typeName: TypeSyntax?
6363
IntegerLiteralExprSyntax(context, radix: .binary)
6464
}
6565

66+
let unsafeKeyword: TokenSyntax? = isUnsafeKeywordSupported ? .keyword(.unsafe, trailingTrivia: .space) : nil
6667
var result: DeclSyntax = """
6768
@available(*, deprecated, message: "This property is an implementation detail of the testing library. Do not use it directly.")
6869
private nonisolated \(staticKeyword(for: typeName)) let \(name): Testing.__TestContentRecord = (
6970
\(kindExpr), \(kind.commentRepresentation)
7071
0,
71-
\(accessorName),
72+
\(unsafeKeyword)\(accessorName),
7273
\(contextExpr),
7374
0
7475
)

Sources/TestingMacros/TestDeclarationMacro.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -494,12 +494,13 @@ public struct TestDeclarationMacro: PeerMacro, Sendable {
494494
#if !SWT_NO_LEGACY_TEST_DISCOVERY
495495
// Emit a type that contains a reference to the test content record.
496496
let enumName = context.makeUniqueName(thunking: functionDecl, withPrefix: "__🟡$")
497+
let unsafeKeyword: TokenSyntax? = isUnsafeKeywordSupported ? .keyword(.unsafe, trailingTrivia: .space) : nil
497498
result.append(
498499
"""
499500
@available(*, deprecated, message: "This type is an implementation detail of the testing library. Do not use it directly.")
500501
enum \(enumName): Testing.__TestContentRecordContainer {
501502
nonisolated static var __testContentRecord: Testing.__TestContentRecord {
502-
\(testContentRecordName)
503+
\(unsafeKeyword)\(testContentRecordName)
503504
}
504505
}
505506
"""
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//
2+
// This source file is part of the Swift.org open source project
3+
//
4+
// Copyright (c) 2025 Apple Inc. and the Swift project authors
5+
// Licensed under Apache License v2.0 with Runtime Library Exception
6+
//
7+
// See https://swift.org/LICENSE.txt for license information
8+
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
//
10+
11+
#if compiler(>=6.2)
12+
13+
@testable import Testing
14+
15+
#if !hasFeature(StrictMemorySafety)
16+
#error("This file requires strict memory safety to be enabled")
17+
#endif
18+
19+
@Test(.hidden)
20+
func exampleTestFunction() {}
21+
22+
@Suite(.hidden)
23+
struct ExampleSuite {
24+
@Test func example() {}
25+
}
26+
27+
func exampleExitTest() async {
28+
await #expect(processExitsWith: .success) {}
29+
}
30+
31+
#endif

0 commit comments

Comments
 (0)