Skip to content

[Migrator] Suggest String <-> Substring conversions #9307

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 5, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions include/swift/Basic/LangOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,12 @@ namespace swift {
/// Enable keypaths.
bool EnableExperimentalKeyPaths = false;

/// When a conversion from String to Substring fails, emit a fix-it to append
/// the void subscript '[]'.
/// FIXME: Remove this flag when void subscripts are implemented.
/// This is used to guard preemptive testing for the fix-it.
bool FixStringToSubstringConversions = false;

/// Sets the target we are building for and updates platform conditions
/// to match.
///
Expand Down
5 changes: 5 additions & 0 deletions include/swift/Option/FrontendOptions.td
Original file line number Diff line number Diff line change
Expand Up @@ -408,4 +408,9 @@ HelpText<"Diagnostics will be used in editor">;
def validate_tbd_against_ir: Flag<["-"], "validate-tbd-against-ir">,
HelpText<"Compare the symbols in the IR against the TBD file that would be generated.">;

// FIXME: Remove this flag when void subscripts are implemented.
// This is used to guard preemptive testing for the fix-it.
def fix_string_substring_conversion: Flag<["-"], "fix-string-substring-conversion">,
HelpText<"Emit a fix-it to append '[]' to String expressions when converting to Substring.">;

} // end let Flags = [FrontendOption, NoDriverOption, HelpHidden]
6 changes: 6 additions & 0 deletions lib/Frontend/CompilerInvocation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -884,6 +884,12 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
const FrontendOptions &FrontendOpts) {
using namespace options;

/// FIXME: Remove this flag when void subscripts are implemented.
/// This is used to guard preemptive testing for the fix-it.
if (Args.hasArg(OPT_fix_string_substring_conversion)) {
Opts.FixStringToSubstringConversions = true;
}

if (auto A = Args.getLastArg(OPT_swift_version)) {
auto vers = version::Version::parseVersionString(
A->getValue(), SourceLoc(), &Diags);
Expand Down
46 changes: 46 additions & 0 deletions lib/Sema/CSDiag.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3830,6 +3830,45 @@ static bool tryRawRepresentableFixIts(InFlightDiagnostic &diag,
return false;
}

/// Try to add a fix-it when converting between a collection and its slice type,
/// such as String <-> Substring or (eventually) Array <-> ArraySlice
static bool trySequenceSubsequenceConversionFixIts(InFlightDiagnostic &diag,
ConstraintSystem *CS,
Type fromType,
Type toType,
Expr *expr) {
if (CS->TC.Context.getStdlibModule() == nullptr) {
return false;
}
auto String = CS->TC.getStringType(CS->DC);
auto Substring = CS->TC.getSubstringType(CS->DC);

/// FIXME: Remove this flag when void subscripts are implemented.
/// Make this unconditional and remove the if statement.
if (CS->TC.getLangOpts().FixStringToSubstringConversions) {
// String -> Substring conversion
// Add '[]' void subscript call to turn the whole String into a Substring
if (fromType->getCanonicalType() == String->getCanonicalType()) {
if (toType->getCanonicalType() == Substring->getCanonicalType()) {
diag.fixItInsertAfter(expr->getEndLoc (), "[]");
return true;
}
}
}

// Substring -> String conversion
// Wrap in String.init
if (fromType->getCanonicalType() == Substring->getCanonicalType()) {
if (toType->getCanonicalType() == String->getCanonicalType()) {
diag.fixItInsert(expr->getLoc(), "String(");
diag.fixItInsertAfter(expr->getSourceRange().End, ")");
return true;
}
}

return false;
}

/// Attempts to add fix-its for these two mistakes:
///
/// - Passing an integer with the right type but which is getting wrapped with a
Expand Down Expand Up @@ -4268,6 +4307,13 @@ bool FailureDiagnosis::diagnoseContextualConversionError() {
exprType, contextualType);
diag.highlight(expr->getSourceRange());

// Try to convert between a sequence and its subsequence, notably
// String <-> Substring.
if (trySequenceSubsequenceConversionFixIts(diag, CS, exprType, contextualType,
expr)) {
return true;
}

// Attempt to add a fixit for the error.
switch (CS->getContextualTypePurpose()) {
case CTP_CallArgument:
Expand Down
3 changes: 3 additions & 0 deletions lib/Sema/TypeCheckType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ static Type getStdlibType(TypeChecker &TC, Type &cached, DeclContext *dc,
Type TypeChecker::getStringType(DeclContext *dc) {
return ::getStdlibType(*this, StringType, dc, "String");
}
Type TypeChecker::getSubstringType(DeclContext *dc) {
return ::getStdlibType(*this, SubstringType, dc, "Substring");
}
Type TypeChecker::getIntType(DeclContext *dc) {
return ::getStdlibType(*this, IntType, dc, "Int");
}
Expand Down
2 changes: 2 additions & 0 deletions lib/Sema/TypeChecker.h
Original file line number Diff line number Diff line change
Expand Up @@ -792,6 +792,7 @@ class TypeChecker final : public LazyResolver {
Type ImageLiteralType;
Type FileReferenceLiteralType;
Type StringType;
Type SubstringType;
Type IntType;
Type Int8Type;
Type UInt8Type;
Expand Down Expand Up @@ -883,6 +884,7 @@ class TypeChecker final : public LazyResolver {
Type getOptionalType(SourceLoc loc, Type elementType);
Type getImplicitlyUnwrappedOptionalType(SourceLoc loc, Type elementType);
Type getStringType(DeclContext *dc);
Type getSubstringType(DeclContext *dc);
Type getIntType(DeclContext *dc);
Type getInt8Type(DeclContext *dc);
Type getUInt8Type(DeclContext *dc);
Expand Down
78 changes: 56 additions & 22 deletions test/Migrator/Inputs/string_to_substring_conversion.swift
Original file line number Diff line number Diff line change
@@ -1,46 +1,80 @@
let s = "foo"
// RUN: %target-swift-frontend -typecheck -verify fix-string-substring-conversion %s

let s = "Hello"
let ss = s[s.startIndex..<s.endIndex]

// CTP_Initialization
do {
let s1: Substring = { return s }()
_ = s1
}

// CTP_ReturnStmt
func returnStringButWantedSubstring() -> Substring {
return s
do {
func returnsASubstring() -> Substring {
return s
}
}

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

// CTP_EnumCaseRawValue
// Substrings can't be raw values because they aren't literals.

// CTP_DefaultParameter
// Never do this.
func defaultArgIsSubstring(ss: Substring = s) {}
do {
func foo(x: Substring = s) {}
}

// CTP_CalleeResult
func returnString() -> String {
return s
do {
func getString() -> String { return s }
let gottenSubstring: Substring = getString()
_ = gottenSubstring
}
let s2: Substring = returnString()

// CTP_CallArgument
func takeString(s: String) {}
func takeSubstring(ss: Substring) {}

takeSubstring(ss)
takeSubstring(s)
do {
func takesASubstring(_ ss: Substring) {}
takesASubstring(s)
}

// CTP_ClosureResult
[1,2,3].map { (x: Int) -> Substring in
return s
do {
[s].map { (x: String) -> Substring in x }
}

// CTP_ArrayElement
let a: [Substring] = [ s ]
do {
let a: [Substring] = [ s ]
_ = a
}

// CTP_DictionaryKey
do {
let d: [ Substring : Substring ] = [ s : ss ]
_ = d
}

// CTP_DictionaryValue
let d: [Substring : Substring] = [
"CTP_DictionaryValue" : s,
s : "CTP_DictionaryKey",
]
do {
let d: [ Substring : Substring ] = [ ss : s ]
_ = d
}

// CTP_CoerceOperand
let s3: Substring = s as Substring
do {
let s1: Substring = s as Substring
_ = s1
}

// CTP_AssignSource
let s4: Substring = s
do {
let s1: Substring = s
_ = s1
}

78 changes: 55 additions & 23 deletions test/Migrator/Inputs/substring_to_string_conversion.swift
Original file line number Diff line number Diff line change
@@ -1,46 +1,78 @@
let s = "foo"
let s = "Hello"
let ss = s[s.startIndex..<s.endIndex]

// CTP_ReturnStmt
// CTP_Initialization
do {
let s1: String = { return ss }()
_ = s1
}

func returnSubstringButWantedString() -> String {
return ss
// CTP_ReturnStmt
do {
func returnsAString() -> String {
return ss
}
}

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

// Never do this.
func defaultArgIsString(s: String = ss) {}
// CTP_EnumCaseRawValue
// Substrings can't be raw values because they aren't literals.

// CTP_DefaultParameter
do {
func foo(x: String = ss) {}
}

// CTP_CalleeResult
func returnSubstring() -> Substring {
return ss
do {
func getSubstring() -> Substring { return ss }
let gottenString : String = getSubstring()
_ = gottenString
}
let s2: String = returnSubstring()

// CTP_CallArgument
func takeString(_ s: String) {}

takeString(s)
takeString(ss)
do {
func takesAString(_ s: String) {}
takesAString(ss)
}

// CTP_ClosureResult
[1,2,3].map { (x: Int) -> String in
return ss
do {
[ss].map { (x: Substring) -> String in x }
}

// CTP_ArrayElement
let a: [String] = [ ss ]
do {
let a: [String] = [ ss ]
_ = a
}

// CTP_DictionaryKey
do {
let d: [ String : String ] = [ ss : s ]
_ = d
}

// CTP_DictionaryValue
let d: [String : String] = [
"CTP_DictionaryValue" : ss,
ss : "CTP_DictionaryKey",
]
do {
let d: [ String : String ] = [ s : ss ]
_ = d
}

// CTP_CoerceOperand
let s3: String = ss as String
do {
let s1: String = ss as String
_ = s1
}

// CTP_AssignSource
let s4: String = ss
do {
let s1: String = ss
_ = s1
}

Loading