Skip to content

Commit 4aaec65

Browse files
committed
[CS] Warn on mismatched tuple labels for subtyping
This is something that we'd like to fix to bring in line with tuple conversion, so start warning on cases where it occurs.
1 parent da36a2c commit 4aaec65

File tree

9 files changed

+103
-0
lines changed

9 files changed

+103
-0
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1191,6 +1191,10 @@ WARNING(coercion_may_fail_warning,none,
11911191
"coercion from %0 to %1 may fail; use 'as?' or 'as!' instead",
11921192
(Type, Type))
11931193

1194+
WARNING(tuple_label_mismatch_warning,none,
1195+
"tuple conversion from %0 to %1 mismatches labels",
1196+
(Type, Type))
1197+
11941198
ERROR(missing_explicit_conversion,none,
11951199
"%0 is not implicitly convertible to %1; "
11961200
"did you mean to use 'as' to explicitly convert?", (Type, Type))

include/swift/Sema/CSFix.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,9 @@ enum class FixKind : uint8_t {
376376
/// Ignore a type mismatch between deduced element type and externally
377377
/// imposed one.
378378
IgnoreCollectionElementContextualMismatch,
379+
380+
/// Produce a warning for a tuple label mismatch.
381+
AllowTupleLabelMismatch,
379382
};
380383

381384
class ConstraintFix {
@@ -2798,6 +2801,27 @@ class AllowInvalidStaticMemberRefOnProtocolMetatype final
27982801
}
27992802
};
28002803

