Skip to content

Any.Type issue with @Test #76637

Open
swiftlang/swift-testing
#808
@Kyle-Ye

Description

@Kyle-Ye
Contributor

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

DemoKit.zip

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

Activity

added
bugA deviation from expected or documented behavior. Also: expected but undesirable behavior.
on Sep 22, 2024
changed the title [-]Any.Type issue with [/-] [+]Any.Type issue with @Test[/+] on Sep 22, 2024
Kyle-Ye

Kyle-Ye commented on Sep 22, 2024

@Kyle-Ye
ContributorAuthor

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)
grynspan

grynspan commented on Sep 22, 2024

@grynspan
Contributor

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.

Kyle-Ye

Kyle-Ye commented on Sep 23, 2024

@Kyle-Ye
ContributorAuthor

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"),
]
grynspan

grynspan commented on Sep 23, 2024

@grynspan
Contributor

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.

Kyle-Ye

Kyle-Ye commented on Oct 7, 2024

@Kyle-Ye
ContributorAuthor

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)
}
stmontgomery

stmontgomery commented on Oct 9, 2024

@stmontgomery
Contributor

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.

grynspan

grynspan commented on Oct 9, 2024

@grynspan
Contributor

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?

DougGregor

DougGregor commented on Nov 7, 2024

@DougGregor
Member

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugA deviation from expected or documented behavior. Also: expected but undesirable behavior.swift macroFeature → declarations: Swift `macro` declarations

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Participants

      @stmontgomery@DougGregor@grynspan@Kyle-Ye

      Issue actions

        Any.Type issue with @Test · Issue #76637 · swiftlang/swift