Skip to content

Commit 8d14ee2

Browse files
authored
Merge pull request #9314 from bitjammer/rdar-31665649-migrator-string-substring-swift-4.0-branch
[Migrator] Suggest String <-> Substring conversions
2 parents e530413 + 39b137f commit 8d14ee2

12 files changed

+450
-91
lines changed

include/swift/Basic/LangOptions.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,12 @@ namespace swift {
231231
/// Enable keypaths.
232232
bool EnableExperimentalKeyPaths = true;
233233

234+
/// When a conversion from String to Substring fails, emit a fix-it to append
235+
/// the void subscript '[]'.
236+
/// FIXME: Remove this flag when void subscripts are implemented.
237+
/// This is used to guard preemptive testing for the fix-it.
238+
bool FixStringToSubstringConversions = false;
239+
234240
/// Sets the target we are building for and updates platform conditions
235241
/// to match.
236242
///

include/swift/Option/FrontendOptions.td

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,4 +408,9 @@ HelpText<"Diagnostics will be used in editor">;
408408
def validate_tbd_against_ir: Flag<["-"], "validate-tbd-against-ir">,
409409
HelpText<"Compare the symbols in the IR against the TBD file that would be generated.">;
410410

411+
// FIXME: Remove this flag when void subscripts are implemented.
412+
// This is used to guard preemptive testing for the fix-it.
413+
def fix_string_substring_conversion: Flag<["-"], "fix-string-substring-conversion">,
414+
HelpText<"Emit a fix-it to append '[]' to String expressions when converting to Substring.">;
415+
411416
} // end let Flags = [FrontendOption, NoDriverOption, HelpHidden]

