Parameter packs, same-shape requirements, and spreading arrays

A few things that I wish I could do with parameter packs appear to be impossible. I'll outline my use case here.

I'm working with SwiftSyntax, looking for instances of layer.cornerRadius = blah:

let list = CodeBlockItemListSyntax(node.statements.map { item in
    guard let sequenceExpr = item.item.as(SequenceExprSyntax.self),
                        // helper function to turn .elements into `[ExprSyntax]`
            let exprs = expressions(sequenceExpr.elements, expecting: 3),

            // is assignment
            let _ = exprs[1].as(AssignmentExprSyntax.self),

            let memberAccess = exprs[0].as(MemberAccessExprSyntax.self),
            let layerAccess = memberAccess.base.as(DeclReferenceExprSyntax.self),
            // last member access is `cornerRadius`
            memberAccess.declName.baseName.text == "cornerRadius",
            // previous member access is `layer`
            layerAccess.baseName.text == "layer",
            let assignmentValue = exprs[2].as(ExprSyntax.self)
    else {
        // Not a corner radius assignment, skip
        return item
    }

    ...
}

I'd like to tidy up this boilerplate with variadic generics somehow. Ideally I should be able to do this:

// imagined signature of `tuplify`
func tuplify<T, each U>(_ value: [T]) -> (repeat each U)

...

let list = CodeBlockItemListSyntax(node.statements.map { item in
    guard let sequenceExpr = item.item.as(SequenceExprSyntax.self),
            let (
                memberAccess: MemberAccessExprSyntax,
                _: AssignmentExprSyntax,
                assignmentValue: ExprSyntax,
            ) = tuplify(sequenceExpr.elements), // pretend .elements is an array

            let layerAccess = memberAccess.base.as(DeclReferenceExprSyntax.self),
            // last member access is `cornerRadius`
            memberAccess.declName.baseName.text == "cornerRadius",
            // previous member access is `layer`
            layerAccess.baseName.text == "layer"
    else {
        // Not a corner radius assignment, skip
        return item
    }

    ...
}

This currently isn't possible because you can't pass in [T] in place of a repeat each T parameter. And Swift doesn't have a way to "spread" the array to fit it, either. Doing so would require a runtime check as to whether the run-time length of the array matches the compile-time length of the pack.

I can see how this use case was overlooked, but I wish an affordance had been included. Onto my next attempt...

Let's try this instead:

// new imagined signature of `tuplify`
func tuplify<each T, each U>(_ value: repeat each T) -> (repeat each U)

...

let list = CodeBlockItemListSyntax(node.statements.map { item in
    guard let sequenceExpr = item.item.as(SequenceExprSyntax.self),
            let s = sequenceExpr.elements,  // pretend `s` is an array
            let (
                memberAccess: MemberAccessExprSyntax,
                _: AssignmentExprSyntax,
                assignmentValue: ExprSyntax,
            ) = tuplify(s[0], s[1], s[2]),

            let layerAccess = memberAccess.base.as(DeclReferenceExprSyntax.self),
            // last member access is `cornerRadius`
            memberAccess.declName.baseName.text == "cornerRadius",
            // previous member access is `layer`
            layerAccess.baseName.text == "layer"
    else {
        // Not a corner radius assignment, skip
        return item
    }

    ...
}

I can't even do this, I assume because only one pack is used in the signature, so it for some reason can't guarantee that they have the same size? The error is Pack expansion requires that 'each T' and 'each U' have the same shape.

Also, you can't cast between each T and each U, as far as I can tell.

Here's how I tried to implement the last tuplify():

func tuplify<each T, each U>(_ value: repeat each T) -> (repeat each U) {
    return (repeat each value as! each U)
}

Are there any other ways to accomplish what I want here, or am I outta luck here?

Wow, that is exactly what I wanted to do, thank you so much!

I guess not having to spell out the second generic by extending Sequence itself helps, and you were able to use the "unused" pack as a parameter by typing it as .Type, very clever.

Thanks again :)

1 Like

I didn't know you could do this as an expression, what is this doing exactly? Or, where is it documented?

(repeat { _ in
    …
} ((each Element).self))

We don’t have a syntax to directly state a same-shape requirement, but we infer same-shape requirements whenever a function’s parameter or return type contains a pack expansion with more than one pack reference in it. So here for example:

func zip<each T, each U>(…) -> (repeat (each T, each U))

One of the positions where we infer same-shape requirements happens to be the where clause itself. So when all else fails, you can state a useless conformance requirement to Any:

func weirdThing<each T, each U>(_: repeat each T) -> (repeat each U)
    where (repeat (each T, each U)): Any

This is also how we print same-shape requirements in swiftinterface files, where we must make all inferred requirements explicit.

4 Likes

You can eliminate the immediately-applied closure by using a tuple expression and then discarding all elements but the “real” one that you want, eg repeat (foo(), each …).0.