[IDE] Refactoring: conversion from “force try” to error handled version (#12063)
* [IDE] Refactoring: conversion from “force try” do error handled version
* Removed unnecessary empty line
* Changed name of the refactor
* Review fixes
* Fixed recognizing try_! location by CursorResolver
* Review fixes
diff --git a/include/swift/IDE/RefactoringKinds.def b/include/swift/IDE/RefactoringKinds.def
index d9b6123..5f436f1 100644
--- a/include/swift/IDE/RefactoringKinds.def
+++ b/include/swift/IDE/RefactoringKinds.def
@@ -40,6 +40,8 @@
CURSOR_REFACTORING(CollapseNestedIfExpr, "Collapse Nested If Expression", collapse.nested.if.expr)
+CURSOR_REFACTORING(ConvertToDoCatch, "Convert To Do/Catch", convert.do.catch)
+
RANGE_REFACTORING(ExtractExpr, "Extract Expression", extract.expr)
RANGE_REFACTORING(ExtractFunction, "Extract Method", extract.function)
diff --git a/lib/IDE/Refactoring.cpp b/lib/IDE/Refactoring.cpp
index d127303..f6bba17 100644
--- a/lib/IDE/Refactoring.cpp
+++ b/lib/IDE/Refactoring.cpp
@@ -12,6 +12,7 @@
#include "swift/IDE/Refactoring.h"
#include "swift/AST/ASTContext.h"
+#include "swift/AST/ASTPrinter.h"
#include "swift/AST/Decl.h"
#include "swift/AST/DiagnosticsRefactoring.h"
#include "swift/AST/Expr.h"
@@ -1923,6 +1924,60 @@
return false;
}
+static CharSourceRange
+ findSourceRangeToWrapInCatch(ResolvedCursorInfo CursorInfo,
+ SourceFile *TheFile,
+ SourceManager &SM) {
+ Expr *E = CursorInfo.TrailingExpr;
+ if (!E)
+ return CharSourceRange();
+ auto Node = ASTNode(E);
+ auto NodeChecker = [](ASTNode N) { return N.isStmt(StmtKind::Brace); };
+ ContextFinder Finder(*TheFile, Node, NodeChecker);
+ Finder.resolve();
+ auto Contexts = Finder.getContexts();
+ if (Contexts.size() == 0)
+ return CharSourceRange();
+ auto TargetNode = Contexts.back();
+ BraceStmt *BStmt = dyn_cast<BraceStmt>(TargetNode.dyn_cast<Stmt*>());
+ auto ConvertToCharRange = [&SM](SourceRange SR) {
+ return Lexer::getCharSourceRangeFromSourceRange(SM, SR);
+ };
+ assert(BStmt);
+ auto ExprRange = ConvertToCharRange(E->getSourceRange());
+ // Check elements of the deepest BraceStmt, pick one that covers expression.
+ for (auto Elem: BStmt->getElements()) {
+ auto ElemRange = ConvertToCharRange(Elem.getSourceRange());
+ if (ElemRange.contains(ExprRange))
+ TargetNode = Elem;
+ }
+ return ConvertToCharRange(TargetNode.getSourceRange());
+}
+
+bool RefactoringActionConvertToDoCatch::isApplicable(ResolvedCursorInfo Tok) {
+ if (!Tok.TrailingExpr)
+ return false;
+ return isa<ForceTryExpr>(Tok.TrailingExpr);
+}
+
+bool RefactoringActionConvertToDoCatch::performChange() {
+ auto *TryExpr = dyn_cast<ForceTryExpr>(CursorInfo.TrailingExpr);
+ if (!TryExpr)
+ return true;
+ auto Range = findSourceRangeToWrapInCatch(CursorInfo, TheFile, SM);
+ if (!Range.isValid())
+ return true;
+ // Wrap given range in do catch block.
+ EditConsumer.accept(SM, Range.getStart(), "do {\n");
+ EditorConsumerInsertStream OS(EditConsumer, SM, Range.getEnd());
+ OS << "\n} catch {\n" << getCodePlaceholder() << "\n}";
+
+ // Delete ! from try! expression
+ auto ExclaimRange = CharSourceRange(TryExpr->getExclaimLoc(), 1);
+ EditConsumer.accept(SM, ExclaimRange, "");
+ return false;
+}
+
/// Given a cursor position, this function tries to collect a number literal
/// expression immediately following the cursor.
static NumberLiteralExpr *getTrailingNumberLiteral(ResolvedCursorInfo Tok) {
diff --git a/lib/IDE/SwiftSourceDocInfo.cpp b/lib/IDE/SwiftSourceDocInfo.cpp
index 303c389..e2215ea 100644
--- a/lib/IDE/SwiftSourceDocInfo.cpp
+++ b/lib/IDE/SwiftSourceDocInfo.cpp
@@ -188,9 +188,14 @@
ContainerType = ME->getBase()->getType();
}
}
-
+ auto IsProperCursorLocation = E->getStartLoc() == LocToResolve;
+ // Handle cursor placement between try and ! in ForceTryExpr.
+ if (auto *FTE = dyn_cast<ForceTryExpr>(E)) {
+ IsProperCursorLocation = LocToResolve == FTE->getExclaimLoc() ||
+ IsProperCursorLocation;
+ }
// Keep track of trailing expressions.
- if (!E->isImplicit() && E->getStartLoc() == LocToResolve)
+ if (!E->isImplicit() && IsProperCursorLocation)
TrailingExprStack.push_back(E);
}
return true;
diff --git a/test/refactoring/ConvertForceTryToTryCatch/Outputs/basic/L5.swift.expected b/test/refactoring/ConvertForceTryToTryCatch/Outputs/basic/L5.swift.expected
new file mode 100644
index 0000000..3bf5e71
--- /dev/null
+++ b/test/refactoring/ConvertForceTryToTryCatch/Outputs/basic/L5.swift.expected
@@ -0,0 +1,8 @@
+func throwingFunc() throws {
+ return
+}
+do {
+try throwingFunc()
+} catch {
+<#code#>
+}
diff --git a/test/refactoring/ConvertForceTryToTryCatch/Outputs/basic_in_func/L5.swift.expected b/test/refactoring/ConvertForceTryToTryCatch/Outputs/basic_in_func/L5.swift.expected
new file mode 100644
index 0000000..8c0477c
--- /dev/null
+++ b/test/refactoring/ConvertForceTryToTryCatch/Outputs/basic_in_func/L5.swift.expected
@@ -0,0 +1,10 @@
+func throwingFunc() throws {
+ return
+}
+func foo() {
+ do {
+try throwingFunc()
+} catch {
+<#code#>
+}
+}
diff --git a/test/refactoring/ConvertForceTryToTryCatch/Outputs/for_loop/L5.swift.expected b/test/refactoring/ConvertForceTryToTryCatch/Outputs/for_loop/L5.swift.expected
new file mode 100644
index 0000000..4ab08b7
--- /dev/null
+++ b/test/refactoring/ConvertForceTryToTryCatch/Outputs/for_loop/L5.swift.expected
@@ -0,0 +1,10 @@
+func throwingFunc() throws -> [Int] {
+ return []
+}
+do {
+for num in try throwingFunc() {
+ let _ = num
+}
+} catch {
+<#code#>
+}
diff --git a/test/refactoring/ConvertForceTryToTryCatch/Outputs/in_if_clause/L5.swift.expected b/test/refactoring/ConvertForceTryToTryCatch/Outputs/in_if_clause/L5.swift.expected
new file mode 100644
index 0000000..28bbad5
--- /dev/null
+++ b/test/refactoring/ConvertForceTryToTryCatch/Outputs/in_if_clause/L5.swift.expected
@@ -0,0 +1,10 @@
+func throwingFunc() throws -> Bool {
+ return true
+}
+do {
+if try throwingFunc() {
+ let _ = 3
+}
+} catch {
+<#code#>
+}
diff --git a/test/refactoring/ConvertForceTryToTryCatch/Outputs/in_if_clause_nested/L5.swift.expected b/test/refactoring/ConvertForceTryToTryCatch/Outputs/in_if_clause_nested/L5.swift.expected
new file mode 100644
index 0000000..135ab26
--- /dev/null
+++ b/test/refactoring/ConvertForceTryToTryCatch/Outputs/in_if_clause_nested/L5.swift.expected
@@ -0,0 +1,12 @@
+func throwingFunc() throws -> Bool {
+ return true
+}
+if 4 > 3 {
+ do {
+if try throwingFunc() {
+ let _ = 3
+ }
+} catch {
+<#code#>
+}
+}
diff --git a/test/refactoring/ConvertForceTryToTryCatch/Outputs/in_if_clause_with_let_binding/L5.swift.expected b/test/refactoring/ConvertForceTryToTryCatch/Outputs/in_if_clause_with_let_binding/L5.swift.expected
new file mode 100644
index 0000000..e21b4a4
--- /dev/null
+++ b/test/refactoring/ConvertForceTryToTryCatch/Outputs/in_if_clause_with_let_binding/L5.swift.expected
@@ -0,0 +1,10 @@
+func throwingFunc() throws -> Int? {
+ return nil
+}
+do {
+if let val = try throwingFunc() {
+ let _ = val
+}
+} catch {
+<#code#>
+}
diff --git a/test/refactoring/ConvertForceTryToTryCatch/Outputs/inside_constructor/L5.swift.expected b/test/refactoring/ConvertForceTryToTryCatch/Outputs/inside_constructor/L5.swift.expected
new file mode 100644
index 0000000..42adf87
--- /dev/null
+++ b/test/refactoring/ConvertForceTryToTryCatch/Outputs/inside_constructor/L5.swift.expected
@@ -0,0 +1,9 @@
+func throwingFunc() throws -> [Int] {
+ return []
+}
+struct X { let array: [Int] }
+do {
+let _ = X(array: try throwingFunc())
+} catch {
+<#code#>
+}
diff --git a/test/refactoring/ConvertForceTryToTryCatch/basic.swift b/test/refactoring/ConvertForceTryToTryCatch/basic.swift
new file mode 100644
index 0000000..54e1982
--- /dev/null
+++ b/test/refactoring/ConvertForceTryToTryCatch/basic.swift
@@ -0,0 +1,7 @@
+func throwingFunc() throws {
+ return
+}
+try! throwingFunc()
+// RUN: rm -rf %t.result && mkdir -p %t.result
+// RUN: %refactor -convert-to-do-catch -source-filename %s -pos=4:3 > %t.result/L5.swift
+// RUN: diff -u %S/Outputs/basic/L5.swift.expected %t.result/L5.swift
diff --git a/test/refactoring/ConvertForceTryToTryCatch/basic_in_func.swift b/test/refactoring/ConvertForceTryToTryCatch/basic_in_func.swift
new file mode 100644
index 0000000..f7e7d41
--- /dev/null
+++ b/test/refactoring/ConvertForceTryToTryCatch/basic_in_func.swift
@@ -0,0 +1,9 @@
+func throwingFunc() throws {
+ return
+}
+func foo() {
+ try! throwingFunc()
+}
+// RUN: rm -rf %t.result && mkdir -p %t.result
+// RUN: %refactor -convert-to-do-catch -source-filename %s -pos=5:7 > %t.result/L5.swift
+// RUN: diff -u %S/Outputs/basic_in_func/L5.swift.expected %t.result/L5.swift
diff --git a/test/refactoring/ConvertForceTryToTryCatch/for_loop.swift b/test/refactoring/ConvertForceTryToTryCatch/for_loop.swift
new file mode 100644
index 0000000..c378013
--- /dev/null
+++ b/test/refactoring/ConvertForceTryToTryCatch/for_loop.swift
@@ -0,0 +1,9 @@
+func throwingFunc() throws -> [Int] {
+ return []
+}
+for num in try! throwingFunc() {
+ let _ = num
+}
+// RUN: rm -rf %t.result && mkdir -p %t.result
+// RUN: %refactor -convert-to-do-catch -source-filename %s -pos=4:14 > %t.result/L5.swift
+// RUN: diff -u %S/Outputs/for_loop/L5.swift.expected %t.result/L5.swift
diff --git a/test/refactoring/ConvertForceTryToTryCatch/in_if_clause.swift b/test/refactoring/ConvertForceTryToTryCatch/in_if_clause.swift
new file mode 100644
index 0000000..6af7fa2
--- /dev/null
+++ b/test/refactoring/ConvertForceTryToTryCatch/in_if_clause.swift
@@ -0,0 +1,9 @@
+func throwingFunc() throws -> Bool {
+ return true
+}
+if try! throwingFunc() {
+ let _ = 3
+}
+// RUN: rm -rf %t.result && mkdir -p %t.result
+// RUN: %refactor -convert-to-do-catch -source-filename %s -pos=4:4 > %t.result/L5.swift
+// RUN: diff -u %S/Outputs/in_if_clause/L5.swift.expected %t.result/L5.swift
diff --git a/test/refactoring/ConvertForceTryToTryCatch/in_if_clause_nested.swift b/test/refactoring/ConvertForceTryToTryCatch/in_if_clause_nested.swift
new file mode 100644
index 0000000..7cf27bc
--- /dev/null
+++ b/test/refactoring/ConvertForceTryToTryCatch/in_if_clause_nested.swift
@@ -0,0 +1,11 @@
+func throwingFunc() throws -> Bool {
+ return true
+}
+if 4 > 3 {
+ if try! throwingFunc() {
+ let _ = 3
+ }
+}
+// RUN: rm -rf %t.result && mkdir -p %t.result
+// RUN: %refactor -convert-to-do-catch -source-filename %s -pos=5:10 > %t.result/L5.swift
+// RUN: diff -u %S/Outputs/in_if_clause_nested/L5.swift.expected %t.result/L5.swift
diff --git a/test/refactoring/ConvertForceTryToTryCatch/in_if_clause_with_let_binding.swift b/test/refactoring/ConvertForceTryToTryCatch/in_if_clause_with_let_binding.swift
new file mode 100644
index 0000000..d381356
--- /dev/null
+++ b/test/refactoring/ConvertForceTryToTryCatch/in_if_clause_with_let_binding.swift
@@ -0,0 +1,9 @@
+func throwingFunc() throws -> Int? {
+ return nil
+}
+if let val = try! throwingFunc() {
+ let _ = val
+}
+// RUN: rm -rf %t.result && mkdir -p %t.result
+// RUN: %refactor -convert-to-do-catch -source-filename %s -pos=4:16 > %t.result/L5.swift
+// RUN: diff -u %S/Outputs/in_if_clause_with_let_binding/L5.swift.expected %t.result/L5.swift
diff --git a/test/refactoring/ConvertForceTryToTryCatch/inside_constructor.swift b/test/refactoring/ConvertForceTryToTryCatch/inside_constructor.swift
new file mode 100644
index 0000000..2c7df9d
--- /dev/null
+++ b/test/refactoring/ConvertForceTryToTryCatch/inside_constructor.swift
@@ -0,0 +1,8 @@
+func throwingFunc() throws -> [Int] {
+ return []
+}
+struct X { let array: [Int] }
+let _ = X(array: try! throwingFunc())
+// RUN: rm -rf %t.result && mkdir -p %t.result
+// RUN: %refactor -convert-to-do-catch -source-filename %s -pos=5:19 > %t.result/L5.swift
+// RUN: diff -u %S/Outputs/inside_constructor/L5.swift.expected %t.result/L5.swift
diff --git a/test/refactoring/RefactoringKind/basic.swift b/test/refactoring/RefactoringKind/basic.swift
index bb8ad22..99edb0a 100644
--- a/test/refactoring/RefactoringKind/basic.swift
+++ b/test/refactoring/RefactoringKind/basic.swift
@@ -211,6 +211,11 @@
let _ = name + "Bourne"
let _ = name + one
}
+
+func testForceTry() {
+ func throwingFunc() throws -> Int { return 3 }
+ let _ = try! throwingFunc()
+}
// RUN: %refactor -source-filename %s -pos=2:1 -end-pos=5:13 | %FileCheck %s -check-prefix=CHECK1
// RUN: %refactor -source-filename %s -pos=3:1 -end-pos=5:13 | %FileCheck %s -check-prefix=CHECK1
// RUN: %refactor -source-filename %s -pos=4:1 -end-pos=5:13 | %FileCheck %s -check-prefix=CHECK1
@@ -291,6 +296,11 @@
// RUN: %refactor -source-filename %s -pos=211:11 -end-pos=211:26 | %FileCheck %s -check-prefix=CHECK-STRINGS-INTERPOLATION
// RUN: %refactor -source-filename %s -pos=212:11 -end-pos=212:21 | %FileCheck %s -check-prefix=CHECK-STRINGS-INTERPOLATION
+// RUN: %refactor -source-filename %s -pos=217:11 | %FileCheck %s -check-prefix=CHECK-TRY-CATCH
+// RUN: %refactor -source-filename %s -pos=217:12 | %FileCheck %s -check-prefix=CHECK-TRY-CATCH
+// RUN: %refactor -source-filename %s -pos=217:13 | %FileCheck %s -check-prefix=CHECK-TRY-CATCH
+// RUN: %refactor -source-filename %s -pos=217:14 | %FileCheck %s -check-prefix=CHECK-TRY-CATCH
+
// CHECK1: Action begins
// CHECK1-NEXT: Extract Method
// CHECK1-NEXT: Action ends
@@ -330,3 +340,5 @@
// CHECK-STRINGS-INTERPOLATION: Convert to String Interpolation
+// CHECK-TRY-CATCH: Convert To Do/Catch
+
diff --git a/tools/swift-refactor/swift-refactor.cpp b/tools/swift-refactor/swift-refactor.cpp
index 84cef21..39deddc 100644
--- a/tools/swift-refactor/swift-refactor.cpp
+++ b/tools/swift-refactor/swift-refactor.cpp
@@ -39,11 +39,13 @@
clEnumValN(RefactoringKind::LocalizeString,
"localize-string", "Perform string localization refactoring"),
clEnumValN(RefactoringKind::CollapseNestedIfExpr,
- "collapse-nested-if", "Perform collapse nested if statements"),
+ "collapse-nested-if", "Perform collapse nested if statements"),
+ clEnumValN(RefactoringKind::ConvertToDoCatch,
+ "convert-to-do-catch", "Perform force try to do try catch refactoring"),
clEnumValN(RefactoringKind::SimplifyNumberLiteral,
"simplify-long-number", "Perform simplify long number literal refactoring"),
clEnumValN(RefactoringKind::ConvertStringsConcatenationToInterpolation,
- "strings-concatenation-to-interpolation", "Perform strings concatenation to interpolation refactoring"),
+ "strings-concatenation-to-interpolation", "Perform strings concatenation to interpolation refactoring"),
clEnumValN(RefactoringKind::ExtractFunction,
"extract-function", "Perform extract function refactoring"),
clEnumValN(RefactoringKind::GlobalRename,