Any.Type issue with @Test
Description
I'm using name2 pattern at first. But then I thought I should refactor to use name1 pattern. But it will give me a strange compiler error.
Is this an expected unsupported feature like swiftlang/swift-testing#202 or a bug here.
struct DemoKitTests {
@Test(
arguments: [
(type: Int.self, nominalName: "Int"),
(type: String.self, nominalName: "String"),
]
)
func name1(type: Any.Type, nominalName: String) {
#expect(API.name(type) == nominalName)
}
@Test
func name2() {
#expect(API.name(Int.self) == "Int")
#expect(API.name(String.self) == "String")
}
}
Expected behavior
Compile and test successfully.
Actual behavior
Compile error on language mode v5. (On v6 it is Any is not conform to Sendable)
xx.swift:17:4 Static method '__function(named:in:xcTestCompatibleSelector:displayName:traits:arguments:sourceLocation:parameters:testFunction:)' requires the types 'Any' and '(any Any.Type, String)' be equivalent
Steps to reproduce
See description field.
Or use the following demokit package
swift-testing version/commit hash
Xcode 16.0.0
Swift & OS version (output of swift --version ; uname -a)
Swift 6 compiler with Swift 5 language mode
Looks like it can be workaround by explicitly mark as Any.Type.
Hope the compiler/swift-testing macro can infer it automatically here.
@Test(
arguments: [
(type: Int.self as Any.Type, nominalName: "Int"),
(type: String.self as Any.Type, nominalName: "String"),
]
)
func name(type: Any.Type, nominalName: String)
Type inference here is at the compiler level. Swift Testing receives an array of [Any] and doesn't have the opportunity to do its own type checking.
Swift Testing receives an array of
[Any]and doesn't have the opportunity to do its own type checking.
Since we already have the parameter signature here - (Any.Type, String), can we add some explicit type inference hint to the Test macro's arguments variable type? (the variable c in the following example)
let a = (Int.self, "A") // Inferred as (Int.Type, String)
let b1 = [
(Int.self, "Int"),
(String.self, "String"),
] // Inferred as [Any] with an error "Heterogeneous collection literal could only be inferred to '[Any]'; add explicit type annotation if this is intentional"
let b2: [Any] = [
(Int.self, "Int"),
(String.self, "String"),
]
let c: [(Any.Type, String)] = [
(Int.self, "Int"),
(String.self, "String"),
]
Type inference occurs before the @Test macro is expanded. There's nothing we can add that's in the language today that would tell the compiler to infer a type other than [Any] here, and the signature of the test function itself does not (currently) participate in type inference for arguments to the macro.
Find a solution here - use as [(Any.Type, String)] after the Array Liternal
@Test(
arguments: [
(type: Int.self, nominalName: "Int"),
(type: String.self, nominalName: "String"),
] as [(Any.Type, String)]
)
func name1(type: Any.Type, nominalName: String) {
#expect(API.name(type) == nominalName)
}
Although the original reporter of this issue identified a workaround (explicitly typing the expression using as), I think we should pursue some kind macros enhancement to automate this, since users of Swift Testing in particular hit it fairly often. In particular, I think it would be great if there were a way for an attached macro such as @Test to include, as part of its declaration, that the types of arguments passed to some of its parameters (arguments: ...) should use the parameter types of the declaration the macro is attached to as a hint.
This is not something we can do in Swift Testing today because the failure happens before macro expansion (because the type inference on the array resolves to [Any] instead of [any Sendable] or [Any.Type].) I know I'd filed a bug against macros ages ago asking for some way to infer types more betterer but I do not know where it ended up. @DougGregor do you happen to recall that issue/radar?
There was some discussion at one point about being able to reference the enclosing Self type within the macro declaration as a way to help type inference, but it didn't go anywhere and I don't think that's what you mean. This would be a generalization of that (which was effectively constraining based on the type of the self parameter) to all of the parameter types. For example, let's imagine that ParameterType would magically be provided with the types of all of the parameters to the function to which this @Test macro is attached. Maybe the macro looks like this:
@attached(peer)
@_documentation(visibility: private)
public macro Test<C, each ParameterType>(
_ traits: any TestTrait...,
arguments collection: C
) = #externalMacro(module: "TestingMacros", type: "TestDeclarationMacro")
where C: Collection, C.Element = (repeat each ParameterType)
and if you attached it to a function like this:
@Test(
arguments: [
(type: Int.self, nominalName: "Int"),
(type: String.self, nominalName: "String"),
]
)
func name1(type: Any.Type, nominalName: String) {
#expect(API.name(type) == nominalName)
}
ParameterType would get Any.Type, String, so the collection C would have the element type (Any.Type, String).
This would all need language design and I don't know if it's worth doing, but I think it could work.