ST-0011: Issue Handling Traits

Hello Swift community,

The review of ST-0011 "Issue Handling Traits" begins now and runs through Wednesday July 9, 2025. The proposal is available here:

https://github.com/swiftlang/swift-evolution/blob/main/proposals/testing/0011-issue-handling-traits.md

Reviews are an important part of the Swift evolution process. All review feedback should be either on this forum thread or, if you would like to keep your feedback private, directly to the review manager. When emailing the review manager directly, please keep the proposal link at the top of the message.

Trying it out

To try this feature out, add a dependency to the main branch of
swift-testing to your package:

dependencies: [
  ...
  .package(url: "https://github.com/swiftlang/swift-testing.git", branch: "main"),
]

Then, add a target dependency to your test target:

.testTarget(
  ...
  dependencies: [
    ...
    .product(name: "Testing", package: "swift-testing"),
  ]

Finally, import Swift Testing using @_spi(Experimental) import Testing.

What goes into a review?

The goal of the review process is to improve the proposal under review
through constructive criticism and, eventually, determine the direction of
Swift. When writing your review, here are some questions you might want to
answer in your review:

  • What is your evaluation of the proposal?
  • Is the problem being addressed significant enough to warrant a change to Swift?
  • Does this proposal fit well with the feel and direction of Swift?
  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

More information about the Swift evolution process is available at

swift-evolution/process.md at main · swiftlang/swift-evolution · GitHub

Thank you for contributing to Swift!

Paul LeMarquand

Review Manager

4 Likes

A couple of notes:

Concretely, this policy means that issues for which the value of the kind property is .system will not be passed to the closure of an issue handling trait. Similarly, it is not supported for a closure passed to compactMapIssues(_:) to return an issue for which the value of kind is.system.

I'd like to include .apiMisused in this constraint, at least for the moment. We can relax that restriction in the future if we think it's necessary, but .apiMisused is currently reserved for misuses of Swift Testing's APIs, so it falls into the same general category as .system. (Again, this doesn't mean we can't adjust the policy later, but once it's allowed it will be very difficult to disallow it.)

  • While this means that issue handlers cannot directly perform asynchronous work when processing an individual issue, future enhancements could offer alternative mechanisms for asynchronous issue processing work at the end of a test. See the Future directions section for more discussion about this.

Might be worth calling out Task.immediate {} here as a possible workaround/solution.

@Test(.compactMapIssues { issue in
  var issue = issue
  issue.comments.append("Checking whether two literals are equal")
  return issue
})

Did you consider any alternatives to the trailing closure syntax here? I don't have anything specific in mind, just asking.

1 Like

The proposal rules out passing issues as inout like this:

However, in order to suppress an issue, the parameter would also need to become optional (inout Issue? ) and this would mean that all usages would first need to be unwrapped. This feels non-ergonomic, and would differ from the standard library's typical pattern for compactMap functions.

But this is not the only option. We could make the closure return Void? instead and have the parameter remain inout Issue. With this change, all the sample code from the proposal would remain the same, save for the shadowing of the parameter:

@Test(.compactMapIssues { issue in
-  var issue = issue
   issue.comments.append("Checking whether two literals are equal")
-  return issue
})

Importantly, you can still return nil to filter out issues:

@Test(.compactMapIssues { issue in
  guard !SensitiveTerms.all.contains(where: { description.contains($0) }) else {
    return nil
  }
  issue.comments.append("Not sensitive")
})

With this change I could also see one of the simpler names like transformIssues or handleIssues making a comeback.

1 Like

This is an interesting (ab)use of the type system, but it isn't a pattern I've seen elsewhere in Swift, in particular in the standard library. I would be hesitant to adopt it without prior art, and I would be worried that changes to the compiler could cause it to fail to build in the future (i.e. if Void? starts getting treated less like Void and more like other specializations of Optional).

I wouldn’t call it abuse and there’s no risk as this pattern relies solely on the documented behavior of 1) the compiler inserting an implicit return () and 2) the compiler implicitly promoting values to optionals when possible. Neither of those features could realistically ever change, certainly not without a new language mode. So the question is only about whether this is the right shape for the API at hand. I’m sympathetic to the concern that this is unprecedented in the standard library (though neither is compactMap really a thing outside of the Sequence and Collection APIs).

To clarify, the implicit return is documented for Void, but not (to my knowledge) for Optional<Void>. While the two types are semantically related, one is not always substitutable for the other, and I'm not sure if this particular scenario is intentional or emergent behaviour.

I did a very haphazard search of the Swift repo and didn't see anything directly relevant (there's one SIL-level file that talks about return being shorthand for return () but that's about it I think.) I'm happy to be wrong about this, of course. :face_without_mouth: