Variadic generics cause weird errors or crash the compiler

I have a struct using variadic generics and I'm getting this error:

error: cannot use a value of protocol type in embedded Swift
30 |
31 |         target.draw(
32 |             VStack {
   |             `- error: cannot use a value of protocol type in embedded Swift
33 |                 TileText("Hello, world!", color: .Pico.white)
34 |                 TileText("Hello, world?", color: .Pico.white)
   :
38 |         )
39 |     }
40 | }

But there are no protocol types used anywhere, VStack is generic and uses the for in repeat each loop to draw the nested types. There's no values of protocol types anywhere, not in the result builder, not in the implementation, and definitely not in TileText. Are variadic generics broken in embedded Swift?

Variadic generics are not yet supported in Embedded Swift. Behind the scenes they currently rely on similar (if not the same) metadata as existentials, and require compiler changes before they can be used in the Embedded mode.

@Slava_Pestov, @kubamracek, and @John_McCall all know more about this, and feel free to correct me if needed.

Aww :/

That's not great. I can use the old SwiftUI hack:

@resultBuilder
public struct FlatteningDrawableBuilder {
    public static func buildBlock(_ d0: some Drawable) -> [Image] {
        [d0.flatten()]
    }
    
    public static func buildBlock(_ d0: some Drawable, _ d1: some Drawable) -> [Image] {
        [
            d0.flatten(),
            d1.flatten()
        ]
    }
    ...
}

But this allocates and evaluates all the pixels instead of being lazy, losing any additional information the type had, which is important for later figuring out which ones have click events and such. Is there a workaround for this, like creating vtables manually? I don't know what that would look like in Swift.

I don’t know a lot about your use case, but this looks like exactly the kind of thing that buildPartialBlock is for (avoiding tons of overloads on buildBlock).

2 Likes

There’s no inherent reason variadic generics can’t be supported in embedded Swift in the same manner as ordinary generics, via specialization. The problem here is that the SIL optimizer isn’t very good at specializing away variadic generics. So runtime metadata calls remain in the code, and it gets diagnosed.

Oh, thank you for the suggestion! This does make more sense for the workaround and is much nicer

    public static func buildPartialBlock(first: some Drawable) -> [Image] { [first.flatten()] }

    public static func buildPartialBlock(accumulated: [Image], next: some Drawable) -> [Image] {
        var acc = accumulated
        acc.append(next.flatten())
        return acc
    }

It has the same issue of allocation but at least it's much less code :slight_smile:

Though it also has to copy the array which could be a problem