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

[refactoring] Implement "Convert to Trailing Closure" refactoring action #12268

Merged
merged 2 commits into from Oct 16, 2017

Conversation

rintaro
Copy link
Member

@rintaro rintaro commented Oct 4, 2017

Implement SR-5738

As a groundwork, I modified CursorInfoResolver and ResolvedCursorInfo so that it preserves surrounding ASTNode (direct child of BraceStmt).
Also, introduced CursorInfoKind::Middle (naming TBD) which means the cursor is at middle of someting, like:

do {
  foo(x: bar, y: baz)
        here
}

As a ground work, added SourceFile to ResolvedCursorInfo struct. Also, let CursorInfoResolver always set Loc to ResolvedCursorInfo so that we can manually resolve the surrounding context later.

This refactoring action searches innermost CallExpr from the cursor position. If found, it's applicable if that CallExpr has ClosureExpr as the last argument.

@rintaro
Copy link
Member Author

rintaro commented Oct 4, 2017

@swift-ci Please smoke test

@rintaro
Copy link
Member Author

rintaro commented Oct 4, 2017

@nkcsgexi Could you review this?
I'm not 100% sure that this strategy is good.

@rintaro rintaro force-pushed the refactoring-trailingclosure branch from a2308f5 to 8d05ecb Compare October 4, 2017 16:50
@rintaro
Copy link
Member Author

rintaro commented Oct 4, 2017

@swift-ci Please smoke test

@nkcsgexi nkcsgexi self-requested a review October 5, 2017 00:20
default:
Loc = CursorInfo.Loc;
break;
}
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 just replace this with an early return in the second case? and use SourceLoc Loc = CursorInfo.Loc; in general. CursorInfo.TrailingExpr->getStartLoc() and CursorInfo.Loc should be equivalent.

@@ -152,6 +156,14 @@ bool CursorInfoResolver::walkToStmtPre(Stmt *S) {
// non-implicit Stmts (fix Stmts created for lazy vars).
if (!S->isImplicit() && !rangeContainsLoc(S->getSourceRange()))
return false;

if (isa<BraceStmt>(S)) {
ContextStack.push_back(ContextNode);
Copy link
Member

Choose a reason for hiding this comment

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

We only capture explicit brace statement as the context node. I think the main benefit is we can start walk from the brace statement instead of the source file. However, walking AST is fairly efficient operation. I think we can afford to start walking from SourceFIle.

Copy link
Member Author

Choose a reason for hiding this comment

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

OK, I will try walking from SourceFIle.
Can I add SourceFile property to ResolvedCursorInfo then?
Should I pass SF from collectAvailableRefactorings() to isApplicable()?

Copy link
Member

@nkcsgexi nkcsgexi Oct 6, 2017

Choose a reason for hiding this comment

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

That's a good idea! can we add a SoureFile field to ResolvedCursorInfo instead of passing it? I bet other refactorings may need this information too.

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually mine needs it, too :) I've changed the isApplicable to expect ApplicabilityContext, which is a struct that contains DiagnosticEngine reference and SourceFile pointer. You could check it here: #12281. To be honest either way works, so I'm happy to change it.
One thought, though: If we're passing SourceFile to the ResolvedCursorInfo— shouldn't we pass DiagnosticEngine the same way for consistency?

Copy link
Member Author

Choose a reason for hiding this comment

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

If we're passing SourceFile to the ResolvedCursorInfo — shouldn't we pass DiagnosticEngine the same way for consistency?

I don't think DiagnosticEngine should be a property of ResolvedCursorInfo, whereas SourceFile is directly related to the cursor position.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, you're right 👍

}

public:
CallExprFinder(SourceManager &SM, SourceLoc Loc) : SM(SM), Loc(Loc) {}
Copy link
Member

Choose a reason for hiding this comment

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

Once we walk the entire source file to find the call expression. We can use an existing utility in this file ContextFinder. right?

@nkcsgexi
Copy link
Member

nkcsgexi commented Oct 5, 2017

@rintaro Looks good in general. Some comments inline 😄

@rintaro
Copy link
Member Author

rintaro commented Oct 10, 2017

@swift-ci Please smoke test

@rintaro
Copy link
Member Author

rintaro commented Oct 10, 2017

@swift-ci Please smoke test Linux platform

