-
Notifications
You must be signed in to change notification settings - Fork 10.6k
Description
Description
As described here, it seems like ResultBuilders have strange behavior when dealing with parameter packs.
This makes something like a simple tuple builder require an intermediate type to work.
The type checker for result builders seems to prefer nested types when parameter packs are involved, resulting in types like:
(((String, Int), String) Int)
where you might otherwise expect:
(String, Int, String Int)
And not only are is the former, a more "complex" option chosen, it is the only type option available, as detailed below.
Reproduction
@resultBuilder public enum TupleBuilder {
public static func buildPartialBlock<T>(first: T) -> (T) {
return first
}
public static func buildPartialBlock<each A, B>(accumulated: (repeat each A), next: B) -> (repeat each A, B) {
return (repeat each accumulated, next)
}
}
func builder<each A>(@TupleBuilder content: ()->(repeat each A)) -> (repeat each A) {
return content()
}
let manual: (String, Int, String) = {
let a = TupleBuilder.buildPartialBlock(first: "a")
let b = TupleBuilder.buildPartialBlock(accumulated: a, next: 2)
return TupleBuilder.buildPartialBlock(accumulated: b, next: "c")
}()
// Cannot convert return expression of type '((String, Int), String)' to return type '(String, Int, String)'
let built: (String, Int, String) = {
return builder {
"a"
2
"c"
}
}()
// Works
let built2: ((String, Int), String) = {
return builder {
"a"
2
"c"
}
}()
// Interestingly *doesn't* work? ...but in the opposite way as `built`?
//
// Cannot convert value of type '(String, Int, String)' to closure result type '((String, Int), String)'
let manual2: ((String, Int), String) = {
let a = TupleBuilder.buildPartialBlock(first: "a")
let b = TupleBuilder.buildPartialBlock(accumulated: a, next: 2)
return TupleBuilder.buildPartialBlock(accumulated: b, next: "c")
}()Expected behavior
I would expect both of these to work/type check:
let builtA: (String, Int, String) = {
return builder {
"a"
2
"c"
}
}()
let builtB: ((String, Int), String) = {
return builder {
"a"
2
"c"
}
}()For example a simple tuple merge function allows for both groupings:
private func merge<each A, B>(_ a: (repeat each A), _ b: B) -> (repeat each A, B) {
return (repeat each a, b)
}
let t: (String) = "1"
let tt = merge(t, 2)
let ttt1: (String, Int, String) = merge(tt, "C") // Works
let ttt2: ((String, Int), String) = merge(tt, "C") // WorksAt the very least it would be more intuitive for the compiler to prefer the type of builtA above because the (String, Int, String) tuple is less "complex"/nested (imo) compared to ((String, Int), String).
This packing complexity also compounds at each step, resulting in:
let builtC: (((String, Int), String), Int) = {
return builder {
"a"
2
"c"
4
}
}()Environment
swift-driver version: 1.127.14.1 Apple Swift version 6.2.1 (swiftlang-6.2.1.4.8 clang-1700.4.4.1)
Target: arm64-apple-macosx26.0
Additional information
Discussion about parameter pack builders: Here