Skip to content
21 changes: 21 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,18 @@ let package = Package(
"Testing",
"_Testing_CoreGraphics",
"_Testing_Foundation",
"MemorySafeTestingTests",
],
swiftSettings: .packageSettings
),
.target(
name: "MemorySafeTestingTests",
dependencies: [
"Testing",
],
path: "Tests/_MemorySafeTestingTests",
swiftSettings: .packageSettings + .strictMemorySafety
),

.macro(
name: "TestingMacros",
Expand Down Expand Up @@ -355,6 +364,18 @@ extension Array where Element == PackageDescription.SwiftSetting {

return result
}

/// Settings necessary to enable Strict Memory Safety, introduced in
/// [SE-0458: Opt-in Strict Memory Safety Checking](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0458-strict-memory-safety.md#swiftpm-integration).
static var strictMemorySafety: Self {
#if compiler(>=6.2)
// FIXME: Adopt official `.strictMemorySafety()` condition once the minimum
// supported toolchain is 6.2.
[.unsafeFlags(["-strict-memory-safety"])]
#else
[]
#endif
}
}

extension Array where Element == PackageDescription.CXXSetting {
Expand Down
3 changes: 3 additions & 0 deletions Sources/Testing/ExitTests/ExitTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,9 @@ extension ExitTest {
///
/// - Warning: This function is used to implement the
/// `#expect(processExitsWith:)` macro. Do not use it directly.
#if compiler(>=6.2)
@safe
#endif
public static func __store<each T>(
_ id: (UInt64, UInt64, UInt64, UInt64),
_ body: @escaping @Sendable (repeat each T) async throws -> Void,
Expand Down
3 changes: 3 additions & 0 deletions Sources/Testing/Test+Discovery.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ extension Test {
///
/// - Warning: This function is used to implement the `@Test` macro. Do not
/// use it directly.
#if compiler(>=6.2)
@safe
#endif
public static func __store(
_ generator: @escaping @Sendable () async -> Test,
into outValue: UnsafeMutableRawPointer,
Expand Down
3 changes: 2 additions & 1 deletion Sources/TestingMacros/ConditionMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -496,10 +496,11 @@ extension ExitTestConditionMacro {
var recordDecl: DeclSyntax?
#if !SWT_NO_LEGACY_TEST_DISCOVERY
let legacyEnumName = context.makeUniqueName("__🟡$")
let unsafeKeyword = isUnsafeKeywordSupported ? TokenSyntax.keyword(.unsafe) : nil
recordDecl = """
enum \(legacyEnumName): Testing.__TestContentRecordContainer {
nonisolated static var __testContentRecord: Testing.__TestContentRecord {
\(enumName).testContentRecord
\(unsafeKeyword) \(enumName).testContentRecord
}
}
"""
Expand Down
3 changes: 2 additions & 1 deletion Sources/TestingMacros/SuiteDeclarationMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -169,12 +169,13 @@ public struct SuiteDeclarationMacro: MemberMacro, PeerMacro, Sendable {
#if !SWT_NO_LEGACY_TEST_DISCOVERY
// Emit a type that contains a reference to the test content record.
let enumName = context.makeUniqueName("__🟡$")
let unsafeKeyword = isUnsafeKeywordSupported ? TokenSyntax.keyword(.unsafe) : nil
result.append(
"""
@available(*, deprecated, message: "This type is an implementation detail of the testing library. Do not use it directly.")
enum \(enumName): Testing.__TestContentRecordContainer {
nonisolated static var __testContentRecord: Testing.__TestContentRecord {
\(testContentRecordName)
\(unsafeKeyword) \(testContentRecordName)
}
}
"""
Expand Down
22 changes: 12 additions & 10 deletions Sources/TestingMacros/Support/EffectfulExpressionHandling.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,17 @@ extension BidirectionalCollection<Syntax> {

// MARK: - Inserting effect keywords/thunks

/// Whether or not the `unsafe` expression keyword is supported.
var isUnsafeKeywordSupported: Bool {
// The 'unsafe' keyword was introduced in 6.2 as part of SE-0458. Older
// toolchains are not aware of it.
#if compiler(>=6.2)
true
#else
false
#endif
}

/// Make a function call expression to an effectful thunk function provided by
/// the testing library.
///
Expand Down Expand Up @@ -127,12 +138,7 @@ func applyEffectfulKeywords(_ effectfulKeywords: Set<Keyword>, to expr: some Exp
let needAwait = effectfulKeywords.contains(.await) && !expr.is(AwaitExprSyntax.self)
let needTry = effectfulKeywords.contains(.try) && !expr.is(TryExprSyntax.self)

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

// First, add thunk function calls.
if needAwait {
Expand All @@ -141,11 +147,9 @@ func applyEffectfulKeywords(_ effectfulKeywords: Set<Keyword>, to expr: some Exp
if needTry {
expr = _makeCallToEffectfulThunk(.identifier("__requiringTry"), passing: expr)
}
#if compiler(>=6.2)
if needUnsafe {
expr = _makeCallToEffectfulThunk(.identifier("__requiringUnsafe"), passing: expr)
}
#endif

// Then add keyword expressions. (We do this separately so we end up writing
// `try await __r(__r(self))` instead of `try __r(await __r(self))` which is
Expand All @@ -166,7 +170,6 @@ func applyEffectfulKeywords(_ effectfulKeywords: Set<Keyword>, to expr: some Exp
)
)
}
#if compiler(>=6.2)
if needUnsafe {
expr = ExprSyntax(
UnsafeExprSyntax(
Expand All @@ -175,7 +178,6 @@ func applyEffectfulKeywords(_ effectfulKeywords: Set<Keyword>, to expr: some Exp
)
)
}
#endif

expr.leadingTrivia = originalExpr.leadingTrivia
expr.trailingTrivia = originalExpr.trailingTrivia
Expand Down
3 changes: 2 additions & 1 deletion Sources/TestingMacros/Support/TestContentGeneration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,13 @@ func makeTestContentRecordDecl(named name: TokenSyntax, in typeName: TypeSyntax?
IntegerLiteralExprSyntax(context, radix: .binary)
}

let unsafeKeyword = isUnsafeKeywordSupported ? TokenSyntax.keyword(.unsafe) : nil
var result: DeclSyntax = """
@available(*, deprecated, message: "This property is an implementation detail of the testing library. Do not use it directly.")
private nonisolated \(staticKeyword(for: typeName)) let \(name): Testing.__TestContentRecord = (
\(kindExpr), \(kind.commentRepresentation)
0,
\(accessorName),
\(unsafeKeyword) \(accessorName),
\(contextExpr),
0
)
Expand Down
3 changes: 2 additions & 1 deletion Sources/TestingMacros/TestDeclarationMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -494,12 +494,13 @@ public struct TestDeclarationMacro: PeerMacro, Sendable {
#if !SWT_NO_LEGACY_TEST_DISCOVERY
// Emit a type that contains a reference to the test content record.
let enumName = context.makeUniqueName(thunking: functionDecl, withPrefix: "__🟡$")
let unsafeKeyword = isUnsafeKeywordSupported ? TokenSyntax.keyword(.unsafe) : nil
result.append(
"""
@available(*, deprecated, message: "This type is an implementation detail of the testing library. Do not use it directly.")
enum \(enumName): Testing.__TestContentRecordContainer {
nonisolated static var __testContentRecord: Testing.__TestContentRecord {
\(testContentRecordName)
\(unsafeKeyword) \(testContentRecordName)
}
}
"""
Expand Down
31 changes: 31 additions & 0 deletions Tests/_MemorySafeTestingTests/MemorySafeTestDecls.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2025 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
//

#if compiler(>=6.2)

@testable import Testing

#if !hasFeature(StrictMemorySafety)
#error("This file requires strict memory safety to be enabled")
#endif

@Test(.hidden)
func exampleTestFunction() {}

@Suite(.hidden)
struct ExampleSuite {
@Test func example() {}
}

func exampleExitTest() async {
await #expect(processExitsWith: .success) {}
}

#endif