2804+
/// Emit a warning for mismatched tuple labels.
2805+
class AllowTupleLabelMismatch final : public ContextualMismatch {
2806+
AllowTupleLabelMismatch(ConstraintSystem &cs, Type fromType, Type toType,
2807+
ConstraintLocator *locator)
2808+
: ContextualMismatch(cs, FixKind::AllowTupleLabelMismatch, fromType,
2809+
toType, locator, /*warning*/ true) {}
2810+
2811+
public:
2812+
std::string getName() const override { return "allow tuple label mismatch"; }
2813+
2814+
bool diagnose(const Solution &solution, bool asNote = false) const override;
2815+
2816+
static AllowTupleLabelMismatch *create(ConstraintSystem &cs, Type fromType,
2817+
Type toType,
2818+
ConstraintLocator *locator);
2819+
2820+
static bool classof(const ConstraintFix *fix) {
2821+
return fix->getKind() == FixKind::AllowTupleLabelMismatch;
2822+
}
2823+
};
2824+
28012825
class AllowNonOptionalWeak final : public ConstraintFix {
28022826
AllowNonOptionalWeak(ConstraintSystem &cs, ConstraintLocator *locator)
28032827
: ConstraintFix(cs, FixKind::AllowNonOptionalWeak, locator) {}

lib/Sema/CSDiagnostics.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7867,6 +7867,12 @@ bool InvalidWeakAttributeUse::diagnoseAsError() {
78677867
return true;
78687868
}
78697869

7870+
bool TupleLabelMismatchWarning::diagnoseAsError() {
7871+
emitDiagnostic(diag::tuple_label_mismatch_warning, getFromType(), getToType())
7872+
.highlight(getSourceRange());
7873+
return true;
7874+
}
7875+
78707876
bool SwiftToCPointerConversionInInvalidContext::diagnoseAsError() {
78717877
auto argInfo = getFunctionArgApplyInfo(getLocator());
78727878
if (!argInfo)

lib/Sema/CSDiagnostics.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2620,6 +2620,16 @@ class InvalidWeakAttributeUse final : public FailureDiagnostic {
26202620
bool diagnoseAsError() override;
26212621
};
26222622

2623+
/// Emit a warning for mismatched tuple labels.
2624+
class TupleLabelMismatchWarning final : public ContextualFailure {
2625+
public:
2626+
TupleLabelMismatchWarning(const Solution &solution, Type fromType,
2627+
Type toType, ConstraintLocator *locator)
2628+
: ContextualFailure(solution, fromType, toType, locator) {}
2629+
2630+
bool diagnoseAsError() override;
2631+
};
2632+
26232633
/// Diagnose situations where Swift -> C pointer implicit conversion
26242634
/// is attempted on a Swift function instead of one imported from C header.
26252635
///

lib/Sema/CSFix.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2024,6 +2024,20 @@ AllowNonOptionalWeak *AllowNonOptionalWeak::create(ConstraintSystem &cs,
20242024
return new (cs.getAllocator()) AllowNonOptionalWeak(cs, locator);
20252025
}
20262026

2027+
AllowTupleLabelMismatch *
2028+
AllowTupleLabelMismatch::create(ConstraintSystem &cs, Type fromType,
2029+
Type toType, ConstraintLocator *locator) {
2030+
return new (cs.getAllocator())
2031+
AllowTupleLabelMismatch(cs, fromType, toType, locator);
2032+
}
2033+
2034+
bool AllowTupleLabelMismatch::diagnose(const Solution &solution,
2035+
bool asNote) const {
2036+
TupleLabelMismatchWarning warning(solution, getFromType(), getToType(),
2037+
getLocator());
2038+
return warning.diagnose(asNote);
2039+
}
2040+
20272041
bool AllowSwiftToCPointerConversion::diagnose(const Solution &solution,
20282042
bool asNote) const {
20292043
SwiftToCPointerConversionInInvalidContext failure(solution, getLocator());

lib/Sema/CSSimplify.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1620,6 +1620,7 @@ ConstraintSystem::matchTupleTypes(TupleType *tuple1, TupleType *tuple2,
16201620
}
16211621
}
16221622

1623+
auto hasLabelMismatch = false;
16231624
for (unsigned i = 0, n = tuple1->getNumElements(); i != n; ++i) {
16241625
const auto &elt1 = tuple1->getElement(i);
16251626
const auto &elt2 = tuple2->getElement(i);
@@ -1641,6 +1642,11 @@ ConstraintSystem::matchTupleTypes(TupleType *tuple1, TupleType *tuple2,
16411642
// used at some other position.
16421643
if (elt2.hasName() && tuple1->getNamedElementId(elt2.getName()) != -1)
16431644
return getTypeMatchFailure(locator);
1645+
1646+
// If both elements have names and they mismatch, make a note of it
1647+
// so we can emit a warning.
1648+
if (elt1.hasName() && elt2.hasName())
1649+
hasLabelMismatch = true;
16441650
}
16451651
}
16461652

@@ -1656,6 +1662,13 @@ ConstraintSystem::matchTupleTypes(TupleType *tuple1, TupleType *tuple2,
16561662
return result;
16571663
}
16581664

1665+
if (hasLabelMismatch) {
1666+
// If we had a label mismatch, emit a warning. This is something we
1667+
// shouldn't permit, as it's more permissive than what a conversion would
1668+
// allow. Ideally we'd turn this into an error in Swift 6 mode.
1669+
recordFix(AllowTupleLabelMismatch::create(
1670+
*this, tuple1, tuple2, getConstraintLocator(locator)));
1671+
}
16591672
return getTypeMatchSuccess();
16601673
}
16611674

@@ -12031,6 +12044,7 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyFixConstraint(
1203112044
case FixKind::DropThrowsAttribute:
1203212045
case FixKind::DropAsyncAttribute:
1203312046
case FixKind::AllowSwiftToCPointerConversion:
12047+
case FixKind::AllowTupleLabelMismatch:
1203412048
llvm_unreachable("handled elsewhere");
1203512049
}
1203612050

test/Constraints/construction.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@ func test_that_optionality_of_closure_result_is_preserved() {
268268
// rdar://85263844 - initializer 'init(_:)' requires the types be equivalent
269269
func rdar85263844(arr: [(q: String, a: Int)]) -> AnySequence<(question: String, answer: Int)> {
270270
AnySequence(arr.map { $0 })
271+
// expected-warning@-1 {{tuple conversion from '(q: String, a: Int)' to '(question: String, answer: Int)' mismatches labels}}
271272
}
272273

273274
// Another case for rdar://85263844
@@ -284,9 +285,11 @@ public struct S4<T> {
284285
extension S4 where T == (outer: Int, y: Int) {
285286
init(arr: [Int]) {
286287
self.init(arr.map { (inner: $0, y: $0) })
288+
// expected-warning@-1 {{tuple conversion from '(inner: Int, y: Int)' to '(outer: Int, y: Int)' mismatches labels}}
287289
}
288290
}
289291

290292
public func rdar85263844_2(_ x: [Int]) -> S4<(outer: Int, y: Int)> {
291293
S4(x.map { (inner: $0, y: $0) })
294+
// expected-warning@-1 {{tuple conversion from '(inner: Int, y: Int)' to '(outer: Int, y: Int)' mismatches labels}}
292295
}

test/Constraints/rdar85263844_swift6.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ extension S4 where T == (outer: Int, y: Int) {
2626
// generate constraints for it, it continues to compile. We should fix
2727
// tuple subtyping for Swift 6 mode to not accept label mismatches.
2828
self.init(arr.map { (inner: $0, y: $0) })
29+
// expected-warning@-1 {{tuple conversion from '(inner: Int, y: Int)' to '(outer: Int, y: Int)' mismatches labels}}
2930
}
3031
}
3132

test/Constraints/tuple.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,3 +337,30 @@ var optionalTuple3: (UInt64, Int)? = (bignum, 1) // expected-error {{cannot conv
337337
optionalTuple = (bignum, 1) // expected-error {{cannot assign value of type '(Int64, Int)' to type '(Int, Int)'}}
338338
// Optional to Optional
339339
optionalTuple = optionalTuple2 // expected-error {{cannot assign value of type '(Int64, Int)?' to type '(Int, Int)?'}}
340+
341+
func testTupleLabelMismatchFuncConversion(fn1: @escaping ((x: Int, y: Int)) -> Void,
342+
fn2: @escaping () -> (x: Int, Int)) {
343+
// Warn on mismatches
344+
let _: ((a: Int, b: Int)) -> Void = fn1 // expected-warning {{tuple conversion from '(a: Int, b: Int)' to '(x: Int, y: Int)' mismatches labels}}
345+
let _: ((x: Int, b: Int)) -> Void = fn1 // expected-warning {{tuple conversion from '(x: Int, b: Int)' to '(x: Int, y: Int)' mismatches labels}}
346+
347+
let _: () -> (y: Int, Int) = fn2 // expected-warning {{tuple conversion from '(x: Int, Int)' to '(y: Int, Int)' mismatches labels}}
348+
let _: () -> (y: Int, k: Int) = fn2 // expected-warning {{tuple conversion from '(x: Int, Int)' to '(y: Int, k: Int)' mismatches labels}}
349+
350+
// Attempting to shuffle has always been illegal here
351+
let _: () -> (y: Int, x: Int) = fn2 // expected-error {{cannot convert value of type '() -> (x: Int, Int)' to specified type '() -> (y: Int, x: Int)'}}
352+
353+
// Losing labels is okay though.
354+
let _: () -> (Int, Int) = fn2
355+
356+
// Gaining labels also okay.
357+
let _: ((x: Int, Int)) -> Void = fn1
358+
let _: () -> (x: Int, y: Int) = fn2
359+
let _: () -> (Int, y: Int) = fn2
360+
}
361+
362+
func testTupleLabelMismatchKeyPath() {
363+
// Very Cursed.
364+
let _: KeyPath<(x: Int, y: Int), Int> = \(a: Int, b: Int).x
365+
// expected-warning@-1 {{tuple conversion from '(a: Int, b: Int)' to '(x: Int, y: Int)' mismatches labels}}
366+
}

0 commit comments

Comments
 (0)