Skip to content

Commit afb7d42

Browse files
committed
Sema: Support optional promotion of tuple patterns in non-closure contexts
1 parent e7cc51b commit afb7d42

File tree

4 files changed

+352
-0
lines changed

4 files changed

+352
-0
lines changed

lib/Sema/TypeCheckPattern.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1166,6 +1166,24 @@ Pattern *TypeChecker::coercePatternToType(ContextualPattern pattern,
11661166
case PatternKind::Tuple: {
11671167
TuplePattern *TP = cast<TuplePattern>(P);
11681168

1169+
// Allow optional promotion only for pattern matching, not pattern binding.
1170+
const bool allowOptionalPromotion = !pattern.getPatternBindingDecl();
1171+
1172+
// If optional promotion is allowed and the coercion type is optional,
1173+
// pretend we are in the context of an enum case payload.
1174+
if (allowOptionalPromotion && type->isOptional()) {
1175+
subOptions.setContext(TypeResolverContext::EnumPatternPayload);
1176+
subOptions |= TypeResolutionFlags::FromNonInferredPattern;
1177+
}
1178+
1179+
const auto origTy = type;
1180+
if (allowOptionalPromotion) {
1181+
// Strip the coercion type of optionality. If the coercion succeeds, we
1182+
// will promote the resulting pattern to the original type by wrapping it
1183+
// in 'OptionalSome' patterns.
1184+
type = type->lookThroughAllOptionalTypes();
1185+
}
1186+
11691187
TupleType *tupleTy = type->getAs<TupleType>();
11701188

11711189
// Sometimes a paren is just a paren. If the tuple pattern has a single
@@ -1238,6 +1256,21 @@ Pattern *TypeChecker::coercePatternToType(ContextualPattern pattern,
12381256
P->setType(type);
12391257
}
12401258

1259+
if (allowOptionalPromotion) {
1260+
// If the original coercion type is optional, promote the resulting
1261+
// pattern to match the optionality.
1262+
if (auto wrappedTy = origTy->getOptionalObjectType()) {
1263+
do {
1264+
auto *OSP =
1265+
OptionalSomePattern::createImplicit(Context, P, P->getEndLoc());
1266+
OSP->setType(OptionalType::get(P->getType()));
1267+
OSP->setImplicit();
1268+
P = OSP;
1269+
wrappedTy = wrappedTy->getOptionalObjectType();
1270+
} while (wrappedTy);
1271+
}
1272+
}
1273+
12411274
return P;
12421275
}
12431276

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %gyb -D EXPLICIT=1 %s -o %t/generated.swift
3+
// RUN: %target-swift-emit-silgen -module-name m %t/generated.swift > %t/explicit.sil
4+
// RUN: %gyb -D EXPLICIT=0 %s -o %t/generated.swift
5+
// RUN: %target-swift-emit-silgen -module-name m %t/generated.swift > %t/implicit.sil
6+
// RUN: %diff -u %t/explicit.sil %t/implicit.sil
7+
8+
// Test that implicitly optional-promoted patterns produce the same SIL as their
9+
// explicit counterparts.
10+
11+
%{
12+
modes = ['regular']
13+
pattern_suffix = '?' if int(EXPLICIT) else ''
14+
15+
def get_test_case_introducer(mode):
16+
if mode == 'regular':
17+
return 'do'
18+
19+
assert False, 'unhandled mode!'
20+
}%
21+
22+
enum E {
23+
case a
24+
}
25+
26+
// Enum element patterns.
27+
func enumElementPatterns(oe: E?, ooe: E??) {
28+
% for mode in modes:
29+
${get_test_case_introducer(mode)} {
30+
if case .a${pattern_suffix} = oe {}
31+
if case .a${pattern_suffix}${pattern_suffix} = ooe {}
32+
if case .a?${pattern_suffix} = ooe {}
33+
if case .some(.a${pattern_suffix}) = ooe {}
34+
35+
% if mode == 'regular':
36+
guard case .a${pattern_suffix} = oe else { return }
37+
guard case .a${pattern_suffix}${pattern_suffix} = ooe else { return }
38+
guard case .a?${pattern_suffix} = ooe else { return }
39+
guard case .some(.a${pattern_suffix}) = ooe else { return }
40+
41+
while case .a${pattern_suffix} = oe {}
42+
while case .a${pattern_suffix}${pattern_suffix} = ooe {}
43+
while case .a?${pattern_suffix} = ooe {}
44+
while case .some(.a${pattern_suffix}) = ooe {}
45+
% end
46+
47+
for case .a${pattern_suffix} in [oe] {}
48+
for case .a${pattern_suffix}${pattern_suffix} in [ooe] {}
49+
for case .a?${pattern_suffix} in [ooe] {}
50+
for case .some(.a${pattern_suffix}) in [ooe] {}
51+
52+
switch oe {
53+
case nil: ()
54+
case .a${pattern_suffix}: ()
55+
}
56+
switch ooe {
57+
case nil, .some(nil): ()
58+
case .a${pattern_suffix}${pattern_suffix}: ()
59+
}
60+
switch ooe {
61+
case nil, .some(nil): ()
62+
case .a?${pattern_suffix}: ()
63+
}
64+
switch ooe {
65+
case nil, .some(nil): ()
66+
case .some(.a${pattern_suffix}): ()
67+
}
68+
}
69+
70+
% end
71+
}
72+
73+
// Tuple patterns.
74+
func tuplePatterns(ot: (E, E)?, oot: (E, E)??, tot: ((E, E)?, E)) {
75+
% for mode in modes:
76+
${get_test_case_introducer(mode)} {
77+
switch ot {
78+
case nil: ()
79+
case (.a, .a)${pattern_suffix}: ()
80+
}
81+
switch ot {
82+
case nil: ()
83+
case let (x, y)${pattern_suffix}: ()
84+
}
85+
switch oot {
86+
case nil, .some(nil): ()
87+
case (.a, .a)${pattern_suffix}${pattern_suffix}: ()
88+
}
89+
switch oot {
90+
case nil, .some(nil): ()
91+
case (.a, .a)?${pattern_suffix}: ()
92+
}
93+
switch tot {
94+
case (nil, .a): ()
95+
case ((.a, .a)${pattern_suffix}, .a): ()
96+
}
97+
}
98+
% end
99+
}
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %gyb %s -o %t/generated.swift
3+
// RUN: %target-swift-frontend -typecheck -verify -dump-ast %t/generated.swift | %FileCheck %t/generated.swift
4+
5+
// Test that optional promotion works for refutable patterns when matching
6+
// patterns. In 'switch' statements, additionally check for AST pattern
7+
// equivalence to show that an implicitly promoted pattern will have the same
8+
// space projection as the explicit counterpart.
9+
//
10+
// Note: We do not test 'catch' statements because patterns there are always
11+
// matched against a non-optional.
12+
13+
%{
14+
modes = ['regular']
15+
def get_test_case_introducer(mode):
16+
if mode == 'regular':
17+
return 'do'
18+
19+
assert False, 'unhandled mode!'
20+
}%
21+
22+
enum E {
23+
case a
24+
}
25+
26+
// Enum element patterns.
27+
do {
28+
var oe: E?
29+
var ooe: E??
30+
// expected-warning@-2 {{variable 'oe' was never mutated; consider changing to 'let' constant}}
31+
// expected-warning@-2 {{variable 'ooe' was never mutated; consider changing to 'let' constant}}
32+
33+
% for mode in modes:
34+
% test_case_introducer = get_test_case_introducer(mode)
35+
do {
36+
${test_case_introducer} {
37+
if case .a = oe {}
38+
if case .a = ooe {}
39+
if case .a? = ooe {}
40+
if case .some(.a) = ooe {}
41+
42+
% if mode == 'regular':
43+
guard case .a = oe else {}
44+
guard case .a = ooe else {}
45+
guard case .a? = ooe else {}
46+
guard case .some(.a) = ooe else {}
47+
48+
while case .a = oe {}
49+
while case .a = ooe {}
50+
while case .a? = ooe {}
51+
while case .some(.a) = ooe {}
52+
% end
53+
54+
for case .a in [oe] {}
55+
for case .a in [ooe] {}
56+
for case .a? in [ooe] {}
57+
for case .some(.a) in [ooe] {}
58+
59+
switch oe {
60+
case nil: ()
61+
case .a: () // CHECK: (case_stmt range=[{{.+}}.swift:[[@LINE]]
62+
// CHECK: (case_label_item
63+
// CHECK-NEXT: (pattern_optional_some implicit type='E?'
64+
// CHECK-NEXT: (pattern_enum_element type='E' E.a
65+
}
66+
switch ooe {
67+
case nil, .some(nil): ()
68+
case .a: () // CHECK: (case_stmt range=[{{.+}}.swift:[[@LINE]]
69+
// CHECK: (case_label_item
70+
// CHECK-NEXT: (pattern_optional_some implicit type='E??'
71+
// CHECK-NEXT: (pattern_optional_some implicit type='E?'
72+
// CHECK-NEXT: (pattern_enum_element type='E' E.a
73+
}
74+
switch ooe {
75+
case nil, .some(nil): ()
76+
case .a?: () // CHECK: (case_stmt range=[{{.+}}.swift:[[@LINE]]
77+
// CHECK: (case_label_item
78+
// CHECK-NEXT: (pattern_optional_some type='E??'
79+
// CHECK-NEXT: (pattern_optional_some implicit type='E?'
80+
// CHECK-NEXT: (pattern_enum_element type='E' E.a
81+
}
82+
switch ooe {
83+
case nil, .some(nil): ()
84+
case .some(.a): () // CHECK: (case_stmt range=[{{.+}}.swift:[[@LINE]]
85+
// CHECK: (case_label_item
86+
// CHECK-NEXT: (pattern_enum_element type='E??' E??.some
87+
// CHECK-NEXT: (pattern_paren type='(E?)'
88+
// CHECK-NEXT: (pattern_optional_some implicit type='(E?)'
89+
// CHECK-NEXT: (pattern_enum_element type='E' E.a
90+
}
91+
}
92+
}
93+
94+
do {
95+
${test_case_introducer} {
96+
switch oe {
97+
case nil: ()
98+
// FIXME: '.a' is OK but 'E.a' is not?
99+
case E.a: () // expected-error {{enum case 'a' is not a member of type 'E?'}}
100+
}
101+
}
102+
}
103+
104+
% end
105+
}
106+
107+
// Tuple patterns.
108+
do {
109+
var ot: (E, E)?
110+
var oot: (E, E)??
111+
var tot: ((E, E)?, E)
112+
113+
% for mode in modes:
114+
% test_case_introducer = get_test_case_introducer(mode)
115+
do {
116+
${test_case_introducer} {
117+
// FIXME: Works in a 'switch' but not an 'if'.
118+
if case (.a, .a) = ot {}
119+
// expected-error@-1 {{value of optional type '(E, E)?' must be unwrapped to a value of type '(E, E)'}}
120+
// expected-note@-2 {{coalesce}}
121+
// expected-note@-3 {{force-unwrap}}
122+
}
123+
}
124+
125+
do {
126+
${test_case_introducer} {
127+
switch ot {
128+
case nil: ()
129+
case (.a, .a): () // CHECK: (case_stmt range=[{{.+}}.swift:[[@LINE]]
130+
// CHECK: (case_label_item
131+
// CHECK-NEXT: (pattern_optional_some implicit type='(E, E)?'
132+
// CHECK-NEXT: (pattern_tuple type='(E, E)'
133+
}
134+
switch ot {
135+
case nil: ()
136+
case let (x, y): () // CHECK: (case_stmt range=[{{.+}}.swift:[[@LINE]]
137+
// CHECK: (case_label_item
138+
// CHECK-NEXT: (pattern_let type='(E, E)?'
139+
// CHECK-NEXT: (pattern_optional_some implicit type='(E, E)?'
140+
// CHECK-NEXT: (pattern_tuple type='(E, E)'
141+
}
142+
switch oot {
143+
case nil, .some(nil): ()
144+
case (.a, .a): () // CHECK: (case_stmt range=[{{.+}}.swift:[[@LINE]]
145+
// CHECK: (case_label_item
146+
// CHECK-NEXT: (pattern_optional_some implicit type='(E, E)??'
147+
// CHECK-NEXT: (pattern_optional_some implicit type='(E, E)?'
148+
// CHECK-NEXT: (pattern_tuple type='(E, E)'
149+
}
150+
switch oot {
151+
case nil, .some(nil): ()
152+
case (.a, .a)?: () // CHECK: (case_stmt range=[{{.+}}.swift:[[@LINE]]
153+
// CHECK: (case_label_item
154+
// CHECK-NEXT: (pattern_optional_some type='(E, E)??'
155+
// CHECK-NEXT: (pattern_optional_some implicit type='(E, E)?'
156+
// CHECK-NEXT: (pattern_tuple type='(E, E)'
157+
}
158+
switch tot {
159+
case (nil, .a): ()
160+
case ((.a, .a), .a): () // CHECK: (case_stmt range=[{{.+}}.swift:[[@LINE]]
161+
// CHECK: (case_label_item
162+
// CHECK-NEXT: (pattern_tuple type='((E, E)?, E)'
163+
// CHECK-NEXT: (pattern_optional_some implicit type='(E, E)?'
164+
// CHECK-NEXT: (pattern_tuple type='(E, E)'
165+
}
166+
}
167+
}
168+
169+
% end
170+
}
171+
172+
// Boolean patterns.
173+
do {
174+
var ob: Bool? // expected-warning {{variable 'ob' was never mutated; consider changing to 'let' constant}}
175+
176+
% for mode in modes:
177+
% test_case_introducer = get_test_case_introducer(mode)
178+
do {
179+
${test_case_introducer} {
180+
// FIXME: Works but the literal resolves to an expression pattern.
181+
if case true = ob {}
182+
183+
// FIXME(https://github.com/apple/swift/issues/61817): This should be exhaustive
184+
switch ob { // expected-error {{switch must be exhaustive}}
185+
// expected-note@-1 {{add missing case: '.some(_)'}}
186+
case nil: ()
187+
case true: ()
188+
case false: ()
189+
}
190+
}
191+
}
192+
193+
% end
194+
}

test/decl/var/variables.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,3 +138,29 @@ func patternBindingWithTwoEntries() {
138138
let x2 = 1, (_, _) = (1, 2)
139139
// expected-warning@-1 {{immutable value 'x2' was never used; consider replacing with '_' or removing it}}
140140
}
141+
142+
143+
// Tuple patterns cannot be bound to optional tuple types.
144+
do {
145+
let (a1, a2): (Int, Int)? // expected-error {{tuple pattern cannot match values of the non-tuple type '(Int, Int)?'}}
146+
let (b1, b2): (Int, Int)?? // expected-error {{tuple pattern cannot match values of the non-tuple type '(Int, Int)??'}}
147+
let (c1, (c2, c3)): (Int, (Int, Int)?) // expected-error {{tuple pattern cannot match values of the non-tuple type '(Int, Int)?'}}
148+
let (x: d1, y: d2): (Int, Int)? // expected-error {{tuple pattern cannot match values of the non-tuple type '(Int, Int)?'}}
149+
}
150+
151+
// Pattern binding oddities.
152+
do {
153+
let (x: a1): Bool // OK?
154+
// FIXME: Not OK?
155+
let (x: a2) = true // expected-error {{cannot convert value of type 'Bool' to specified type '(x: _)'}}
156+
157+
let (x: a3): (Int, Int) // OK?
158+
159+
// FIXME: Suggested fix does not parse
160+
let (b1, x: b2): (Int, Int) // expected-error {{tuple pattern element label 'x' must be '_'}}
161+
let (b3, _: b4): (Int, Int) // expected-error {{expected ',' separator}} expected-error{{expected pattern}}
162+
163+
let (c1, c2): (x: Int, y: Int) // OK
164+
let (c3, c4) = (true, x: true) // OK
165+
let (x: c5, y: c6) = (true, true) // OK
166+
}

0 commit comments

Comments
 (0)