Skip to content

Commit d5b232c

Browse files
authored
Merge pull request swiftlang#27834 from xedin/for-in-diag
[TypeChecker] Produce a tailored diagnostic for `for-in` sequence fai…
2 parents fb6ce64 + 74a7f3d commit d5b232c

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:
@@ -1936,6 +1937,21 @@ bool ContextualFailure::diagnoseAsError() {
19361937
if (diagnoseYieldByReferenceMismatch())
19371938
return true;
19381939

1940+
if (CTP == CTP_ForEachStmt) {
1941+
if (FromType->isAnyExistentialType()) {
1942+
emitDiagnostic(anchor->getLoc(), diag::type_cannot_conform,
1943+
/*isExistentialType=*/true, FromType, ToType);
1944+
return true;
1945+
}
1946+
1947+
emitDiagnostic(
1948+
anchor->getLoc(),
1949+
diag::foreach_sequence_does_not_conform_to_expected_protocol,
1950+
FromType, ToType, bool(FromType->getOptionalObjectType()))
1951+
.highlight(anchor->getSourceRange());
1952+
return true;
1953+
}
1954+
19391955
auto contextualType = getToType();
19401956
if (auto msg = getDiagnosticFor(CTP, contextualType->isExistentialType())) {
19411957
diagnostic = *msg;
@@ -1973,6 +1989,7 @@ getContextualNilDiagnostic(ContextualTypePurpose CTP) {
19731989
return diag::cannot_convert_to_return_type_nil;
19741990

19751991
case CTP_ThrowStmt:
1992+
case CTP_ForEachStmt:
19761993
case CTP_YieldByReference:
19771994
return None;
19781995

@@ -2747,6 +2764,7 @@ ContextualFailure::getDiagnosticFor(ContextualTypePurpose context,
27472764
return diag::cannot_convert_condition_value;
27482765

27492766
case CTP_ThrowStmt:
2767+
case CTP_ForEachStmt:
27502768
case CTP_Unused:
27512769
case CTP_CannotFail:
27522770
case CTP_YieldByReference:

lib/Sema/CSSimplify.cpp

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

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
}

0 commit comments

Comments
 (0)