Skip to content

Commit d58bf54

Browse files
[Diagnostics] Improve diagnostics for implicit (un)tupling. (#28076)
Fixes rdar://problem/56436226.
1 parent a49428c commit d58bf54

File tree

5 files changed

+97
-48
lines changed

5 files changed

+97
-48
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3694,18 +3694,14 @@ ERROR(pattern_type_mismatch_context,none,
36943694

36953695
ERROR(tuple_pattern_in_non_tuple_context,none,
36963696
"tuple pattern cannot match values of the non-tuple type %0", (Type))
3697-
WARNING(matching_pattern_with_many_assoc_values, none,
3698-
"cannot match several associated values at once, "
3699-
"implicitly tupling the associated values and trying to match that "
3700-
"instead", ())
3701-
WARNING(matching_tuple_pattern_with_many_assoc_values,none,
3702-
"a tuple pattern cannot match several associated values at once, "
3703-
"implicitly tupling the associated values and trying to match "
3704-
"that instead", ())
3705-
WARNING(matching_many_patterns_with_tupled_assoc_value,none,
3706-
"the enum case has a single tuple as an associated value, but "
3707-
"there are several patterns here, implicitly tupling the patterns "
3708-
"and trying to match that instead", ())
3697+
WARNING(found_one_pattern_for_several_associated_values,none,
3698+
"enum case '%0' has %1 associated values; matching them as a tuple "
3699+
"is deprecated", (StringRef, unsigned))
3700+
WARNING(converting_tuple_into_several_associated_values,none,
3701+
"enum case '%0' has %1 associated values", (StringRef, unsigned))
3702+
WARNING(converting_several_associated_values_into_tuple,none,
3703+
"enum case '%0' has one associated value that is a tuple of %1 "
3704+
"elements",(StringRef, unsigned))
37093705
ERROR(closure_argument_list_tuple,none,
37103706
"contextual closure type %0 expects %1 argument%s1, "
37113707
"but %2 %select{were|was}3 used in closure body", (Type, unsigned, unsigned, bool))

lib/Sema/TypeCheckPattern.cpp

Lines changed: 63 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -827,50 +827,89 @@ llvm::Expected<Type> PatternTypeRequest::evaluate(
827827
llvm_unreachable("bad pattern kind!");
828828
}
829829

830-
namespace {
831-
830+
/// Potentially tuple/untuple a pattern before passing it to the pattern engine.
831+
///
832832
/// We need to allow particular matches for backwards compatibility, so we
833-
/// "repair" the pattern if needed, so that the exhaustiveness checker receives
834-
/// well-formed input. Also emit diagnostics warning the user to fix their code.
833+
/// "repair" the pattern if needed. This ensures that the pattern engine
834+
/// receives well-formed input, avoiding the need to implement an additional
835+
/// compatibility hack there, as doing that is lot more tricky due to the
836+
/// different cases that need to handled.
837+
///
838+
/// We also emit diagnostics and potentially a fix-it to help the user.
835839
///
836840
/// See SR-11160 and SR-11212 for more discussion.
837841
//
838842
// type ~ (T1, ..., Tn) (n >= 2)
839-
// 1a. pat ~ ((P1, ..., Pm)) (m >= 2)
840-
// 1b. pat
843+
// 1a. pat ~ ((P1, ..., Pm)) (m >= 2) -> untuple the pattern
844+
// 1b. pat (a single pattern, not a tuple) -> handled by pattern engine
841845
// type ~ ((T1, ..., Tn)) (n >= 2)
842-
// 2. pat ~ (P1, ..., Pm) (m >= 2)
843-
void implicitlyUntuplePatternIfApplicable(ASTContext &Ctx,
844-
Pattern *&enumElementInnerPat,
845-
Type enumPayloadType) {
846+
// 2. pat ~ (P1, ..., Pm) (m >= 2) -> tuple the pattern
847+
static
848+
void repairTupleOrAssociatedValuePatternIfApplicable(
849+
ASTContext &Ctx,
850+
Pattern *&enumElementInnerPat,
851+
Type enumPayloadType,
852+
const EnumElementDecl *enumCase) {
846853
auto &DE = Ctx.Diags;
854+
bool addDeclNote = false;
847855
if (auto *tupleType = dyn_cast<TupleType>(enumPayloadType.getPointer())) {
848856
if (tupleType->getNumElements() >= 2
849857
&& enumElementInnerPat->getKind() == PatternKind::Paren) {
850858
auto *semantic = enumElementInnerPat->getSemanticsProvidingPattern();
851859
if (auto *tuplePattern = dyn_cast<TuplePattern>(semantic)) {
852860
if (tuplePattern->getNumElements() >= 2) {
853-
DE.diagnose(tuplePattern->getLoc(),
854-
diag::matching_tuple_pattern_with_many_assoc_values);
861+
auto diag = DE.diagnose(tuplePattern->getLoc(),
862+
diag::converting_tuple_into_several_associated_values,
863+
enumCase->getNameStr(), tupleType->getNumElements());
864+
auto subPattern =
865+
dyn_cast<ParenPattern>(enumElementInnerPat)->getSubPattern();
866+
867+
// We might also have code like
868+
//
869+
// enum Upair { case upair(Int, Int) }
870+
// func f(u: Upair) { switch u { case .upair(let (x, y)): () } }
871+
//
872+
// This needs a more complex rearrangement to fix the code. So only
873+
// apply the fix-it if we have a tuple immediately inside.
874+
if (subPattern->getKind() == PatternKind::Tuple) {
875+
auto leadingParen = SourceRange(enumElementInnerPat->getStartLoc());
876+
auto trailingParen = SourceRange(enumElementInnerPat->getEndLoc());
877+
diag.fixItRemove(leadingParen)
878+
.fixItRemove(trailingParen);
879+
}
880+
881+
addDeclNote = true;
855882
enumElementInnerPat = semantic;
856883
}
857884
} else {
858885
DE.diagnose(enumElementInnerPat->getLoc(),
859-
diag::matching_pattern_with_many_assoc_values);
886+
diag::found_one_pattern_for_several_associated_values,
887+
enumCase->getNameStr(),
888+
tupleType->getNumElements());
889+
addDeclNote = true;
860890
}
861891
}
862892
} else if (auto *tupleType = enumPayloadType->getAs<TupleType>()) {
863-
if (tupleType->getNumElements() >= 2
864-
&& enumElementInnerPat->getKind() == PatternKind::Tuple) {
865-
DE.diagnose(enumElementInnerPat->getLoc(),
866-
diag::matching_many_patterns_with_tupled_assoc_value);
867-
enumElementInnerPat =
868-
new (Ctx) ParenPattern(enumElementInnerPat->getStartLoc(),
869-
enumElementInnerPat,
870-
enumElementInnerPat->getEndLoc());
893+
if (tupleType->getNumElements() >= 2) {
894+
if (auto *tuplePattern = dyn_cast<TuplePattern>(enumElementInnerPat)) {
895+
DE.diagnose(enumElementInnerPat->getLoc(),
896+
diag::converting_several_associated_values_into_tuple,
897+
enumCase->getNameStr(),
898+
tupleType->getNumElements())
899+
.fixItInsert(enumElementInnerPat->getStartLoc(), "(")
900+
.fixItInsertAfter(enumElementInnerPat->getEndLoc(), ")");
901+
addDeclNote = true;
902+
enumElementInnerPat =
903+
new (Ctx) ParenPattern(enumElementInnerPat->getStartLoc(),
904+
enumElementInnerPat,
905+
enumElementInnerPat->getEndLoc());
906+
}
871907
}
872908
}
873-
}
909+
910+
if (addDeclNote)
911+
DE.diagnose(enumCase->getStartLoc(), diag::decl_declared_here,
912+
enumCase->getFullName());
874913
}
875914

876915
/// Perform top-down type coercion on the given pattern.
@@ -1450,7 +1489,8 @@ Pattern *TypeChecker::coercePatternToType(ContextualPattern pattern,
14501489
newSubOptions.setContext(TypeResolverContext::EnumPatternPayload);
14511490
newSubOptions |= TypeResolutionFlags::FromNonInferredPattern;
14521491

1453-
::implicitlyUntuplePatternIfApplicable(Context, sub, elementType);
1492+
::repairTupleOrAssociatedValuePatternIfApplicable(
1493+
Context, sub, elementType, elt);
14541494

14551495
sub = coercePatternToType(
14561496
pattern.forSubPattern(sub, /*retainTopLevel=*/false), elementType,

test/Parse/matching_patterns.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,8 @@ enum Voluntary<T> : Equatable {
100100
()
101101

102102
case .Twain(), // expected-error{{tuple pattern has the wrong length for tuple type '(T, T)'}}
103-
.Twain(_), // expected-warning {{cannot match several associated values at once, implicitly tupling the associated values and trying to match that instead}}
103+
.Twain(_), // expected-warning {{enum case 'Twain' has 2 associated values; matching them as a tuple is deprecated}}
104+
// expected-note@-25 {{'Twain' declared here}}
104105
.Twain(_, _),
105106
.Twain(_, _, _): // expected-error{{tuple pattern has the wrong length for tuple type '(T, T)'}}
106107
()
@@ -143,7 +144,8 @@ case Voluntary<Int>.Mere,
143144
.Mere(_):
144145
()
145146
case .Twain,
146-
.Twain(_), // expected-warning {{cannot match several associated values at once, implicitly tupling the associated values and trying to match that instead}}
147+
.Twain(_), // expected-warning {{enum case 'Twain' has 2 associated values; matching them as a tuple is deprecated}}
148+
// expected-note@-69 {{'Twain' declared here}}
147149
.Twain(_, _),
148150
.Twain(_, _, _): // expected-error{{tuple pattern has the wrong length for tuple type '(Int, Int)'}}
149151
()

test/Sema/exhaustive_switch.swift

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1279,21 +1279,24 @@ enum SR11212Tests {
12791279
func sr11212_content_untupled_pattern_tupled1(u: Untupled) -> (Int, Int) {
12801280
switch u {
12811281
case .upair((let x, let y)): return (x, y)
1282-
// expected-warning@-1 {{a tuple pattern cannot match several associated values at once, implicitly tupling the associated values and trying to match that instead}}
1282+
// expected-warning@-1 {{enum case 'upair' has 2 associated values}}{{16-17=}}{{31-32=}}
1283+
// expected-note@-7 {{'upair' declared here}}
12831284
}
12841285
}
12851286

12861287
func sr11212_content_untupled_pattern_tupled2(u: Untupled) -> (Int, Int) {
12871288
switch u {
12881289
case .upair(let (x, y)): return (x, y)
1289-
// expected-warning@-1 {{a tuple pattern cannot match several associated values at once, implicitly tupling the associated values and trying to match that instead}}
1290+
// expected-warning@-1 {{enum case 'upair' has 2 associated values}} // No fix-it as that would require us to peek inside the 'let' :-/
1291+
// expected-note@-15 {{'upair' declared here}}
12901292
}
12911293
}
12921294

12931295
func sr11212_content_untupled_pattern_tupled3(u: Untupled) -> (Int, Int) {
12941296
switch u {
12951297
case let .upair((x, y)): return (x, y)
1296-
// expected-warning@-1 {{a tuple pattern cannot match several associated values at once, implicitly tupling the associated values and trying to match that instead}}
1298+
// expected-warning@-1 {{enum case 'upair' has 2 associated values}}{{20-21=}}{{27-28=}}
1299+
// expected-note@-23 {{'upair' declared here}}
12971300
}
12981301
}
12991302

@@ -1312,14 +1315,16 @@ enum SR11212Tests {
13121315
func sr11212_content_untupled_pattern_ambiguous1(u: Untupled) -> (Int, Int) {
13131316
switch u {
13141317
case .upair(let u_): return u_
1315-
// expected-warning@-1 {{cannot match several associated values at once, implicitly tupling the associated values and trying to match that instead}}
1318+
// expected-warning@-1 {{enum case 'upair' has 2 associated values; matching them as a tuple is deprecated}}
1319+
// expected-note@-43 {{'upair' declared here}}
13161320
}
13171321
}
13181322

13191323
func sr11212_content_untupled_pattern_ambiguous2(u: Untupled) -> (Int, Int) {
13201324
switch u {
13211325
case let .upair(u_): return u_
1322-
// expected-warning@-1 {{cannot match several associated values at once, implicitly tupling the associated values and trying to match that instead}}
1326+
// expected-warning@-1 {{enum case 'upair' has 2 associated values; matching them as a tuple is deprecated}}
1327+
// expected-note@-51 {{'upair' declared here}}
13231328
}
13241329
}
13251330

@@ -1348,14 +1353,16 @@ enum SR11212Tests {
13481353
func sr11212_content_tupled_pattern_untupled1(t: Tupled) -> (Int, Int) {
13491354
switch t {
13501355
case .tpair(let x, let y): return (x, y)
1351-
// expected-warning@-1 {{the enum case has a single tuple as an associated value, but there are several patterns here, implicitly tupling the patterns and trying to match that instead}}
1356+
// expected-warning@-1 {{enum case 'tpair' has one associated value that is a tuple of 2 elements}}{{16-16=(}}{{30-30=)}}
1357+
// expected-note@-25 {{'tpair' declared here}}
13521358
}
13531359
}
13541360

13551361
func sr11212_content_tupled_pattern_untupled2(t: Tupled) -> (Int, Int) {
13561362
switch t {
13571363
case let .tpair(x, y): return (x, y)
1358-
// expected-warning@-1 {{the enum case has a single tuple as an associated value, but there are several patterns here, implicitly tupling the patterns and trying to match that instead}}
1364+
// expected-warning@-1 {{enum case 'tpair' has one associated value that is a tuple of 2 elements}}{{20-20=(}}{{26-26=)}}
1365+
// expected-note@-33 {{'tpair' declared here}}
13591366
}
13601367
}
13611368

@@ -1396,22 +1403,25 @@ enum SR11212Tests {
13961403
func sr11212_content_generic_pattern_untupled1(b: Box<(Int, Int)>) -> (Int, Int) {
13971404
switch b {
13981405
case .box(let x, let y): return (x, y)
1399-
// expected-warning@-1 {{the enum case has a single tuple as an associated value, but there are several patterns here, implicitly tupling the patterns and trying to match that instead}}
1406+
// expected-warning@-1 {{enum case 'box' has one associated value that is a tuple of 2 elements}}{{14-14=(}}{{28-28=)}}
1407+
// expected-note@-25 {{'box' declared here}}
14001408
}
14011409
}
14021410

14031411
func sr11212_content_generic_pattern_untupled2(b: Box<(Int, Int)>) -> (Int, Int) {
14041412
switch b {
14051413
case let .box(x, y): return (x, y)
1406-
// expected-warning@-1 {{the enum case has a single tuple as an associated value, but there are several patterns here, implicitly tupling the patterns and trying to match that instead}}
1414+
// expected-warning@-1 {{enum case 'box' has one associated value that is a tuple of 2 elements}}{{18-18=(}}{{24-24=)}}
1415+
// expected-note@-33 {{'box' declared here}}
14071416
}
14081417
}
14091418

14101419
// rdar://problem/58578342
14111420
func sr11212_content_generic_pattern_untupled3(b: Box<((Int, Int), Int)>) -> (Int, Int, Int) {
14121421
switch b {
14131422
case let .box((x, y), z): return (x, y, z)
1414-
// expected-warning@-1 {{the enum case has a single tuple as an associated value, but there are several patterns here, implicitly tupling the patterns and trying to match that instead}}
1423+
// expected-warning@-1 {{enum case 'box' has one associated value that is a tuple of 2 elements}}{{18-18=(}}{{29-29=)}}
1424+
// expected-note@-42 {{'box' declared here}}
14151425
}
14161426
}
14171427

test/decl/enum/enumtest.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,8 @@ func testDirection() {
277277
i = x
278278
break
279279

280-
case .NorthEast(let x): // expected-warning {{cannot match several associated values at once, implicitly tupling the associated values and trying to match that instead}}
280+
case .NorthEast(let x): // expected-warning {{enum case 'NorthEast' has 2 associated values; matching them as a tuple is deprecated}}
281+
// expected-note@-14 {{'NorthEast(distanceNorth:distanceEast:)' declared here}}
281282
i = x.distanceEast
282283
break
283284
}

0 commit comments

Comments
 (0)