Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Any.Type issue with @Test #76637

Open
Kyle-Ye opened this issue Sep 22, 2024 · 8 comments · May be fixed by swiftlang/swift-testing#808
Open

Any.Type issue with @Test #76637

Kyle-Ye opened this issue Sep 22, 2024 · 8 comments · May be fixed by swiftlang/swift-testing#808
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. swift macro Feature → declarations: Swift `macro` declarations

Comments

@Kyle-Ye
Copy link
Contributor

Kyle-Ye commented Sep 22, 2024

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

@Kyle-Ye Kyle-Ye added the bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. label Sep 22, 2024
@Kyle-Ye Kyle-Ye changed the title Any.Type issue with Any.Type issue with @Test Sep 22, 2024
@Kyle-Ye
Copy link
Contributor Author

Kyle-Ye commented Sep 22, 2024

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
Copy link
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.

@grynspan grynspan transferred this issue from swiftlang/swift-testing Sep 22, 2024
@Kyle-Ye
Copy link
Contributor Author

Kyle-Ye commented Sep 23, 2024

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
Copy link
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
Copy link
Contributor Author

Kyle-Ye commented Oct 7, 2024

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
Copy link
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.

@stmontgomery stmontgomery reopened this Oct 9, 2024
@stmontgomery stmontgomery added the swift macro Feature → declarations: Swift `macro` declarations label Oct 9, 2024
@grynspan
Copy link
Contributor

grynspan commented Oct 9, 2024

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
Copy link
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
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. swift macro Feature → declarations: Swift `macro` declarations
Projects
None yet
4 participants