lib/Frontend/CompilerInvocation.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -884,6 +884,12 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
884884
const FrontendOptions &FrontendOpts) {
885885
using namespace options;
886886

887+
/// FIXME: Remove this flag when void subscripts are implemented.
888+
/// This is used to guard preemptive testing for the fix-it.
889+
if (Args.hasArg(OPT_fix_string_substring_conversion)) {
890+
Opts.FixStringToSubstringConversions = true;
891+
}
892+
887893
if (auto A = Args.getLastArg(OPT_swift_version)) {
888894
auto vers = version::Version::parseVersionString(
889895
A->getValue(), SourceLoc(), &Diags);

lib/Sema/CSDiag.cpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3830,6 +3830,45 @@ static bool tryRawRepresentableFixIts(InFlightDiagnostic &diag,
38303830
return false;
38313831
}
38323832

3833+
/// Try to add a fix-it when converting between a collection and its slice type,
3834+
/// such as String <-> Substring or (eventually) Array <-> ArraySlice
3835+
static bool trySequenceSubsequenceConversionFixIts(InFlightDiagnostic &diag,
3836+
ConstraintSystem *CS,
3837+
Type fromType,
3838+
Type toType,
3839+
Expr *expr) {
3840+
if (CS->TC.Context.getStdlibModule() == nullptr) {
3841+
return false;
3842+
}
3843+
auto String = CS->TC.getStringType(CS->DC);
3844+
auto Substring = CS->TC.getSubstringType(CS->DC);
3845+
3846+
/// FIXME: Remove this flag when void subscripts are implemented.
3847+
/// Make this unconditional and remove the if statement.
3848+
if (CS->TC.getLangOpts().FixStringToSubstringConversions) {
3849+
// String -> Substring conversion
3850+
// Add '[]' void subscript call to turn the whole String into a Substring
3851+
if (fromType->getCanonicalType() == String->getCanonicalType()) {
3852+
if (toType->getCanonicalType() == Substring->getCanonicalType()) {
3853+
diag.fixItInsertAfter(expr->getEndLoc (), "[]");
3854+
return true;
3855+
}
3856+
}
3857+
}
3858+
3859+
// Substring -> String conversion
3860+
// Wrap in String.init
3861+
if (fromType->getCanonicalType() == Substring->getCanonicalType()) {
3862+
if (toType->getCanonicalType() == String->getCanonicalType()) {
3863+
diag.fixItInsert(expr->getLoc(), "String(");
3864+
diag.fixItInsertAfter(expr->getSourceRange().End, ")");
3865+
return true;
3866+
}
3867+
}
3868+
3869+
return false;
3870+
}
3871+
38333872
/// Attempts to add fix-its for these two mistakes:
38343873
///
38353874
/// - Passing an integer with the right type but which is getting wrapped with a
@@ -4268,6 +4307,13 @@ bool FailureDiagnosis::diagnoseContextualConversionError() {
42684307
exprType, contextualType);
42694308
diag.highlight(expr->getSourceRange());
42704309

4310+
// Try to convert between a sequence and its subsequence, notably
4311+
// String <-> Substring.
4312+
if (trySequenceSubsequenceConversionFixIts(diag, CS, exprType, contextualType,
4313+
expr)) {
4314+
return true;
4315+
}
4316+
42714317
// Attempt to add a fixit for the error.
42724318
switch (CS->getContextualTypePurpose()) {
42734319
case CTP_CallArgument:

lib/Sema/TypeCheckType.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ static Type getStdlibType(TypeChecker &TC, Type &cached, DeclContext *dc,
100100
Type TypeChecker::getStringType(DeclContext *dc) {
101101
return ::getStdlibType(*this, StringType, dc, "String");
102102
}
103+
Type TypeChecker::getSubstringType(DeclContext *dc) {
104+
return ::getStdlibType(*this, SubstringType, dc, "Substring");
105+
}
103106
Type TypeChecker::getIntType(DeclContext *dc) {
104107
return ::getStdlibType(*this, IntType, dc, "Int");
105108
}

lib/Sema/TypeChecker.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -790,6 +790,7 @@ class TypeChecker final : public LazyResolver {
790790
Type ImageLiteralType;
791791
Type FileReferenceLiteralType;
792792
Type StringType;
793+
Type SubstringType;
793794
Type IntType;
794795
Type Int8Type;
795796
Type UInt8Type;
@@ -881,6 +882,7 @@ class TypeChecker final : public LazyResolver {
881882
Type getOptionalType(SourceLoc loc, Type elementType);
882883
Type getImplicitlyUnwrappedOptionalType(SourceLoc loc, Type elementType);
883884
Type getStringType(DeclContext *dc);
885+
Type getSubstringType(DeclContext *dc);
884886
Type getIntType(DeclContext *dc);
885887
Type getInt8Type(DeclContext *dc);
886888
Type getUInt8Type(DeclContext *dc);
Lines changed: 56 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,80 @@
1-
let s = "foo"
1+
// RUN: %target-swift-frontend -typecheck -verify fix-string-substring-conversion %s
2+
3+
let s = "Hello"
24
let ss = s[s.startIndex..<s.endIndex]
35

6+
// CTP_Initialization
7+
do {
8+
let s1: Substring = { return s }()
9+
_ = s1
10+
}
11+
412
// CTP_ReturnStmt
5-
func returnStringButWantedSubstring() -> Substring {
6-
return s
13+
do {
14+
func returnsASubstring() -> Substring {
15+
return s
16+
}
717
}
818

19+
// CTP_ThrowStmt
20+
// Doesn't really make sense for this fix-it - see case in diagnoseContextualConversionError:
21+
// The conversion destination of throw is always ErrorType (at the moment)
22+
// if this ever expands, this should be a specific form like () is for
23+
// return.
24+
25+
// CTP_EnumCaseRawValue
26+
// Substrings can't be raw values because they aren't literals.
27+
928
// CTP_DefaultParameter
10-
// Never do this.
11-
func defaultArgIsSubstring(ss: Substring = s) {}
29+
do {
30+
func foo(x: Substring = s) {}
31+
}
1232

1333
// CTP_CalleeResult
14-
func returnString() -> String {
15-
return s
34+
do {
35+
func getString() -> String { return s }
36+
let gottenSubstring: Substring = getString()
37+
_ = gottenSubstring
1638
}
17-
let s2: Substring = returnString()
1839

1940
// CTP_CallArgument
20-
func takeString(s: String) {}
21-
func takeSubstring(ss: Substring) {}
22-
23-
takeSubstring(ss)
24-
takeSubstring(s)
41+
do {
42+
func takesASubstring(_ ss: Substring) {}
43+
takesASubstring(s)
44+
}
2545

2646
// CTP_ClosureResult
27-
[1,2,3].map { (x: Int) -> Substring in
28-
return s
47+
do {
48+
[s].map { (x: String) -> Substring in x }
2949
}
3050

3151
// CTP_ArrayElement
32-
let a: [Substring] = [ s ]
52+
do {
53+
let a: [Substring] = [ s ]
54+
_ = a
55+
}
3356

3457
// CTP_DictionaryKey
58+
do {
59+
let d: [ Substring : Substring ] = [ s : ss ]
60+
_ = d
61+
}
62+
3563
// CTP_DictionaryValue
36-
let d: [Substring : Substring] = [
37-
"CTP_DictionaryValue" : s,
38-
s : "CTP_DictionaryKey",
39-
]
64+
do {
65+
let d: [ Substring : Substring ] = [ ss : s ]
66+
_ = d
67+
}
4068

4169
// CTP_CoerceOperand
42-
let s3: Substring = s as Substring
70+
do {
71+
let s1: Substring = s as Substring
72+
_ = s1
73+
}
4374

4475
// CTP_AssignSource
45-
let s4: Substring = s
76+
do {
77+
let s1: Substring = s
78+
_ = s1
79+
}
4680

Lines changed: 55 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,78 @@
1-
let s = "foo"
1+
let s = "Hello"
22
let ss = s[s.startIndex..<s.endIndex]
33

4-
// CTP_ReturnStmt
4+
// CTP_Initialization
5+
do {
6+
let s1: String = { return ss }()
7+
_ = s1
8+
}
59

6-
func returnSubstringButWantedString() -> String {
7-
return ss
10+
// CTP_ReturnStmt
11+
do {
12+
func returnsAString() -> String {
13+
return ss
14+
}
815
}
916

10-
// CTP_DefaultParameter
17+
// CTP_ThrowStmt
18+
// Doesn't really make sense for this fix-it - see case in diagnoseContextualConversionError:
19+
// The conversion destination of throw is always ErrorType (at the moment)
20+
// if this ever expands, this should be a specific form like () is for
21+
// return.
1122

12-
// Never do this.
13-
func defaultArgIsString(s: String = ss) {}
23+
// CTP_EnumCaseRawValue
24+
// Substrings can't be raw values because they aren't literals.
25+
26+
// CTP_DefaultParameter
27+
do {
28+
func foo(x: String = ss) {}
29+
}
1430

1531
// CTP_CalleeResult
16-
func returnSubstring() -> Substring {
17-
return ss
32+
do {
33+
func getSubstring() -> Substring { return ss }
34+
let gottenString : String = getSubstring()
35+
_ = gottenString
1836
}
19-
let s2: String = returnSubstring()
2037

2138
// CTP_CallArgument
22-
func takeString(_ s: String) {}
23-
24-
takeString(s)
25-
takeString(ss)
39+
do {
40+
func takesAString(_ s: String) {}
41+
takesAString(ss)
42+
}
2643

2744
// CTP_ClosureResult
28-
[1,2,3].map { (x: Int) -> String in
29-
return ss
45+
do {
46+
[ss].map { (x: Substring) -> String in x }
3047
}
3148

3249
// CTP_ArrayElement
33-
let a: [String] = [ ss ]
50+
do {
51+
let a: [String] = [ ss ]
52+
_ = a
53+
}
3454

3555
// CTP_DictionaryKey
56+
do {
57+
let d: [ String : String ] = [ ss : s ]
58+
_ = d
59+
}
60+
3661
// CTP_DictionaryValue
37-
let d: [String : String] = [
38-
"CTP_DictionaryValue" : ss,
39-
ss : "CTP_DictionaryKey",
40-
]
62+
do {
63+
let d: [ String : String ] = [ s : ss ]
64+
_ = d
65+
}
4166

4267
// CTP_CoerceOperand
43-
let s3: String = ss as String
68+
do {
69+
let s1: String = ss as String
70+
_ = s1
71+
}
4472

4573
// CTP_AssignSource
46-
let s4: String = ss
74+
do {
75+
let s1: String = ss
76+
_ = s1
77+
}
78+

0 commit comments

Comments
 (0)