Skip to content

Commit 519d17f

Browse files
committed
[Typechecker] Ban tuples with duplicate labels
1 parent 20d522d commit 519d17f

File tree

7 files changed

+118
-7
lines changed

7 files changed

+118
-7
lines changed

CHANGELOG.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,28 @@ CHANGELOG
2525
Swift 5.1
2626
---------
2727

28+
* [SR-8974][]:
29+
30+
Duplicate tuple element labels are no longer allowed, because it leads
31+
to incorrect behavior. For example:
32+
33+
```
34+
let dupLabels: (foo: Int, foo: Int) = (foo: 1, foo: 2)
35+
36+
enum Foo { case bar(x: Int, x: Int) }
37+
let f: Foo = .bar(x: 0, x: 1)
38+
```
39+
40+
will now be diagnosed as an error.
41+
42+
Note: You can still use duplicate labels when declaring functions and
43+
subscripts, as long as the internal labels are different. For example:
44+
45+
```
46+
func foo(bar x: Int, bar y: Int) {}
47+
subscript(a x: Int, a y: Int) -> Int {}
48+
```
49+
2850
* [SE-0244][]:
2951

3052
Functions can now hide their concrete return type by declaring what protocols
@@ -7671,4 +7693,5 @@ Swift 1.0
76717693
[SR-7799]: <https://bugs.swift.org/browse/SR-7799>
76727694
[SR-8109]: <https://bugs.swift.org/browse/SR-8109>
76737695
[SR-8546]: <https://bugs.swift.org/browse/SR-8546>
7696+
[SR-8974]: <https://bugs.swift.org/browse/SR-8974>
76747697
[SR-9043]: <https://bugs.swift.org/browse/SR-9043>

include/swift/AST/DiagnosticsSema.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3513,6 +3513,8 @@ ERROR(tuple_single_element,none,
35133513
"cannot create a single-element tuple with an element label", ())
35143514
ERROR(tuple_ellipsis,none,
35153515
"cannot create a variadic tuple", ())
3516+
ERROR(tuple_duplicate_label,none,
3517+
"cannot create a tuple with a duplicate element label", ())
35163518
ERROR(enum_element_ellipsis,none,
35173519
"variadic enum cases are not supported", ())
35183520

lib/Sema/MiscDiagnostics.cpp

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@ static void diagSyntacticUseRestrictions(TypeChecker &TC, const Expr *E,
6363
class DiagnoseWalker : public ASTWalker {
6464
SmallPtrSet<Expr*, 4> AlreadyDiagnosedMetatypes;
6565
SmallPtrSet<DeclRefExpr*, 4> AlreadyDiagnosedBitCasts;
66-
67-
// Keep track of acceptable DiscardAssignmentExpr's.
66+
67+
/// Keep track of acceptable DiscardAssignmentExpr's.
6868
SmallPtrSet<DiscardAssignmentExpr*, 2> CorrectDiscardAssignmentExprs;
6969

7070
/// Keep track of InOutExprs
@@ -273,6 +273,42 @@ static void diagSyntacticUseRestrictions(TypeChecker &TC, const Expr *E,
273273
}
274274
}
275275

276+
// Diagnose tuple expressions with duplicate element label
277+
if (auto *tupleExpr = dyn_cast<TupleExpr>(E)) {
278+
// FIXME: Duplicate labels on enum payloads should be diagnosed
279+
// when declared, not when called.
280+
bool isEnumCase = false;
281+
if (auto CE = dyn_cast_or_null<CallExpr>(Parent.getAsExpr())) {
282+
auto calledValue = CE->getCalledValue();
283+
if (calledValue) {
284+
isEnumCase = isa<EnumElementDecl>(calledValue);
285+
}
286+
}
287+
288+
if ((!CallArgs.count(tupleExpr)) || isEnumCase) {
289+
auto diagnose = false;
290+
291+
llvm::SmallDenseSet<Identifier> names;
292+
names.reserve(tupleExpr->getNumElements());
293+
294+
for (auto name : tupleExpr->getElementNames()) {
295+
if (name.empty())
296+
continue;
297+
298+
if (names.count(name) == 1) {
299+
diagnose = true;
300+
break;
301+
}
302+
303+
names.insert(name);
304+
}
305+
306+
if (diagnose) {
307+
TC.diagnose(tupleExpr->getLoc(), diag::tuple_duplicate_label);
308+
}
309+
}
310+
}
311+
276312
return { true, E };
277313
}
278314

