Skip to content

Commit 80698a0

Browse files
Kacper20nkcsgexi
authored andcommitted
[IDE] SR-5745 Refactoring: converting strings concatenation expression to strings interpolation (#11944)
1 parent c112918 commit 80698a0

File tree

10 files changed

+193
-1
lines changed

10 files changed

+193
-1
lines changed

include/swift/AST/ASTContext.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,9 @@ class ASTContext {
440440
/// Get the '+' function on two RangeReplaceableCollection.
441441
FuncDecl *getPlusFunctionOnRangeReplaceableCollection() const;
442442

443+
/// Get the '+' function on two String.
444+
FuncDecl *getPlusFunctionOnString() const;
445+
443446
/// Check whether the standard library provides all the correct
444447
/// intrinsic support for Optional<T>.
445448
///

include/swift/IDE/RefactoringKinds.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ RANGE_REFACTORING(ExtractFunction, "Extract Method", extract.function)
4646

4747
RANGE_REFACTORING(ExtractRepeatedExpr, "Extract Repeated Expression", extract.expr.repeated)
4848

49+
RANGE_REFACTORING(ConvertStringsConcatenationToInterpolation, "Convert to String Interpolation", convert.string-concatenation.interpolation)
4950
#undef CURSOR_REFACTORING
5051
#undef RANGE_REFACTORING
5152
#undef SEMANTIC_REFACTORING

lib/AST/ASTContext.cpp

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,9 @@ struct ASTContext::Implementation {
132132
/// The declaration of '+' function for two RangeReplaceableCollection.
133133
FuncDecl *PlusFunctionOnRangeReplaceableCollection = nullptr;
134134

135+
/// The declaration of '+' function for two String.
136+
FuncDecl *PlusFunctionOnString = nullptr;
137+
135138
/// The declaration of Swift.Optional<T>.Some.
136139
EnumElementDecl *OptionalSomeDecl = nullptr;
137140

@@ -557,7 +560,7 @@ FuncDecl *ASTContext::getPlusFunctionOnRangeReplaceableCollection() const {
557560
lookupInSwiftModule("+", Results);
558561
for (auto Result : Results) {
559562
if (auto *FD = dyn_cast<FuncDecl>(Result)) {
560-
if(!FD->getOperatorDecl())
563+
if (!FD->getOperatorDecl())
561564
continue;
562565
for (auto Req: FD->getGenericRequirements()) {
563566
if (Req.getKind() == RequirementKind::Conformance &&
@@ -571,6 +574,36 @@ FuncDecl *ASTContext::getPlusFunctionOnRangeReplaceableCollection() const {
571574
return Impl.PlusFunctionOnRangeReplaceableCollection;
572575
}
573576

577+
FuncDecl *ASTContext::getPlusFunctionOnString() const {
578+
if (Impl.PlusFunctionOnString) {
579+
return Impl.PlusFunctionOnString;
580+
}
581+
// Find all of the declarations with this name in the Swift module.
582+
SmallVector<ValueDecl *, 1> Results;
583+
lookupInSwiftModule("+", Results);
584+
for (auto Result : Results) {
585+
if (auto *FD = dyn_cast<FuncDecl>(Result)) {
586+
if (!FD->getOperatorDecl())
587+
continue;
588+
auto ResultType = FD->getResultInterfaceType();
589+
if (ResultType->getNominalOrBoundGenericNominal() != getStringDecl())
590+
continue;
591+
auto ParamLists = FD->getParameterLists();
592+
if (ParamLists.size() != 2 || ParamLists[1]->size() != 2)
593+
continue;
594+
auto CheckIfStringParam = [this](ParamDecl* Param) {
595+
auto Type = Param->getInterfaceType()->getNominalOrBoundGenericNominal();
596+
return Type == getStringDecl();
597+
};
598+
if (CheckIfStringParam(ParamLists[1]->get(0)) &&
599+
CheckIfStringParam(ParamLists[1]->get(1))) {
600+
Impl.PlusFunctionOnString = FD;
601+
break;
602+
}
603+
}
604+
}
605+
return Impl.PlusFunctionOnString;
606+
}
574607

575608
#define KNOWN_STDLIB_TYPE_DECL(NAME, DECL_CLASS, NUM_GENERIC_PARAMS) \
576609
DECL_CLASS *ASTContext::get##NAME##Decl() const { \

lib/IDE/Refactoring.cpp

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1555,6 +1555,116 @@ bool RefactoringActionCollapseNestedIfExpr::performChange() {
15551555
return false;
15561556
}
15571557

1558+
static std::unique_ptr<llvm::SetVector<Expr*>>
1559+
findConcatenatedExpressions(ResolvedRangeInfo Info, ASTContext &Ctx) {
1560+
if (Info.Kind != RangeKind::SingleExpression
1561+
&& Info.Kind != RangeKind::PartOfExpression)
1562+
return nullptr;
1563+
Expr *E = Info.ContainedNodes[0].get<Expr*>();
1564+
1565+
struct StringInterpolationExprFinder: public SourceEntityWalker {
1566+
std::unique_ptr<llvm::SetVector<Expr*>> Bucket = llvm::
1567+
make_unique<llvm::SetVector<Expr*>>();
1568+
ASTContext &Ctx;
1569+
1570+
bool IsValidInterpolation = true;
1571+
StringInterpolationExprFinder(ASTContext &Ctx): Ctx(Ctx) {}
1572+
1573+
bool isConcatenationExpr(DeclRefExpr* Expr) {
1574+
if (!Expr)
1575+
return false;
1576+
auto *FD = dyn_cast<FuncDecl>(Expr->getDecl());
1577+
if (FD == nullptr || (FD != Ctx.getPlusFunctionOnString() &&
1578+
FD != Ctx.getPlusFunctionOnRangeReplaceableCollection())) {
1579+
return false;
1580+
}
1581+
return true;
1582+
}
1583+
1584+
bool walkToExprPre(Expr *E) {
1585+
if (E->isImplicit())
1586+
return true;
1587+
auto ExprType = E->getType()->getNominalOrBoundGenericNominal();
1588+
//Only binary concatenation operators should exist in expression
1589+
if (E->getKind() == ExprKind::Binary) {
1590+
auto *BE = dyn_cast<BinaryExpr>(E);
1591+
auto *OperatorDeclRef = BE->getSemanticFn()->getMemberOperatorRef();
1592+
if (!(isConcatenationExpr(OperatorDeclRef)
1593+
&& ExprType == Ctx.getStringDecl())) {
1594+
IsValidInterpolation = false;
1595+
return false;
1596+
}
1597+
return true;
1598+
}
1599+
// Everything that evaluates to string should be gathered.
1600+
if (ExprType == Ctx.getStringDecl()) {
1601+
Bucket->insert(E);
1602+
return false;
1603+
}
1604+
if (auto *DR = dyn_cast<DeclRefExpr>(E)) {
1605+
// Checks whether all function references in expression are concatenations.
1606+
auto *FD = dyn_cast<FuncDecl>(DR->getDecl());
1607+
auto IsConcatenation = isConcatenationExpr(DR);
1608+
if (FD && IsConcatenation) {
1609+
return false;
1610+
}
1611+
}
1612+
// There was non-expected expression, it's not valid interpolation then.
1613+
IsValidInterpolation = false;
1614+
return false;
1615+
}
1616+
} Walker(Ctx);
1617+
Walker.walk(E);
1618+
1619+
// There should be two or more expressions to convert.
1620+
if (!Walker.IsValidInterpolation || Walker.Bucket->size() < 2)
1621+
return nullptr;
1622+
1623+
return std::move(Walker.Bucket);
1624+
}
1625+
1626+
static void interpolatedExpressionForm(Expr *E, SourceManager &SM,
1627+
llvm::raw_ostream &OS) {
1628+
if (auto *Literal = dyn_cast<StringLiteralExpr>(E)) {
1629+
OS << Literal->getValue();
1630+
return;
1631+
}
1632+
auto ExpStr = Lexer::getCharSourceRangeFromSourceRange(SM,
1633+
E->getSourceRange()).str().str();
1634+
if (isa<InterpolatedStringLiteralExpr>(E)) {
1635+
ExpStr.erase(0, 1);
1636+
ExpStr.pop_back();
1637+
OS << ExpStr;
1638+
return;
1639+
}
1640+
OS << "\\(" << ExpStr << ")";
1641+
}
1642+
1643+
bool RefactoringActionConvertStringsConcatenationToInterpolation::
1644+
isApplicable(ResolvedRangeInfo Info, DiagnosticEngine &Diag) {
1645+
auto RangeContext = Info.RangeContext;
1646+
if (RangeContext) {
1647+
auto &Ctx = Info.RangeContext->getASTContext();
1648+
return findConcatenatedExpressions(Info, Ctx) != nullptr;
1649+
}
1650+
return false;
1651+
}
1652+
1653+
bool RefactoringActionConvertStringsConcatenationToInterpolation::performChange() {
1654+
auto Expressions = findConcatenatedExpressions(RangeInfo, Ctx);
1655+
if (!Expressions)
1656+
return true;
1657+
llvm::SmallString<64> Buffer;
1658+
llvm::raw_svector_ostream OS(Buffer);
1659+
OS << "\"";
1660+
for (auto It = Expressions->begin(); It != Expressions->end(); It++) {
1661+
interpolatedExpressionForm(*It, SM, OS);
1662+
}
1663+
OS << "\"";
1664+
EditConsumer.accept(SM, RangeInfo.ContentRange, Buffer);
1665+
return false;
1666+
}
1667+
15581668
/// The helper class analyzes a given nominal decl or an extension decl to
15591669
/// decide whether stubs are required to filled in and the context in which
15601670
/// these stubs should be filled.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
func testStringConcatenation() {
2+
let firstName = "Jason"
3+
let bornYear = "1888"
4+
let _ = "Mr. \(firstName)\(bornYear)"
5+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
func testStringConcatenation() {
2+
let firstName = "Jason"
3+
let number = 3
4+
let closure: () -> String = { return "FOO" }
5+
let _ = "Mr. \(firstName.debugDescription)\(closure())number: \(number)"
6+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
func testStringConcatenation() {
2+
let firstName = "Jason"
3+
let bornYear = "1888"
4+
let _ = "Mr. " + firstName + bornYear
5+
}
6+
// RUN: rm -rf %t.result && mkdir -p %t.result
7+
// RUN: %refactor -strings-concatenation-to-interpolation -source-filename %s -pos=4:11 -end-pos=4:40 > %t.result/L4.swift
8+
// RUN: diff -u %S/Outputs/basic/L4.swift.expected %t.result/L4.swift
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
func testStringConcatenation() {
2+
let firstName = "Jason"
3+
let number = 3
4+
let closure: () -> String = { return "FOO" }
5+
let _ = "Mr. " + firstName.debugDescription + closure() + "number: \(number)"
6+
}
7+
// RUN: rm -rf %t.result && mkdir -p %t.result
8+
// RUN: %refactor -strings-concatenation-to-interpolation -source-filename %s -pos=5:11 -end-pos=5:80 > %t.result/L5.swift
9+
// RUN: diff -u %S/Outputs/func_calls/L5.swift.expected %t.result/L5.swift

test/refactoring/RefactoringKind/basic.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,14 @@ func testExtraIfNestedIf() {
203203
let b = 0
204204
}
205205
}
206+
207+
func testStringInterpolation() -> String {
208+
let name = "Jason"
209+
let one = "\(1)"
210+
let _ = "aaa" + "bbb"
211+
let _ = name + "Bourne"
212+
let _ = name + one
213+
}
206214
// RUN: %refactor -source-filename %s -pos=2:1 -end-pos=5:13 | %FileCheck %s -check-prefix=CHECK1
207215
// RUN: %refactor -source-filename %s -pos=3:1 -end-pos=5:13 | %FileCheck %s -check-prefix=CHECK1
208216
// RUN: %refactor -source-filename %s -pos=4:1 -end-pos=5:13 | %FileCheck %s -check-prefix=CHECK1
@@ -279,6 +287,10 @@ func testExtraIfNestedIf() {
279287
// RUN: %refactor -source-filename %s -pos=193:3 | %FileCheck %s -check-prefix=CHECK-NONE
280288
// RUN: %refactor -source-filename %s -pos=201:3 | %FileCheck %s -check-prefix=CHECK-NONE
281289

290+
// RUN: %refactor -source-filename %s -pos=210:11 -end-pos=210:24 | %FileCheck %s -check-prefix=CHECK-STRINGS-INTERPOLATION
291+
// RUN: %refactor -source-filename %s -pos=211:11 -end-pos=211:26 | %FileCheck %s -check-prefix=CHECK-STRINGS-INTERPOLATION
292+
// RUN: %refactor -source-filename %s -pos=212:11 -end-pos=212:21 | %FileCheck %s -check-prefix=CHECK-STRINGS-INTERPOLATION
293+
282294
// CHECK1: Action begins
283295
// CHECK1-NEXT: Extract Method
284296
// CHECK1-NEXT: Action ends
@@ -315,3 +327,6 @@ func testExtraIfNestedIf() {
315327
// CHECK-LOCALIZE-STRING: Localize String
316328

317329
// CHECK-COLLAPSE-NESTED-IF-EXPRESSION: Collapse Nested If Expression
330+
331+
// CHECK-STRINGS-INTERPOLATION: Convert to String Interpolation
332+

tools/swift-refactor/swift-refactor.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ Action(llvm::cl::desc("kind:"), llvm::cl::init(RefactoringKind::None),
4242
"collapse-nested-if", "Perform collapse nested if statements"),
4343
clEnumValN(RefactoringKind::SimplifyNumberLiteral,
4444
"simplify-long-number", "Perform simplify long number literal refactoring"),
45+
clEnumValN(RefactoringKind::ConvertStringsConcatenationToInterpolation,
46+
"strings-concatenation-to-interpolation", "Perform strings concatenation to interpolation refactoring"),
4547
clEnumValN(RefactoringKind::ExtractFunction,
4648
"extract-function", "Perform extract function refactoring"),
4749
clEnumValN(RefactoringKind::GlobalRename,

0 commit comments

Comments
 (0)