mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
[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:
@@ -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() &&
|
||||
|
||||
@@ -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)
|
||||
}()
|
||||
}
|
||||
|
||||
27
test/Sema/issue-85837.swift
Normal file
27
test/Sema/issue-85837.swift
Normal 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"
|
||||
}
|
||||
Reference in New Issue
Block a user