Skip to content

Commit 74a7f3d

Browse files
committed
[TypeChecker] Produce a tailored diagnostic for for-in sequence failures
`for-in` "sequence" expression is required to conform to `Sequence`.
1 parent 049d56a commit 74a7f3d

13 files changed

+76
-21
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3397,6 +3397,11 @@ ERROR(unresolved_label_corrected,none,
33973397
"use of unresolved label %0; did you mean %1?",
33983398
(Identifier, Identifier))
33993399

3400+
ERROR(foreach_sequence_does_not_conform_to_expected_protocol,none,
3401+
"for-in loop requires %0 to conform to %1"
3402+
"%select{|; did you mean to unwrap optional?}2",
3403+
(Type, Type, bool))
3404+
34003405
// Switch Stmt
34013406
ERROR(no_match_operator,none,
34023407
"no binary '~=' operator available for 'switch' statement", ())

lib/Sema/CSDiagnostics.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,7 @@ Optional<Diag<Type, Type>> GenericArgumentsMismatchFailure::getDiagnosticFor(
734734
return diag::cannot_convert_condition_value;
735735

736736
case CTP_ThrowStmt:
737+
case CTP_ForEachStmt:
737738
case CTP_Unused:
738739
case CTP_CannotFail:
739740
case CTP_YieldByReference:
@@ -1928,6 +1929,21 @@ bool ContextualFailure::diagnoseAsError() {
19281929
if (diagnoseYieldByReferenceMismatch())
19291930
return true;
19301931

1932+
if (CTP == CTP_ForEachStmt) {
1933+
if (FromType->isAnyExistentialType()) {
1934+
emitDiagnostic(anchor->getLoc(), diag::type_cannot_conform,
1935+
/*isExistentialType=*/true, FromType, ToType);
1936+
return true;
1937+
}
1938+
1939+
emitDiagnostic(
1940+
anchor->getLoc(),
1941+
diag::foreach_sequence_does_not_conform_to_expected_protocol,
1942+
FromType, ToType, bool(FromType->getOptionalObjectType()))
1943+
.highlight(anchor->getSourceRange());
1944+
return true;
1945+
}
1946+
19311947
auto contextualType = getToType();
19321948
if (auto msg = getDiagnosticFor(CTP, contextualType->isExistentialType())) {
19331949
diagnostic = *msg;
@@ -1965,6 +1981,7 @@ getContextualNilDiagnostic(ContextualTypePurpose CTP) {
19651981
return diag::cannot_convert_to_return_type_nil;
19661982

19671983
case CTP_ThrowStmt:
1984+
case CTP_ForEachStmt:
19681985
case CTP_YieldByReference:
19691986
return None;
19701987

@@ -2739,6 +2756,7 @@ ContextualFailure::getDiagnosticFor(ContextualTypePurpose context,
27392756
return diag::cannot_convert_condition_value;
27402757

27412758
case CTP_ThrowStmt:
2759+
case CTP_ForEachStmt:
27422760
case CTP_Unused:
27432761
case CTP_CannotFail:
27442762
case CTP_YieldByReference:

lib/Sema/CSSimplify.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3357,6 +3357,25 @@ ConstraintSystem::matchTypes(Type type1, Type type2, ConstraintKind kind,
33573357
formUnsolvedResult);
33583358
}
33593359
}
3360+
3361+
// If the left-hand side of a 'sequence element' constraint
3362+
// is a dependent member type without any type variables it
3363+
// means that conformance check has been "fixed".
3364+
// Let's record other side of the conversion as a "hole"
3365+
// to give the solver a chance to continue and avoid
3366+
// producing diagnostics for both missing conformance and
3367+
// invalid element type.
3368+
if (shouldAttemptFixes()) {
3369+
if (auto last = locator.last()) {
3370+
if (last->is<LocatorPathElt::SequenceElementType>() &&
3371+
desugar1->is<DependentMemberType>() &&
3372+
!desugar1->hasTypeVariable()) {
3373+
recordHole(typeVar2);
3374+
return getTypeMatchSuccess();
3375+
}
3376+
}
3377+
}
3378+
33603379
return formUnsolvedResult();
33613380
}
33623381

lib/Sema/TypeCheckConstraints.cpp

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2908,6 +2908,8 @@ bool TypeChecker::typeCheckForEachBinding(DeclContext *dc, ForEachStmt *stmt) {
29082908
bool builtConstraints(ConstraintSystem &cs, Expr *expr) override {
29092909
// Save the locator we're using for the expression.
29102910
Locator = cs.getConstraintLocator(expr);
2911+
auto *contextualLocator =
2912+
cs.getConstraintLocator(expr, LocatorPathElt::ContextualType());
29112913

29122914
// The expression type must conform to the Sequence.
29132915
auto &tc = cs.getTypeChecker();
@@ -2924,11 +2926,16 @@ bool TypeChecker::typeCheckForEachBinding(DeclContext *dc, ForEachStmt *stmt) {
29242926
cs.addConstraint(ConstraintKind::Conversion, cs.getType(expr),
29252927
SequenceType, Locator);
29262928
cs.addConstraint(ConstraintKind::ConformsTo, SequenceType,
2927-
sequenceProto->getDeclaredType(), Locator);
2929+
sequenceProto->getDeclaredType(), contextualLocator);
29282930

2929-
auto elementLocator =
2930-
cs.getConstraintLocator(Locator,
2931-
ConstraintLocator::SequenceElementType);
2931+
// Since we are using "contextual type" here, it has to be recorded
2932+
// in the constraint system for diagnostics to have access to "purpose".
2933+
cs.setContextualType(
2934+
expr, TypeLoc::withoutLoc(sequenceProto->getDeclaredType()),
2935+
CTP_ForEachStmt);
2936+
2937+
auto elementLocator = cs.getConstraintLocator(
2938+
contextualLocator, ConstraintLocator::SequenceElementType);
29322939

29332940
// Collect constraints from the element pattern.
29342941
auto pattern = Stmt->getPattern();

lib/Sema/TypeChecker.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,8 @@ enum ContextualTypePurpose {
221221
///< result type.
222222
CTP_Condition, ///< Condition expression of various statements e.g.
223223
///< `if`, `for`, `while` etc.
224+
CTP_ForEachStmt, ///< "expression/sequence" associated with 'for-in' loop
225+
///< is expected to conform to 'Sequence' protocol.
224226

225227
CTP_CannotFail, ///< Conversion can never fail. abort() if it does.
226228
};

test/Constraints/diagnostics.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ struct A : P2 {
8686
func wonka() {}
8787
}
8888
let a = A()
89-
for j in i.wibble(a, a) { // expected-error {{type 'A' does not conform to protocol 'Sequence'}} expected-error{{variable 'j' is not bound by any pattern}}
89+
for j in i.wibble(a, a) { // expected-error {{for-in loop requires 'A' to conform to 'Sequence'}} expected-error{{variable 'j' is not bound by any pattern}}
9090
}
9191

9292
// Generic as part of function/tuple types

test/Constraints/generic_protocol_witness.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,5 +59,5 @@ func usesAGenericMethod<U : NeedsAGenericMethod>(_ x: U) {
5959
struct L<T>: Sequence {} // expected-error {{type 'L<T>' does not conform to protocol 'Sequence'}}
6060

6161
func z(_ x: L<Int>) {
62-
for xx in x {} // expected-error {{variable 'xx' is not bound by any pattern}}
62+
for xx in x {} // expected-warning {{immutable value 'xx' was never used; consider replacing with '_' or removing it}}
6363
}

test/Constraints/members.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ struct S22490787 {
316316
func f22490787() {
317317
var path: S22490787 = S22490787()
318318

319-
for p in path { // expected-error {{type 'S22490787' does not conform to protocol 'Sequence'}} expected-error{{variable 'p' is not bound by any pattern}}
319+
for p in path { // expected-error {{for-in loop requires 'S22490787' to conform to 'Sequence'}} expected-error{{variable 'p' is not bound by any pattern}}
320320
}
321321
}
322322

test/Generics/deduction.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ class DeducePropertyParams {
311311
// SR-69
312312
struct A {}
313313
func foo() {
314-
for i in min(1,2) { // expected-error{{type 'Int' does not conform to protocol 'Sequence'}} expected-error {{variable 'i' is not bound by any pattern}}
314+
for i in min(1,2) { // expected-error{{for-in loop requires 'Int' to conform to 'Sequence'}} expected-error {{variable 'i' is not bound by any pattern}}
315315
}
316316
let j = min(Int(3), Float(2.5)) // expected-error{{cannot convert value of type 'Float' to expected argument type 'Int'}}
317317
let k = min(A(), A()) // expected-error{{global function 'min' requires that 'A' conform to 'Comparable'}}

test/Sema/editor_placeholders.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,6 @@ f(<#T#> + 1) // expected-error{{editor placeholder in source file}}
2626
f(<#T##Int#>) // expected-error{{editor placeholder in source file}}
2727
f(<#T##String#>) // expected-error{{editor placeholder in source file}} expected-error{{cannot convert value of type 'String' to expected argument type 'Int'}}
2828

29-
for x in <#T#> { // expected-error{{editor placeholder in source file}} expected-error{{type '()' does not conform to protocol 'Sequence'}}
29+
for x in <#T#> { // expected-error{{editor placeholder in source file}} expected-error{{for-in loop requires '()' to conform to 'Sequence'}}
3030
// expected-error@-1{{variable 'x' is not bound by any pattern}}
3131
}

test/SourceKit/Sema/placeholders.swift.placeholders.response

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,13 @@
2727
key.column: 19,
2828
key.filepath: placeholders.swift,
2929
key.severity: source.diagnostic.severity.error,
30-
key.description: "type '()' does not conform to protocol 'Sequence'",
31-
key.diagnostic_stage: source.diagnostic.stage.swift.sema
30+
key.description: "for-in loop requires '()' to conform to 'Sequence'",
31+
key.diagnostic_stage: source.diagnostic.stage.swift.sema,
32+
key.ranges: [
33+
{
34+
key.offset: 75,
35+
key.length: 9
36+
}
37+
]
3238
}
3339
]

test/attr/attr_dynamic_member_lookup.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -600,8 +600,8 @@ func keypath_with_subscripts(_ arr: SubscriptLens<[Int]>,
600600

601601
func keypath_with_incorrect_return_type(_ arr: Lens<Array<Int>>) {
602602
for idx in 0..<arr.count {
603-
// expected-error@-1 {{operator function '..<' requires that 'Lens<Int>' conform to 'Strideable'}}
604-
// expected-error@-2 {{operator function '..<' requires that 'Lens<Int>.Stride' conform to 'SignedInteger'}}
603+
// expected-error@-1 {{protocol 'Sequence' requires that 'Lens<Int>' conform to 'Strideable'}}
604+
// expected-error@-2 {{protocol 'Sequence' requires that 'Lens<Int>.Stride' conform to 'SignedInteger'}}
605605
// expected-error@-3 {{cannot convert value of type 'Int' to expected argument type 'Lens<Int>'}}
606606
// expected-error@-4 {{referencing operator function '..<' on 'Comparable' requires that 'Lens<Int>' conform to 'Comparable'}}
607607
// expected-error@-5 {{variable 'idx' is not bound by any pattern}}

test/stmt/foreach.swift

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ struct BadContainer1 {
55
}
66

77
func bad_containers_1(bc: BadContainer1) {
8-
for e in bc { } // expected-error{{type 'BadContainer1' does not conform to protocol 'Sequence'}}
8+
for e in bc { } // expected-error{{for-in loop requires 'BadContainer1' to conform to 'Sequence'}}
99
// expected-error@-1{{variable 'e' is not bound by any pattern}}
1010
}
1111

@@ -15,7 +15,7 @@ struct BadContainer2 : Sequence { // expected-error{{type 'BadContainer2' does n
1515

1616
func bad_containers_2(bc: BadContainer2) {
1717
for e in bc { }
18-
// expected-error@-1{{variable 'e' is not bound by any pattern}}
18+
// expected-warning@-1 {{immutable value 'e' was never used; consider replacing with '_' or removing it}}
1919
}
2020

2121
struct BadContainer3 : Sequence { // expected-error{{type 'BadContainer3' does not conform to protocol 'Sequence'}}
@@ -24,7 +24,7 @@ struct BadContainer3 : Sequence { // expected-error{{type 'BadContainer3' does n
2424

2525
func bad_containers_3(bc: BadContainer3) {
2626
for e in bc { }
27-
// expected-error@-1{{variable 'e' is not bound by any pattern}}
27+
// expected-warning@-1 {{immutable value 'e' was never used; consider replacing with '_' or removing it}}
2828
}
2929

3030
struct BadIterator1 {}
@@ -36,7 +36,7 @@ struct BadContainer4 : Sequence { // expected-error{{type 'BadContainer4' does n
3636

3737
func bad_containers_4(bc: BadContainer4) {
3838
for e in bc { }
39-
// expected-error@-1{{variable 'e' is not bound by any pattern}}
39+
// expected-warning@-1 {{immutable value 'e' was never used; consider replacing with '_' or removing it}}
4040
}
4141

4242
// Pattern type-checking
@@ -176,10 +176,8 @@ func testMatchingPatterns() {
176176
// <rdar://problem/21662365> QoI: diagnostic for for-each over an optional sequence isn't great
177177
func testOptionalSequence() {
178178
let array : [Int]?
179-
for x in array { // expected-error {{value of optional type '[Int]?' must be unwrapped}}
180-
// expected-note@-1{{coalesce}}
181-
// expected-note@-2{{force-unwrap}}
182-
// expected-error@-3{{variable 'x' is not bound by any pattern}}
179+
for x in array { // expected-error {{for-in loop requires '[Int]?' to conform to 'Sequence'; did you mean to unwrap optional?}}
180+
// expected-error@-1{{variable 'x' is not bound by any pattern}}
183181
}
184182
}
185183

0 commit comments

Comments
 (0)