Skip to content

Commit eefc5df

Browse files
committed
Fix the major case of <rdar://problem/23641896> QoI: Strings in Swift cannot be indexed directly with integer offsets
This adds special tuned diagnostics to handle the case when someone attempts to index a string with an integer value, which is pretty common to do for people new to Swift.o That said, this only handles one special case, if there are other cases that are common, I'd love to hear about them. Subscripts are already handled with a different approach, I'm not sure which is better, but I added a testcase for both of them.
1 parent f436764 commit eefc5df

File tree

3 files changed

+50
-1
lines changed

3 files changed

+50
-1
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,12 @@ ERROR(unbound_generic_parameter,sema_tcc,none,
661661
ERROR(cannot_bind_generic_parameter_to_type,sema_tcc,none,
662662
"cannot bind generic parameter to type %0",
663663
(Type))
664+
ERROR(string_index_not_integer,sema_tcc,none,
665+
"String may not be indexed with %0, it has variable size elements",
666+
(Type))
667+
NOTE(string_index_not_integer_note,sema_tcc,none,
668+
"consider using an existing high level algorithm, "
669+
"str.startIndex.advancedBy(n), or a projection like str.utf8", ())
664670

665671
ERROR(invalid_c_function_pointer_conversion_expr,sema_tcc,none,
666672
"a C function pointer can only be formed from a reference to a 'func' or "

lib/Sema/CSDiag.cpp

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3038,6 +3038,28 @@ bool FailureDiagnosis::diagnoseCalleeResultContextualConversionError() {
30383038
}
30393039
}
30403040

3041+
3042+
/// Return true if the conversion from fromType to toType is an invalid string
3043+
/// index operation.
3044+
static bool isIntegerToStringIndexConversion(Type fromType, Type toType,
3045+
ConstraintSystem *CS) {
3046+
auto integerType =
3047+
CS->TC.getProtocol(SourceLoc(),
3048+
KnownProtocolKind::IntegerLiteralConvertible);
3049+
if (!integerType) return false;
3050+
3051+
// If the from type is an integer type, and the to type is
3052+
// String.CharacterView.Index, then we found one.
3053+
if (CS->TC.conformsToProtocol(fromType, integerType, CS->DC,
3054+
ConformanceCheckFlags::InExpression)) {
3055+
if (toType->getCanonicalType().getString() == "String.CharacterView.Index")
3056+
return true;
3057+
}
3058+
3059+
return false;
3060+
}
3061+
3062+
30413063
bool FailureDiagnosis::diagnoseContextualConversionError() {
30423064
// If the constraint system has a contextual type, then we can test to see if
30433065
// this is the problem that prevents us from solving the system.
@@ -3193,6 +3215,18 @@ bool FailureDiagnosis::diagnoseContextualConversionError() {
31933215
return true;
31943216
}
31953217

3218+
exprType = exprType->getRValueType();
3219+
3220+
// Special case a some common common conversions involving Swift.String
3221+
// indexes, catching cases where people attempt to index them with an integer.
3222+
if (isIntegerToStringIndexConversion(exprType, contextualType, CS)) {
3223+
diagnose(expr->getLoc(), diag::string_index_not_integer,
3224+
exprType->getRValueType())
3225+
.highlight(expr->getSourceRange());
3226+
diagnose(expr->getLoc(), diag::string_index_not_integer_note);
3227+
return true;
3228+
}
3229+
31963230
// When complaining about conversion to a protocol type, complain about
31973231
// conformance instead of "conversion".
31983232
if (contextualType->is<ProtocolType>() ||
@@ -3221,7 +3255,7 @@ bool FailureDiagnosis::diagnoseContextualConversionError() {
32213255
diagID = diag::noescape_functiontype_mismatch;
32223256
}
32233257

3224-
diagnose(expr->getLoc(), diagID, exprType->getRValueType(), contextualType)
3258+
diagnose(expr->getLoc(), diagID, exprType, contextualType)
32253259
.highlight(expr->getSourceRange());
32263260
return true;
32273261
}

test/Constraints/diagnostics.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -675,4 +675,13 @@ func r23272739(contentType: String) {
675675
return actualAcceptableContentTypes.contains(contentType) // expected-error {{unexpected non-void return value in void function}}
676676
}
677677

678+
// <rdar://problem/23641896> QoI: Strings in Swift cannot be indexed directly with integer offsets
679+
func r23641896() {
680+
var g = "Hello World"
681+
g.replaceRange(0...2, with: "ce") // expected-error {{String may not be indexed with 'Int', it has variable size elements}}
682+
// expected-note @-1 {{consider using an existing high level algorithm, str.startIndex.advancedBy(n), or a projection like str.utf8}}
683+
684+
_ = g[12] // expected-error {{'subscript' is unavailable: cannot subscript String with an Int, see the documentation comment for discussion}}
685+
686+
}
678687

0 commit comments

Comments
 (0)