Skip to content
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

Merged
merged 9 commits into from Sep 18, 2017

Conversation

willtellis
Copy link
Contributor

@willtellis willtellis commented Sep 11, 2017

This implements a refactoring action in the IDE to collapse nested if statements. It handles collapsing only a single outer if statement with single inner if statement. Each if statement may contain one or more conditionals.

For example

if a > 2 {
  if a < 10 {}
}

would be refactored to

if a > 2, a < 10 {}

To do:

  • Filter out statements where the outer if statement contains anything besides the inner if
  • Handle collapsing if let statements
  • Add tests

Addresses SR-5739.

This is a work-in-progress that adds a refactoring action to
collapse nested if statements. See SR-5739.
@willtellis
Copy link
Contributor Author

@harlanhaskins Thanks for offering to help me with this.

SourceLoc StartLoc;
CollapsibleNestedIfInfo IfInfo;
IfStmtFinder(SourceLoc StartLoc): StartLoc(StartLoc), IfInfo() {}
bool walkToStmtPre(Stmt *S) {
Copy link
Collaborator

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;
}

Copy link
Collaborator

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.

Copy link
Member

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;
/**/ 

IfInfo.OuterIf = dyn_cast<IfStmt>(S);
return true;
}
if (S->getKind() == StmtKind::If) {
Copy link
Collaborator

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

@harlanhaskins
Copy link
Collaborator

Wrote a couple of nitpicks, but I think this is definitely in the right track! Thanks for tackling this and thanks for contributing! 🎉

@nkcsgexi
Copy link
Member

@willtellis Awesome work so far! Thank you for contributing to Swift refactoring.

auto OuterIfConditionals = Target.OuterIf->getCond().vec();
auto InnerIfConditionals = Target.InnerIf->getCond().vec();

auto OuterIfConditionalText = Lexer::getCharSourceRangeFromSourceRange(SM, OuterIfConditionals[0].getSourceRange()).str();
Copy link
Collaborator

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 {}
}
}

Copy link
Member

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 {
  }
}

Copy link
Member

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;

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?

Copy link
Member

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.


llvm::SmallString<64> DeclBuffer;
llvm::raw_svector_ostream OS(DeclBuffer);
OS << tok::kw_if << " (" << OuterIfConditionalText << ") && (";
Copy link

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'")
    }

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.
@willtellis willtellis changed the title [WIP] SR-5739 Add refactoring action to collapse nested if SR-5739 Add refactoring action to collapse nested if Sep 15, 2017
@willtellis
Copy link
Contributor Author

I'm still dealing with one small issue. How can I apply proper indentation to my collapsed if statement? Currently,

if a > 2 {
  if a < 10 {
  }
}

becomes

if a > 2, a < 10 {
  }

@harlanhaskins
Copy link
Collaborator

@nkcsgexi might have a better answer, but what if you used the column of the inner if?

This will break if, say, the code is

if x { if y { } }

But that might be a case we don’t care too much about...

@nkcsgexi
Copy link
Member

@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.

@willtellis
Copy link
Contributor Author

@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?

@harlanhaskins
Copy link
Collaborator

@swift-ci please smoke test

@nkcsgexi
Copy link
Member

@swift-ci Please test Linux platform

@swift-ci
Copy link
Collaborator

Build failed
Swift Test Linux Platform
Git Sha - 15a6c60

@nkcsgexi nkcsgexi merged commit 57454eb into apple:master Sep 18, 2017
@nkcsgexi
Copy link
Member

@willtellis Merged!

@harlanhaskins
Copy link
Collaborator

Thanks for the great contribution, @willtellis!

@willtellis
Copy link
Contributor Author

Thanks for all the help!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants