Skip to content

[Refactoring] SR-5743 Try To Force Try Refactor implementation #12128

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

Closed
Closed
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
2 changes: 2 additions & 0 deletions include/swift/IDE/RefactoringKinds.def
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ CURSOR_REFACTORING(CollapseNestedIfExpr, "Collapse Nested If Expression", collap

CURSOR_REFACTORING(ConvertToDoCatch, "Convert To Do/Catch", convert.do.catch)

CURSOR_REFACTORING(ConvertToForceTry, "Convert To Force Try", convert.force.try)

RANGE_REFACTORING(ExtractExpr, "Extract Expression", extract.expr)

RANGE_REFACTORING(ExtractFunction, "Extract Method", extract.function)
Expand Down
83 changes: 83 additions & 0 deletions lib/IDE/Refactoring.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1973,6 +1973,89 @@ bool RefactoringActionConvertToDoCatch::performChange() {
return false;
}

struct TryExpressionConversionInfo {
TryExpr &TE;
DoCatchStmt *Stmt;
unsigned int NumberOfTriesInDoCatch;
TryExpressionConversionInfo(TryExpr &TE, DoCatchStmt *Stmt,
unsigned int NumberOfTriesInDoCatch):
TE(TE),
Stmt(Stmt),
NumberOfTriesInDoCatch(NumberOfTriesInDoCatch) {}
TryExpressionConversionInfo(TryExpr &TE): TE(TE), Stmt(nullptr),
NumberOfTriesInDoCatch(0) {}
};

static TryExpressionConversionInfo findTryConversion(ResolvedCursorInfo Info,
SourceFile *TheFile,
SourceManager &SM) {
auto *TE = dyn_cast<TryExpr>(Info.TrailingExpr);
assert(TE);
auto Node = ASTNode(TE);
auto NodeChecker = [](ASTNode N) {
return N.isStmt(StmtKind::DoCatch);
};
ContextFinder Finder(*TheFile, Node, NodeChecker);
Finder.resolve();
auto Contexts = Finder.getContexts();
if (Contexts.size() == 0) {
return TryExpressionConversionInfo(*TE);
}
auto StmtNode = Contexts.back();
DoCatchStmt *DCStmt = dyn_cast<DoCatchStmt>(StmtNode.dyn_cast<Stmt*>());

struct TryExprCounter: public SourceEntityWalker {
unsigned int Count = 0;
bool walkToExprPre(Expr *E) {
if (auto *FE = dyn_cast<TryExpr>(E)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we don't need FE; so isa<TryExpr>(E) is sufficient.

Count += 1;
}
return true;
}
} Counter;
Counter.walk(DCStmt);

return TryExpressionConversionInfo(*TE, DCStmt, Counter.Count);
}

bool RefactoringActionConvertToForceTry::
isApplicable(ResolvedCursorInfo Tok, DiagnosticEngine &Diag) {
if (!Tok.TrailingExpr)
return false;
return isa<TryExpr>(Tok.TrailingExpr);
}

bool RefactoringActionConvertToForceTry::performChange() {
auto ConversionInfo = findTryConversion(CursorInfo, TheFile, SM);
auto *DCStmt = ConversionInfo.Stmt;
// Add exclamation to the call.
auto TryLoc = ConversionInfo.TE.getTryLoc();
auto TryEndLoc = TryLoc.getAdvancedLocOrInvalid(getKeywordLen(tok::kw_try));
EditConsumer.accept(SM, TryEndLoc, "!");

if (DCStmt && ConversionInfo.NumberOfTriesInDoCatch == 1) {
//It's the only try in do catch block, remove the block.
auto *BodyStmt = DCStmt->getBody();
auto *BCStmt = dyn_cast<BraceStmt>(BodyStmt);
auto FirstNodeStartLoc = BCStmt->getElements().front().getStartLoc();
auto BeforeBodyRange = CharSourceRange(
SM,
DCStmt->getStartLoc(),
FirstNodeStartLoc);
EditConsumer.accept(SM, BeforeBodyRange, "");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a method in EditorConsumer called remove(SM, CharSourceRange) is cleaner than this. Could you add that method?

auto LastNodePastEndLoc = BCStmt->getElements()
.back().getEndLoc().getAdvancedLocOrInvalid(1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are not sure the last token is of length 1. Using Lexer::getLocForEndOfToken instead.

auto CatchRBraceLoc = DCStmt->getEndLoc()
.getAdvancedLocOrInvalid(getKeywordLen(tok::r_paren));
auto AfterBodyRange = CharSourceRange(
SM,
LastNodePastEndLoc,
CatchRBraceLoc);
EditConsumer.accept(SM, AfterBodyRange, "");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the newly added remove function here too.

}
return false;
}

/// Given a cursor position, this function tries to collect a number literal
/// expression immediately following the cursor.
static NumberLiteralExpr *getTrailingNumberLiteral(ResolvedCursorInfo Tok) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
func testTryToForceTry() {
func throwingFunc() throws -> Int { return 3 }
do {
let _ = try! throwingFunc()
let _ = try throwingFunc()
} catch {
let _ = error
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
func testTryToForceTry() {
func throwingFunc() throws -> Int { return 3 }
let _ = try! throwingFunc()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
func testTryToForceTry() {
func throwingFunc() throws -> Int { return 3 }
let _ = try! throwingFunc()
}
12 changes: 12 additions & 0 deletions test/refactoring/ConvertTryToForceTry/multiple_tries.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
func testTryToForceTry() {
func throwingFunc() throws -> Int { return 3 }
do {
let _ = try throwingFunc()
let _ = try throwingFunc()
} catch {
let _ = error
}
}
// RUN: rm -rf %t.result && mkdir -p %t.result
// RUN: %refactor -convert-to-force-try -source-filename %s -pos=4:14 > %t.result/L4.swift
// RUN: diff -u %S/Outputs/multiple_tries/L4.swift.expected %t.result/L4.swift
11 changes: 11 additions & 0 deletions test/refactoring/ConvertTryToForceTry/only_try.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
func testTryToForceTry() {
func throwingFunc() throws -> Int { return 3 }
do {
let _ = try throwingFunc()
} catch {
let _ = error
}
}
// RUN: rm -rf %t.result && mkdir -p %t.result
// RUN: %refactor -convert-to-force-try -source-filename %s -pos=4:14 > %t.result/L3.swift
// RUN: diff -u %S/Outputs/only_try/L3.swift.expected %t.result/L3.swift
7 changes: 7 additions & 0 deletions test/refactoring/ConvertTryToForceTry/try_without_do.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
func testTryToForceTry() {
func throwingFunc() throws -> Int { return 3 }
let _ = try throwingFunc()
}
// RUN: rm -rf %t.result && mkdir -p %t.result
// RUN: %refactor -convert-to-force-try -source-filename %s -pos=3:12 > %t.result/L3.swift
// RUN: diff -u %S/Outputs/try_without_do/L3.swift.expected %t.result/L3.swift
15 changes: 15 additions & 0 deletions test/refactoring/RefactoringKind/basic.swift
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,15 @@ func testForceTry() {
func throwingFunc() throws -> Int { return 3 }
let _ = try! throwingFunc()
}

func testTryToForceTry() {
func throwingFunc() throws -> Int { return 3 }
do {
let _ = try throwingFunc()
} catch {
let _ = error
}
}
// 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
Expand Down Expand Up @@ -301,6 +310,10 @@ func testForceTry() {
// 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

// RUN: %refactor -source-filename %s -pos=223:13 | %FileCheck %s -check-prefix=CHECK-FORCE-TRY
// RUN: %refactor -source-filename %s -pos=223:14 | %FileCheck %s -check-prefix=CHECK-FORCE-TRY
// RUN: %refactor -source-filename %s -pos=223:15 | %FileCheck %s -check-prefix=CHECK-FORCE-TRY

// CHECK1: Action begins
// CHECK1-NEXT: Extract Method
// CHECK1-NEXT: Action ends
Expand Down Expand Up @@ -342,3 +355,5 @@ func testForceTry() {

// CHECK-TRY-CATCH: Convert To Do/Catch

// CHECK-FORCE-TRY: Convert To Force Try

2 changes: 2 additions & 0 deletions tools/swift-refactor/swift-refactor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ Action(llvm::cl::desc("kind:"), llvm::cl::init(RefactoringKind::None),
"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::ConvertToForceTry,
"convert-to-force-try", "Perform try to force try refactoring"),
clEnumValN(RefactoringKind::SimplifyNumberLiteral,
"simplify-long-number", "Perform simplify long number literal refactoring"),
clEnumValN(RefactoringKind::ConvertStringsConcatenationToInterpolation,
Expand Down