1 similar comment
@rintaro
Copy link
Member Author

rintaro commented Oct 10, 2017

@swift-ci Please smoke test Linux platform

@rintaro
Copy link
Member Author

rintaro commented Oct 11, 2017

@nkcsgexi
Could you re-review this?

Copy link
Member

@nkcsgexi nkcsgexi left a comment

Choose a reason for hiding this comment

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

Looks great, @rintaro some minor questions/comments are inline.

@@ -2066,6 +2073,107 @@ bool RefactoringActionSimplifyNumberLiteral::performChange() {
return true;
}

static CallExpr *findTrailingClosureTarget(SourceManager &SM,
ResolvedCursorInfo CursorInfo) {
if (CursorInfo.Kind == CursorInfoKind::StmtStart)
Copy link
Member

Choose a reason for hiding this comment

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

Why are we handling StmtStart specifically here?

Copy link
Member Author

Choose a reason for hiding this comment

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

Because StmtStart cursor position can't be a part of CallExpr.
We can bail-out early.

LastArg = TE->getElements().back();
}

while (auto *ICE = dyn_cast<ImplicitConversionExpr>(LastArg))
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 check isImplicit() instead, which seems to be more generic.

Copy link
Member

Choose a reason for hiding this comment

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

I think your original code makes sense, but can we add it as a member function to ImplicitConversionExpr?

Copy link
Member Author

Choose a reason for hiding this comment

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

isImplicit() doesn't necessary mean it's ImplicitConversionExpr.
Probably, we should add a member function to Expr, say getSingleSyntacticExpr() which dig into isImplicit() expressions using SourceEntitiyWalker(or ASTWalker). (Single because implicit TupleExpr may contain multiple syntactic expressions. Returns nullptr in such cases).

Copy link
Member

Choose a reason for hiding this comment

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

I think we can add getSyntacticSubExpr() to ImplicitConversionExpr. Adding such method to Expr seems to be an overkill because in a long term we will have libSyntax to get rid of all implicit nodes. Also, as you said, we have to return nullptr for TupleExpr which is a little ambiguous for we're not sure whether it means no explicit nodes or multiple of them. What do you think?

Copy link
Member Author

Choose a reason for hiding this comment

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

I agree. Thanks!

if (Finder.getContexts().empty()
|| !Finder.getContexts().back().is<Expr*>())
return nullptr;
CallExpr *CE = dyn_cast<CallExpr>(Finder.getContexts().back().get<Expr*>());
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 use cast here? because we're sure it'll succeed.

Copy link
Member Author

Choose a reason for hiding this comment

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

You are right! 👍

if (NumArgs > 1)
PrevArg = TE->getElement(NumArgs - 2);
}
while (auto *ICE = dyn_cast<ImplicitConversionExpr>(ClosureArg))
Copy link
Member

Choose a reason for hiding this comment

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

Same here; isImplicit() seems to be more appropriate.

while (auto *ICE = dyn_cast<ImplicitConversionExpr>(ClosureArg))
ClosureArg = ICE->getSubExpr();

if (!isa<ClosureExpr>(ClosureArg) || LPLoc.isInvalid() || RPLoc.isInvalid())
Copy link
Member

Choose a reason for hiding this comment

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

Why do we check !isa<ClosureExpr>(ClosureArg)? it seems to me we are sure the closure is present.

SM,
Lexer::getLocForEndOfToken(SM, ClosureArg->getEndLoc()),
Lexer::getLocForEndOfToken(SM, RPLoc));
EditConsumer.accept(SM, PostRange, "");
Copy link
Member

Choose a reason for hiding this comment

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

could we add a remove function to SourceEditConsumer and use it here?

@rintaro
Copy link
Member Author

rintaro commented Oct 16, 2017

@swift-ci Please smoke test

@nkcsgexi
Copy link
Member

Looks great! Merging.

@nkcsgexi
Copy link
Member

@rintaro LGTM, feel free to merge!

@rintaro rintaro merged commit 0b34078 into apple:master Oct 16, 2017
@rintaro
Copy link
Member Author

rintaro commented Oct 16, 2017

Thank you @nkcsgexi !

@rintaro rintaro deleted the refactoring-trailingclosure branch October 16, 2017 20:57
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

3 participants