[CSSimplify] Parameter pack wrapping logic incorrectly considers tuple LValueTypes to not be tuples (#85962)

In #65125 (and beyond) `matchTypes`, has logic to attempt to wrap an
incoming parameter in a tuple under certain conditions that might help
with type expansion.

In the case the incoming type was backed by a `var`, it would be wrapped
by an `LValueType` then be subsequently mis-diagnosed as not-a-tuple.

More details in #85924 , this this is also the cause of (and fix for)
#85837 as well...
This commit is contained in:
Chris Williams
2025-12-12 01:29:42 -05:00
committed by GitHub
parent ca12185204
commit fe0191c62c
3 changed files with 347 additions and 0 deletions

View File

@@ -7407,6 +7407,9 @@ ConstraintSystem::matchTypes(Type type1, Type type2, ConstraintKind kind,
// Notable exceptions here are: `Any` which doesn't require wrapping and
// would be handled by an existential promotion in cases where it's allowed,
// and `Optional<T>` which would be handled by optional injection.
//
// `LValueType`s are also ignored at this stage to avoid accidentally wrapping them. If they
// are valid wrapping targets, they will be tuple-wrapped after the lvalue is converted.
if (isTupleWithUnresolvedPackExpansion(origType1) ||
isTupleWithUnresolvedPackExpansion(origType2)) {
auto isTypeVariableWrappedInOptional = [](Type type) {
@@ -7417,6 +7420,7 @@ ConstraintSystem::matchTypes(Type type1, Type type2, ConstraintKind kind,
};
if (isa<TupleType>(desugar1) != isa<TupleType>(desugar2) &&
!isa<InOutType>(desugar1) && !isa<InOutType>(desugar2) &&
!isa<LValueType>(desugar1) && !isa<LValueType>(desugar2) &&
!isTypeVariableWrappedInOptional(desugar1) &&
!isTypeVariableWrappedInOptional(desugar2) &&
!desugar1->isAny() &&

View File

@@ -300,3 +300,319 @@ func test_one_element_tuple_vs_non_tuple_matching() {
test(V<Int>.self) // Ok
}
}
// Ensure correct behavior of lvalue tuple parameters
/**
Previously `var`-backed parameters would end up wrapped
in an extraneous tuple level, leading to leading to incorrect
nesting in the output type due to the `LValueType` not being unwrapped.
https://github.com/swiftlang/swift/issues/85924
*/
func test_var_let_tuple_merge_equivalence() {
func merge<each A, each B>(_ a: (repeat each A), _ b: (repeat each B)) -> (repeat each A, repeat each B) {
return (repeat each a, repeat each b)
}
// allLets, TupleFirst
let _: (String, Int, String) = {
let a = ("a", 2) // (String, Int)
let b = "c" // String
return merge(a, b)
}()
// Before #85924 was fixed, this would type as ((String, Int), String)
// varFirst, TupleFirst
let _: (String, Int, String) = {
// expected-warning@+1{{variable 'a' was never mutated; consider changing to 'let' constant}}
var a = ("a", 2) // @lvalue (String, Int)
let b = "c" // String
return merge(a, b)
}()
// varSecond, TupleFirst
let _: (String, Int, String) = {
let a = ("a", 2) // (String, Int)
// expected-warning@+1{{variable 'b' was never mutated; consider changing to 'let' constant}}
var b = "c" // @lvalue String
return merge(a, b)
}()
// allVars, TupleFirst
let _: (String, Int, String) = {
// expected-warning@+1{{variable 'a' was never mutated; consider changing to 'let' constant}}
var a = ("a", 2) // @lvalue (String, Int)
// expected-warning@+1{{variable 'b' was never mutated; consider changing to 'let' constant}}
var b = "c" // @lvalue String
return merge(a, b)
}()
// allLets, TupleSecond
let _: (String, Int, String) = {
let a = "a"
let b = (2, "c")
return merge(a, b)
}()
// varFirst, TupleSecond
let _: (String, Int, String) = {
// expected-warning@+1{{variable 'a' was never mutated; consider changing to 'let' constant}}
var a = "a"
let b = (2, "c")
return merge(a, b)
}()
// varSecond, TupleSecond
let _: (String, Int, String) = {
let a = "a"
// expected-warning@+1{{variable 'b' was never mutated; consider changing to 'let' constant}}
var b = (2, "c")
return merge(a, b)
}()
// allVars, TupleSecond
let _: (String, Int, String) = {
// expected-warning@+1{{variable 'a' was never mutated; consider changing to 'let' constant}}
var a = "a"
// expected-warning@+1{{variable 'b' was never mutated; consider changing to 'let' constant}}
var b = (2, "c")
return merge(a, b)
}()
// allLets, MultiTuple
let _: (String, Int, String) = {
let a = ("a")
let b = (2, "c")
return merge(a, b)
}()
// varFirst, MultiTuple
let _: (String, Int, String) = {
// expected-warning@+1{{variable 'a' was never mutated; consider changing to 'let' constant}}
var a = ("a")
let b = (2, "c")
return merge(a, b)
}()
// varSecond, MultiTuple
let _: (String, Int, String) = {
let a = ("a")
// expected-warning@+1{{variable 'b' was never mutated; consider changing to 'let' constant}}
var b = (2, "c")
return merge(a, b)
}()
// allVars, MultiTuple
let _: (String, Int, String) = {
// expected-warning@+1{{variable 'a' was never mutated; consider changing to 'let' constant}}
var a = ("a")
// expected-warning@+1{{variable 'b' was never mutated; consider changing to 'let' constant}}
var b = (2, "c")
return merge(a, b)
}()
}
func test_var_let_tuple_append_equivalence() {
func append<each A, B>(_ a: (repeat each A), _ b: B) -> (repeat each A, B) {
return (repeat each a, b)
}
// allLets, TupleFirst
let _: (String, Int, String) = {
let a = ("a", 2) // (String, Int)
let b = "c" // String
return append(a, b)
}()
// varFirst, TupleFirst
let _: (String, Int, String) = {
// expected-warning@+1{{variable 'a' was never mutated; consider changing to 'let' constant}}
var a = ("a", 2) // @lvalue (String, Int)
let b = "c" // String
return append(a, b)
}()
// varSecond, TupleFirst
let _: (String, Int, String) = {
let a = ("a", 2) // (String, Int)
// expected-warning@+1{{variable 'b' was never mutated; consider changing to 'let' constant}}
var b = "c" // @lvalue String
return append(a, b)
}()
// allVars, TupleFirst
let _: (String, Int, String) = {
// expected-warning@+1{{variable 'a' was never mutated; consider changing to 'let' constant}}
var a = ("a", 2) // @lvalue (String, Int)
// expected-warning@+1{{variable 'b' was never mutated; consider changing to 'let' constant}}
var b = "c" // @lvalue String
return append(a, b)
}()
// allLets, TupleSecond
let _: (String, (Int, String)) = {
let a = "a"
let b = (2, "c")
return append(a, b)
}()
// varFirst, TupleSecond
let _: (String, (Int, String)) = {
// expected-warning@+1{{variable 'a' was never mutated; consider changing to 'let' constant}}
var a = "a"
let b = (2, "c")
return append(a, b)
}()
// varSecond, TupleSecond
let _: (String, (Int, String)) = {
let a = "a"
// expected-warning@+1{{variable 'b' was never mutated; consider changing to 'let' constant}}
var b = (2, "c")
return append(a, b)
}()
// allVars, TupleSecond
let _: (String, (Int, String)) = {
// expected-warning@+1{{variable 'a' was never mutated; consider changing to 'let' constant}}
var a = "a"
// expected-warning@+1{{variable 'b' was never mutated; consider changing to 'let' constant}}
var b = (2, "c")
return append(a, b)
}()
// allLets, MultiTuple
let _: (String, (Int, String)) = {
let a = ("a")
let b = (2, "c")
return append(a, b)
}()
// varFirst, MultiTuple
let _: (String, (Int, String)) = {
// expected-warning@+1{{variable 'a' was never mutated; consider changing to 'let' constant}}
var a = ("a")
let b = (2, "c")
return append(a, b)
}()
// varSecond, MultiTuple
let _: (String, (Int, String)) = {
let a = ("a")
// expected-warning@+1{{variable 'b' was never mutated; consider changing to 'let' constant}}
var b = (2, "c")
return append(a, b)
}()
// allVars, MultiTuple
let _: (String, (Int, String)) = {
// expected-warning@+1{{variable 'a' was never mutated; consider changing to 'let' constant}}
var a = ("a")
// expected-warning@+1{{variable 'b' was never mutated; consider changing to 'let' constant}}
var b = (2, "c")
return append(a, b)
}()
}
func test_var_let_tuple_prefixOnto_equivalence() {
func prefixOnto<A, each B>(_ a: A, _ b: (repeat each B)) -> (A, repeat each B) {
return (a, repeat each b)
}
// allLets, TupleFirst
let _: ((String, Int), String) = {
let a = ("a", 2)
let b = "c"
return prefixOnto(a, b)
}()
// varFirst, TupleFirst
let _: ((String, Int), String) = {
// expected-warning@+1{{variable 'a' was never mutated; consider changing to 'let' constant}}
var a = ("a", 2)
let b = "c"
return prefixOnto(a, b)
}()
// varSecond, TupleFirst
let _: ((String, Int), String) = {
let a = ("a", 2)
// expected-warning@+1{{variable 'b' was never mutated; consider changing to 'let' constant}}
var b = "c"
return prefixOnto(a, b)
}()
// allVars, TupleFirst
let _: ((String, Int), String) = {
// expected-warning@+1{{variable 'a' was never mutated; consider changing to 'let' constant}}
var a = ("a", 2)
// expected-warning@+1{{variable 'b' was never mutated; consider changing to 'let' constant}}
var b = "c"
return prefixOnto(a, b)
}()
// allLets, TupleSecond
let _: (String, Int, String) = {
let a = "a"
let b = (2, "c")
return prefixOnto(a, b)
}()
// varFirst, TupleSecond
let _: (String, Int, String) = {
// expected-warning@+1{{variable 'a' was never mutated; consider changing to 'let' constant}}
var a = "a"
let b = (2, "c")
return prefixOnto(a, b)
}()
// varSecond, TupleSecond
let _: (String, Int, String) = {
let a = "a"
// expected-warning@+1{{variable 'b' was never mutated; consider changing to 'let' constant}}
var b = (2, "c")
return prefixOnto(a, b)
}()
// allVars, TupleSecond
let _: (String, Int, String) = {
// expected-warning@+1{{variable 'a' was never mutated; consider changing to 'let' constant}}
var a = "a"
// expected-warning@+1{{variable 'b' was never mutated; consider changing to 'let' constant}}
var b = (2, "c")
return prefixOnto(a, b)
}()
// allLets, MultiTuple
let _: (String, Int, String) = {
let a = ("a")
let b = (2, "c")
return prefixOnto(a, b)
}()
// varFirst, MultiTuple
let _: (String, Int, String) = {
// expected-warning@+1{{variable 'a' was never mutated; consider changing to 'let' constant}}
var a = ("a")
let b = (2, "c")
return prefixOnto(a, b)
}()
// varSecond, MultiTuple
let _: (String, Int, String) = {
let a = ("a")
// expected-warning@+1{{variable 'b' was never mutated; consider changing to 'let' constant}}
var b = (2, "c")
return prefixOnto(a, b)
}()
// allVars, MultiTuple
let _: (String, Int, String) = {
// expected-warning@+1 {{variable 'a' was never mutated; consider changing to 'let' constant}}
var a = ("a")
// expected-warning@+1 {{variable 'b' was never mutated; consider changing to 'let' constant}}
var b = (2, "c")
return prefixOnto(a, b)
}()
}

View File

@@ -0,0 +1,27 @@
// RUN: %target-typecheck-verify-swift
// Verifies fix for Github Issue #85837
// https://github.com/swiftlang/swift/issues/85837
//
//
@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()
}
// Ensure this optimally packs parameters
let built: (String, Int, String) = builder {
"a"
2
"c"
}