-
Notifications
You must be signed in to change notification settings - Fork 10.6k
Description
I've added a discussion on the forums as well...
Description
In researching/diagnosing #85837 , I came across an weird discovery around parameter-pack-based function calls: It appears their output is materially different based on if their input is a var or a let.
Specifically, as subsequent calls are made to a parameter-packed function, the result is a nested packing that grows (presumably) infinitely.
Examples to follow:
Reproduction
Given the this parameter-packed function:
func merge<each A, B>(_ a: (repeat each A), _ b: B) -> (repeat each A, B) {
return (repeat each a, b)
}The output of a call is different when an input is a var (source):
// The `var` tuple in `a` is not flattened and remains intact in the output tuple:
let mergedVar/*: ((String, Int), String) */ = {
var a = ("a", 1) // @lvalue (String, Int)
let b = "c" // String
let c = merge(a, b) /*: ((String, Int), String) <- */
return c
}()vs when the input is a let (source):
// The `let` value of `a` is flattened into a single-level tuple:
let mergedLet /*: (String, Int, String) */ = {
let a = ("a", 1) // (String, Int)
let b = "c" // String
let c = merge(a, b) /*: (String, Int, String) <- */
return c
}()Different generated constraints:
There doesn't seem to be any major reason for this behavior to be different, but the contraints generated for the merge call do differ:
`var` vs `let`
(Bound Type Variables:
> $T3 := String
> $T4 := Int
> $T5 := String
> $T6 := ((_: $T9), $T8) -> ($T9, $T8)
- > $T10 := @lvalue (String, Int)
+ > $T10 := (String, Int)
> $T11 := String
> $T12 := ($T9, $T8)
)@lvalue-ness
It seems from the above that, for some reason, a DeclRefExpr based on a var variable is being generated with an @lValue type which I believe(?) is causing it to not be simplified further.
Additional Issues
Additionally these two examples (inversely) fail to compile:
// Cannot convert value of type '(String, Int, String)' to closure result type '((String, Int), String)'
let mergedLetError: ((String, Int), String) = {
let a = ("a", 1)
let b = "c"
let c = merge(a, b)
return c
}()
// Cannot convert value of type '((String, Int), String)' to closure result type '(String, Int, String)'
let mergedVarError: (String, Int, String) = {
var a = ("a", 1)
let b = "c"
let c = merge(a, b)
return c
}()I'm not sure if this is expected or which one would be considered correct (see below).
Expected behavior
It seems reasonable that these two calls be equivalent/equatable:
func merge<each A, B>(_ a: (repeat each A), _ b: B) -> (repeat each A, B) {
return (repeat each a, b)
}
// The `let` value of `a` is flattened
let mergedLet = {
let a = ("a", 1)
let b = "c"
let c = merge(a, b)
return c /* (String, Int, String) */
}()
// The `var` value in `a` is not flattened
let mergedVar = {
var a = ("a", 1)
let b = "c"
let c = merge(a, b)
return c /* ((String, Int), String) */
}()
assert(mergedLet == mergedVar)I would personally expect the (String, Int, String) type packing of the former example. At the very least I'd expect both to behave the same way.
As my first experience with parameter packs was this bug, I'm also not clear on if both packings are expected to be valid.
It may make sense to support one packing with a fallback to another for legacy support reasons, but I'm unclear on if there would be a significant constraint solver overhead to doing so...
Environment
Found using build-in Xcode swiftc:
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
Verified this still happens as of the most recent main-snapshot
Apple Swift version 6.3-dev (LLVM 35f48306184cd25, Swifta69dbb3366e64d8)
Target: arm64-apple-macosx26.0
Additional information
This is directly related to/the cause of #85837 . I'm not sure if this behavior is intended overall, but it doesn't seem like it would be intended in the particular case of ResultBuilders.
Additionally I don't know what behavior or existing code might depend on the current behavior.