lib/Sema/TypeCheckPattern.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ class ResolvePattern : public ASTVisitor<ResolvePattern,
313313
Pattern *visitExprPattern(ExprPattern *P) {
314314
if (P->isResolved())
315315
return P;
316-
316+
317317
// Try to convert to a pattern.
318318
Pattern *exprAsPattern = visit(P->getSubExpr());
319319
// If we failed, keep the ExprPattern as is.

lib/Sema/TypeCheckType.cpp

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3126,7 +3126,10 @@ Type TypeResolver::resolveTupleType(TupleTypeRepr *repr,
31263126
TypeResolutionOptions options) {
31273127
SmallVector<TupleTypeElt, 8> elements;
31283128
elements.reserve(repr->getNumElements());
3129-
3129+
3130+
llvm::SmallDenseSet<Identifier> seenEltNames;
3131+
seenEltNames.reserve(repr->getNumElements());
3132+
31303133
auto elementOptions = options;
31313134
if (!repr->isParenType()) {
31323135
elementOptions = elementOptions.withoutContext(true);
@@ -3141,14 +3144,26 @@ Type TypeResolver::resolveTupleType(TupleTypeRepr *repr,
31413144
}
31423145

31433146
bool hadError = false;
3147+
bool foundDupLabel = false;
31443148
for (unsigned i = 0, end = repr->getNumElements(); i != end; ++i) {
31453149
auto *tyR = repr->getElementType(i);
31463150

31473151
Type ty = resolveType(tyR, elementOptions);
31483152
if (!ty || ty->hasError())
31493153
hadError = true;
31503154

3151-
elements.emplace_back(ty, repr->getElementName(i), ParameterTypeFlags());
3155+
auto eltName = repr->getElementName(i);
3156+
3157+
elements.emplace_back(ty, eltName, ParameterTypeFlags());
3158+
3159+
if (eltName.empty())
3160+
continue;
3161+
3162+
if (seenEltNames.count(eltName) == 1) {
3163+
foundDupLabel = true;
3164+
}
3165+
3166+
seenEltNames.insert(eltName);
31523167
}
31533168

31543169
if (hadError)
@@ -3167,6 +3182,11 @@ Type TypeResolver::resolveTupleType(TupleTypeRepr *repr,
31673182
elements[0] = TupleTypeElt(elements[0].getType());
31683183
}
31693184

3185+
// Tuples with duplicate element labels are not permitted
3186+
if (foundDupLabel) {
3187+
diagnose(repr->getLoc(), diag::tuple_duplicate_label);
3188+
}
3189+
31703190
return TupleType::get(elements, Context);
31713191
}
31723192

test/Constraints/tuple.swift

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,3 +289,33 @@ func singleElementTuple() {
289289
let _ = ((label: 123)) // expected-error {{cannot create a single-element tuple with an element label}} {{13-20=}}
290290
let _ = ((label: 123)).label // expected-error {{cannot create a single-element tuple with an element label}} {{13-20=}}
291291
}
292+
293+
// Tuples with duplicate labels
294+
295+
let dupLabel1: (foo: Int, foo: Int) = (foo: 1, foo: 2) // expected-error 2{{cannot create a tuple with a duplicate element label}}
296+
297+
func dupLabel2(x a: Int, x b: Int) -> (y: Int, y: Int) { // expected-error {{cannot create a tuple with a duplicate element label}}
298+
return (a, b)
299+
}
300+
301+
let _ = (bar: 0, bar: "") // expected-error {{cannot create a tuple with a duplicate element label}}
302+
303+
let zeroTuple = (0,0)
304+
305+
if case (foo: let x, foo: let y) = zeroTuple { print(x+y) } // expected-error {{cannot create a tuple with a duplicate element label}}
306+
// expected-warning@-1 {{'if' condition is always true}}
307+
308+
enum BishBash { case bar(foo: Int, foo: String) }
309+
let enumLabelDup: BishBash = .bar(foo: 0, foo: "") // expected-error {{cannot create a tuple with a duplicate element label}}
310+
311+
func dupLabelClosure(_ fn: () -> Void) {}
312+
dupLabelClosure { print((bar: "", bar: 5).bar) } // expected-error {{cannot create a tuple with a duplicate element label}}
313+
314+
struct DupLabelSubscript {
315+
subscript(foo x: Int, foo y: Int) -> Int {
316+
return 0
317+
}
318+
}
319+
320+
let dupLabelSubscriptStruct = DupLabelSubscript()
321+
let _ = dupLabelSubscriptStruct[foo: 5, foo: 5] // ok

test/Parse/strange_interpolation.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ print("[\(x, foo: x)]")
3434
// expected-warning@-2{{interpolating multiple values will not form a tuple in Swift 5}}
3535
// expected-note@-3{{insert parentheses to keep current behavior}} {{11-11=(}} {{20-20=)}}
3636

37-
print("[\(foo: x, foo: x)]")
38-
// CHECK-NEXT: [(foo: 1, foo: 1)]
37+
print("[\(foo: x, bar: x)]")
38+
// CHECK-NEXT: [(foo: 1, bar: 1)]
3939
// expected-warning@-2{{interpolating multiple values will not form a tuple in Swift 5}}
4040
// expected-note@-3{{insert parentheses to keep current behavior}} {{11-11=(}} {{25-25=)}}
4141

0 commit comments

Comments
 (0)