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
SR-5739 Add refactoring action to collapse nested if #11851
Conversation
This is a work-in-progress that adds a refactoring action to collapse nested if statements. See SR-5739.
@harlanhaskins Thanks for offering to help me with this. |
lib/IDE/Refactoring.cpp
Outdated
SourceLoc StartLoc; | ||
CollapsibleNestedIfInfo IfInfo; | ||
IfStmtFinder(SourceLoc StartLoc): StartLoc(StartLoc), IfInfo() {} | ||
bool walkToStmtPre(Stmt *S) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: LLVM style prefers early returns, rather than nesting the body of the function. This would be better expressed by:
if (S->getKind() != StmtKind::If) {
return false;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: LLVM style uses 2 spaces for indentation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could also dyn_cast first
auto *IFS = dyn_cast<IfStmt>(S);
if (!IFS) return false;
/**/
lib/IDE/Refactoring.cpp
Outdated
IfInfo.OuterIf = dyn_cast<IfStmt>(S); | ||
return true; | ||
} | ||
if (S->getKind() == StmtKind::If) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This condition is the same as the outer condition, you can remove this if check
Wrote a couple of nitpicks, but I think this is definitely in the right track! Thanks for tackling this and thanks for contributing! 🎉 |
@willtellis Awesome work so far! Thank you for contributing to Swift refactoring. |
lib/IDE/Refactoring.cpp
Outdated
auto OuterIfConditionals = Target.OuterIf->getCond().vec(); | ||
auto InnerIfConditionals = Target.InnerIf->getCond().vec(); | ||
|
||
auto OuterIfConditionalText = Lexer::getCharSourceRangeFromSourceRange(SM, OuterIfConditionals[0].getSourceRange()).str(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: 80 characters per line, please
if a < 10 {} | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we add a test case to ensure such refactoring kind doesn't show up in a multi-statement brace statement?
e.g
if a > 2 { // not here.
if a < 5 {
}
if a < 10 {
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh, I saw your listed to do above. Please ignore this.
Filter out statements where the outer if statement contains anything besides the inner if
@@ -1463,6 +1463,112 @@ bool RefactoringActionExtractRepeatedExpr::performChange() { | |||
EditConsumer).performChange(); | |||
} | |||
|
|||
struct CollapsibleNestedIfInfo { | |||
IfStmt *OuterIf; | |||
IfStmt *InnerIf; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this mean that refactoring will support only 2 if statements initially?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm personally ok with collapsing only 2 if statements at one refactoring operation, since the users can always do more by invoking the tool multiple times.
lib/IDE/Refactoring.cpp
Outdated
|
||
llvm::SmallString<64> DeclBuffer; | ||
llvm::raw_svector_ostream OS(DeclBuffer); | ||
OS << tok::kw_if << " (" << OuterIfConditionalText << ") && ("; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While thinking on how multiple if-statement conditions could be combined together, I have re-discovered once again that in C-based languages comma is an operator and it's computed left-to-right, which means for boolean conditions it's same as &&
.
So if we have
if a > 1 {
if a < 1 || a > 3 {
}
}
instead of if (a > 1) && (a < 1 || a > 3)
we can just do if a > 1, a < 1 || a > 3
.
It may look a bit off, but when we have optional binding or pattern matching inside if statements, then ,
works for all cases:
if let a = a, a > 1 {
if let b = b, b < 3 || b > 5 {
if d == false {
}
}
}
with ,
combining conditions is very easy:
if let a = a, a > 1, let b = b, b < 3 || b > 5, d == false {
}
It will also work for pattern matches:
if case let .one = enum1, a > 1 {
if case let .two = enum2, b < 5 {
print("hhh'")
}
}
will become:
if case let .one = enum1, a > 1, case let .two = enum2, b < 5 {
print("hhh'")
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was planning on implementing combining of conditionals for if let
statements using this approach next. If we prefer combining conditionals using commas for all cases, it certainly simplifies the logic. I believe it also makes it so we can easily combine if
statements with more than one conditional. Thoughts? Should I change direction and pursue this approach? I don't think it will be a big change from what I already have.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually I just went ahead and did it. Let me know what you think!
Make collapse nested if refactoring action separate conditionals with commas. Update refactoring to handle collapsing if statements with multiple conditionals. This has the consequence of automatically handling collapsing "let" and "case" conditionals.
I'm still dealing with one small issue. How can I apply proper indentation to my collapsed
becomes
|
@nkcsgexi might have a better answer, but what if you used the column of the inner This will break if, say, the code is
But that might be a case we don’t care too much about... |
@willtellis Source editor will take care of indentation after applying a refactoring, so here we need to only worry about generating the correct refactoring content. |
@nkcsgexi That's good to know, thanks! In that case, I think this PR is ready for approval. Do we need to run these smoke tests next? |
@swift-ci please smoke test |
@swift-ci Please test Linux platform |
Build failed |
@willtellis Merged! |
Thanks for the great contribution, @willtellis! |
Thanks for all the help! |
This implements a refactoring action in the IDE to collapse nested
if
statements. It handles collapsing only a single outerif
statement with single innerif
statement. Eachif
statement may contain one or more conditionals.For example
would be refactored to
To do:
if
statement contains anything besides the innerif
if let
statementsAddresses SR-5739.