Are protocol types not supported in Swift Testing test parameters?

I wanted to do the following:

import CloudKit
import Testing

@Test(
    arguments: [
      InviteType.fromGroupAddToParticipants.rawValue,
      Int(InviteType.fromGroupAddToParticipants.rawValue),
      Int64(InviteType.fromGroupAddToParticipants.rawValue),
      NSNumber(value: InviteType.fromGroupAddToParticipants.rawValue)
    ]
  ) func modify_invite_with_variable_type(_ inviteType: CKRecordValueProtocol) throws {
  ...

CKRecordValueProtocol is a protocol, as the name says.

This gave me a compiler error: Type of expression is ambiguous without a type annotation

What works is creating a concrete wrapper type. It’s somewhat ugly and verbose, but does the job.

enum CKRecordValueWrapper {
  case int32(Int32)
  case int(Int)
  case int64(Int64)
  case nsNumber(NSNumber)
  
  var value: CKRecordValueProtocol {
    switch self {
    case .int32(let v): return v
    case .int(let v): return v
    case .int64(let v): return v
    case .nsNumber(let v): return v
    }
  }
}

@Test(
    arguments: [
      CKRecordValueWrapper.int32(InviteType.fromGroupAddToParticipants.rawValue),
      CKRecordValueWrapper.int(Int(InviteType.fromGroupAddToParticipants.rawValue)),
      CKRecordValueWrapper.int64(Int64(InviteType.fromGroupAddToParticipants.rawValue)),
      CKRecordValueWrapper.nsNumber(NSNumber(value: InviteType.fromGroupAddToParticipants.rawValue))
    ]
  ) func modify_invite_with_variable_type(_ inviteType: CKRecordValueWrapper) throws {
...

Is this all by design? Are protocol types not supported as test parameters, and the best workaround is to create this kind of concrete wrapper type?

Starting with Swift 6, protocol types in parameters should have the any or some keyword. Since the some keyword is a shorthand for adding a generic type parameter (which is not supported by Swift Testing currently), you can try this instead: inviteType: any CKRecordValueProtocol

The elements of an array must all be of the same type; Swift does not support heterogenous arrays and will infer the type of this array as [Any], which is not the type you want.

Instead, specify the type of the array explicitly by writing [...] as [CKRecordValueProtocol].

3 Likes

That was the plan at one point but not anymore. Swift 6 language mode does not change anything in this respect, so a value of type P means the same thing as any P. The latter is preferred since it is more explicit, but it means the same thing as just P.

3 Likes

Instead, specify the type of the array explicitly by writing [...] as [CKRecordValueProtocol].

I tried. I then get this Sendable warning about the protocol.

With my original workaround with the wrapper type, there are no warnings and all compiles cleanly, so I think I’ll stick to that for now.

I should also mention the environment and language version. This is all happening with Xcode 26.1.1 in a SPM test target that has swift-tools-version: 5.9. Should I expect different results when I upgrade the tools/language version?

Try it with as [CKRecordValueProtocol & Sendable]

2 Likes

Try it with as [CKRecordValueProtocol & Sendable]

Oh neat. Thank you.

  @Test(
    arguments: [
      InviteType.fromGroupAddToParticipants.rawValue,
      Int(InviteType.fromGroupAddToParticipants.rawValue),
      Int64(InviteType.fromGroupAddToParticipants.rawValue),
      NSNumber(value: InviteType.fromGroupAddToParticipants.rawValue)
    ] as [CKRecordValueProtocol & Sendable]
  ) func create_invite_with_variable_type(_ inviteType: CKRecordValueProtocol) throws {

This builds and runs without warnings in my environment. :tada:

2 Likes

Another option here that might help the compiler see the types is a variadic helper:

protocol P { }

func f(_ a: P...) -> [any P] { a }