Skip to content

[SR-5744] Refactoring action to convert if-let to guard-let and vice versa #24566

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 2 commits into from
Jul 11, 2019
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
4 changes: 4 additions & 0 deletions include/swift/IDE/RefactoringKinds.def
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ RANGE_REFACTORING(ExpandTernaryExpr, "Expand Ternary Expression", expand.ternary

RANGE_REFACTORING(ConvertToTernaryExpr, "Convert To Ternary Expression", convert.ternary.expr)

RANGE_REFACTORING(ConvertIfLetExprToGuardExpr, "Convert To Guard Expression", convert.iflet.to.guard.expr)

RANGE_REFACTORING(ConvertGuardExprToIfLetExpr, "Convert To IfLet Expression", convert.to.iflet.expr)

// These internal refactorings are designed to be helpful for working on
// the compiler/standard library, etc., but are likely to be just confusing and
// noise for general development.
Expand Down
174 changes: 174 additions & 0 deletions lib/IDE/Refactoring.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2068,6 +2068,180 @@ bool RefactoringActionExpandTernaryExpr::performChange() {
return false; //don't abort
}

bool RefactoringActionConvertIfLetExprToGuardExpr::
isApplicable(ResolvedRangeInfo Info, DiagnosticEngine &Diag) {

if (Info.Kind != RangeKind::SingleStatement
&& Info.Kind != RangeKind::MultiStatement)
return false;

if (Info.ContainedNodes.empty())
return false;

IfStmt *If = nullptr;

if (Info.ContainedNodes.size() == 1) {
if (auto S = Info.ContainedNodes[0].dyn_cast<Stmt*>()) {
If = dyn_cast<IfStmt>(S);
}
}

if (!If)
return false;

auto CondList = If->getCond();

if (CondList.size() == 1) {
auto E = CondList[0];
auto P = E.getKind();
if (P == swift::StmtConditionElement::CK_PatternBinding) {
auto Body = dyn_cast_or_null<BraceStmt>(If->getThenStmt());
if (Body)
return true;
}
}

return false;
}

bool RefactoringActionConvertIfLetExprToGuardExpr::performChange() {

auto S = RangeInfo.ContainedNodes[0].dyn_cast<Stmt*>();
IfStmt *If = dyn_cast<IfStmt>(S);
auto CondList = If->getCond();

// Get if-let condition
SourceRange range = CondList[0].getSourceRange();
SourceManager &SM = RangeInfo.RangeContext->getASTContext().SourceMgr;
auto CondCharRange = Lexer::getCharSourceRangeFromSourceRange(SM, range);

auto Body = dyn_cast_or_null<BraceStmt>(If->getThenStmt());

// Get if-let then body.
auto firstElement = Body->getElements()[0];
auto lastElement = Body->getElements().back();
SourceRange bodyRange = firstElement.getSourceRange();
bodyRange.widen(lastElement.getSourceRange());
auto BodyCharRange = Lexer::getCharSourceRangeFromSourceRange(SM, bodyRange);

llvm::SmallString<64> DeclBuffer;
llvm::raw_svector_ostream OS(DeclBuffer);

llvm::StringRef Space = " ";
llvm::StringRef NewLine = "\n";

OS << tok::kw_guard << Space;
OS << CondCharRange.str().str() << Space;
OS << tok::kw_else << Space;
OS << tok::l_brace << NewLine;

// Get if-let else body.
if (auto *ElseBody = dyn_cast_or_null<BraceStmt>(If->getElseStmt())) {
auto firstElseElement = ElseBody->getElements()[0];
auto lastElseElement = ElseBody->getElements().back();
SourceRange elseBodyRange = firstElseElement.getSourceRange();
elseBodyRange.widen(lastElseElement.getSourceRange());
auto ElseBodyCharRange = Lexer::getCharSourceRangeFromSourceRange(SM, elseBodyRange);
OS << ElseBodyCharRange.str().str() << NewLine;
}

OS << tok::kw_return << NewLine;
OS << tok::r_brace << NewLine;
OS << BodyCharRange.str().str();

// Replace if-let to guard
auto ReplaceRange = RangeInfo.ContentRange;
EditConsumer.accept(SM, ReplaceRange, DeclBuffer.str());

return false;
}

bool RefactoringActionConvertGuardExprToIfLetExpr::
isApplicable(ResolvedRangeInfo Info, DiagnosticEngine &Diag) {
if (Info.Kind != RangeKind::SingleStatement
&& Info.Kind != RangeKind::MultiStatement)
return false;

if (Info.ContainedNodes.empty())
return false;

GuardStmt *guardStmt = nullptr;

if (Info.ContainedNodes.size() > 0) {
if (auto S = Info.ContainedNodes[0].dyn_cast<Stmt*>()) {
guardStmt = dyn_cast<GuardStmt>(S);
}
}

if (!guardStmt)
return false;

auto CondList = guardStmt->getCond();

if (CondList.size() == 1) {
auto E = CondList[0];
auto P = E.getPatternOrNull();
if (P && E.getKind() == swift::StmtConditionElement::CK_PatternBinding)
return true;
}

return false;
}

bool RefactoringActionConvertGuardExprToIfLetExpr::performChange() {

// Get guard stmt
auto S = RangeInfo.ContainedNodes[0].dyn_cast<Stmt*>();
GuardStmt *Guard = dyn_cast<GuardStmt>(S);

// Get guard condition
auto CondList = Guard->getCond();

// Get guard condition source
SourceRange range = CondList[0].getSourceRange();
SourceManager &SM = RangeInfo.RangeContext->getASTContext().SourceMgr;
auto CondCharRange = Lexer::getCharSourceRangeFromSourceRange(SM, range);

llvm::SmallString<64> DeclBuffer;
llvm::raw_svector_ostream OS(DeclBuffer);

llvm::StringRef Space = " ";
llvm::StringRef NewLine = "\n";

OS << tok::kw_if << Space;
OS << CondCharRange.str().str() << Space;
OS << tok::l_brace << NewLine;

// Get nodes after guard to place them at if-let body
if (RangeInfo.ContainedNodes.size() > 1) {
auto S = RangeInfo.ContainedNodes[1].getSourceRange();
S.widen(RangeInfo.ContainedNodes.back().getSourceRange());
auto BodyCharRange = Lexer::getCharSourceRangeFromSourceRange(SM, S);
OS << BodyCharRange.str().str() << NewLine;
}
OS << tok::r_brace;

// Get guard body
auto Body = dyn_cast_or_null<BraceStmt>(Guard->getBody());

if (Body && Body->getElements().size() > 1) {
auto firstElement = Body->getElements()[0];
auto lastElement = Body->getElements().back();
SourceRange bodyRange = firstElement.getSourceRange();
bodyRange.widen(lastElement.getSourceRange());
auto BodyCharRange = Lexer::getCharSourceRangeFromSourceRange(SM, bodyRange);
OS << Space << tok::kw_else << Space << tok::l_brace << NewLine;
OS << BodyCharRange.str().str() << NewLine;
OS << tok::r_brace;
}

// Replace guard to if-let
auto ReplaceRange = RangeInfo.ContentRange;
EditConsumer.accept(SM, ReplaceRange, DeclBuffer.str());

return false;
}

/// Struct containing info about an IfStmt that can be converted into an IfExpr.
struct ConvertToTernaryExprInfo {
ConvertToTernaryExprInfo() {}
Expand Down
34 changes: 34 additions & 0 deletions test/refactoring/ConvertToGuard/Outputs/basic/L15-3.swift.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
func foo(idxOpt: Int?) {
if let idx = idxOpt {
print(idx)
}
}

func bar(fooOpt: Int?) {
if let foo = fooOpt {
let incrementedFoo = foo + 1
print(incrementedFoo)
}
}

func fooBar(fooOpt: Int?) {
guard let foo = fooOpt else {
return
}
let incrementedFoo = foo + 1
print(incrementedFoo)
print(foo)
}

func barFoo(idxOpt: Int?) {
if let idx = idxOpt {
print(idx)
} else {
print("nil")
}
}





34 changes: 34 additions & 0 deletions test/refactoring/ConvertToGuard/Outputs/basic/L2-3.swift.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
func foo(idxOpt: Int?) {
guard let idx = idxOpt else {
return
}
print(idx)
}

func bar(fooOpt: Int?) {
if let foo = fooOpt {
let incrementedFoo = foo + 1
print(incrementedFoo)
}
}

func fooBar(fooOpt: Int?) {
if let foo = fooOpt {
let incrementedFoo = foo + 1
print(incrementedFoo)
print(foo)
}
}

func barFoo(idxOpt: Int?) {
if let idx = idxOpt {
print(idx)
} else {
print("nil")
}
}





33 changes: 33 additions & 0 deletions test/refactoring/ConvertToGuard/Outputs/basic/L23-3.swift.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
func foo(idxOpt: Int?) {
if let idx = idxOpt {
print(idx)
}
}

func bar(fooOpt: Int?) {
if let foo = fooOpt {
let incrementedFoo = foo + 1
print(incrementedFoo)
}
}

func fooBar(fooOpt: Int?) {
if let foo = fooOpt {
let incrementedFoo = foo + 1
print(incrementedFoo)
print(foo)
}
}

func barFoo(idxOpt: Int?) {
guard let idx = idxOpt else {
print("nil")
return
}
print(idx)
}





34 changes: 34 additions & 0 deletions test/refactoring/ConvertToGuard/Outputs/basic/L8-3.swift.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
func foo(idxOpt: Int?) {
if let idx = idxOpt {
print(idx)
}
}

func bar(fooOpt: Int?) {
guard let foo = fooOpt else {
return
}
let incrementedFoo = foo + 1
print(incrementedFoo)
}

func fooBar(fooOpt: Int?) {
if let foo = fooOpt {
let incrementedFoo = foo + 1
print(incrementedFoo)
print(foo)
}
}

func barFoo(idxOpt: Int?) {
if let idx = idxOpt {
print(idx)
} else {
print("nil")
}
}





42 changes: 42 additions & 0 deletions test/refactoring/ConvertToGuard/basic.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
func foo(idxOpt: Int?) {
if let idx = idxOpt {
print(idx)
}
}

func bar(fooOpt: Int?) {
if let foo = fooOpt {
let incrementedFoo = foo + 1
print(incrementedFoo)
}
}

func fooBar(fooOpt: Int?) {
if let foo = fooOpt {
let incrementedFoo = foo + 1
print(incrementedFoo)
print(foo)
}
}

func barFoo(idxOpt: Int?) {
if let idx = idxOpt {
print(idx)
} else {
print("nil")
}
}

// RUN: rm -rf %t.result && mkdir -p %t.result

// RUN: %refactor -convert-to-guard -source-filename %s -pos=2:3 -end-pos=4:4 > %t.result/L2-3.swift
// RUN: diff -u %S/Outputs/basic/L2-3.swift.expected %t.result/L2-3.swift

// RUN: %refactor -convert-to-guard -source-filename %s -pos=8:3 -end-pos=11:4 > %t.result/L8-3.swift
// RUN: diff -u %S/Outputs/basic/L8-3.swift.expected %t.result/L8-3.swift

// RUN: %refactor -convert-to-guard -source-filename %s -pos=15:3 -end-pos=19:4 > %t.result/L15-3.swift
// RUN: diff -u %S/Outputs/basic/L15-3.swift.expected %t.result/L15-3.swift

// RUN: %refactor -convert-to-guard -source-filename %s -pos=23:3 -end-pos=27:4 > %t.result/L23-3.swift
// RUN: diff -u %S/Outputs/basic/L23-3.swift.expected %t.result/L23-3.swift
Loading