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
Conversation
@swift-ci Please smoke test |
@nkcsgexi Could you review this? |
a2308f5
to
8d05ecb
Compare
@swift-ci Please smoke test |
lib/IDE/Refactoring.cpp
Outdated
default: | ||
Loc = CursorInfo.Loc; | ||
break; | ||
} |
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 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.
lib/IDE/SwiftSourceDocInfo.cpp
Outdated
@@ -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); |
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.
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.
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.
OK, I will try walking from SourceFIle
.
Can I add SourceFile
property to ResolvedCursorInfo
then?
Should I pass SF
from collectAvailableRefactorings()
to isApplicable()
?
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.
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.
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 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?
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.
If we're passing
SourceFile
to theResolvedCursorInfo
— shouldn't we passDiagnosticEngine
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.
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.
Yeah, you're right 👍
lib/IDE/Refactoring.cpp
Outdated
} | ||
|
||
public: | ||
CallExprFinder(SourceManager &SM, SourceLoc Loc) : SM(SM), Loc(Loc) {} |
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.
Once we walk the entire source file to find the call expression. We can use an existing utility in this file ContextFinder
. right?
@rintaro Looks good in general. Some comments inline 😄 |
784277c
to
32c01dd
Compare
@swift-ci Please smoke test |
@swift-ci Please smoke test Linux platform |
1 similar comment
@swift-ci Please smoke test Linux platform |
@nkcsgexi |
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.
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) |
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.
Why are we handling StmtStart
specifically here?
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.
Because StmtStart
cursor position can't be a part of CallExpr
.
We can bail-out early.
lib/IDE/Refactoring.cpp
Outdated
LastArg = TE->getElements().back(); | ||
} | ||
|
||
while (auto *ICE = dyn_cast<ImplicitConversionExpr>(LastArg)) |
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 check isImplicit()
instead, which seems to be more generic.
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 think your original code makes sense, but can we add it as a member function to ImplicitConversionExpr
?
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.
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).
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 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?
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 agree. Thanks!
lib/IDE/Refactoring.cpp
Outdated
if (Finder.getContexts().empty() | ||
|| !Finder.getContexts().back().is<Expr*>()) | ||
return nullptr; | ||
CallExpr *CE = dyn_cast<CallExpr>(Finder.getContexts().back().get<Expr*>()); |
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 use cast
here? because we're sure it'll succeed.
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.
You are right! 👍
lib/IDE/Refactoring.cpp
Outdated
if (NumArgs > 1) | ||
PrevArg = TE->getElement(NumArgs - 2); | ||
} | ||
while (auto *ICE = dyn_cast<ImplicitConversionExpr>(ClosureArg)) |
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.
Same here; isImplicit()
seems to be more appropriate.
lib/IDE/Refactoring.cpp
Outdated
while (auto *ICE = dyn_cast<ImplicitConversionExpr>(ClosureArg)) | ||
ClosureArg = ICE->getSubExpr(); | ||
|
||
if (!isa<ClosureExpr>(ClosureArg) || LPLoc.isInvalid() || RPLoc.isInvalid()) |
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.
Why do we check !isa<ClosureExpr>(ClosureArg)
? it seems to me we are sure the closure is present.
lib/IDE/Refactoring.cpp
Outdated
SM, | ||
Lexer::getLocForEndOfToken(SM, ClosureArg->getEndLoc()), | ||
Lexer::getLocForEndOfToken(SM, RPLoc)); | ||
EditConsumer.accept(SM, PostRange, ""); |
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 we add a remove
function to SourceEditConsumer
and use it here?
32c01dd
to
fe21f47
Compare
fe21f47
to
a57199c
Compare
@swift-ci Please smoke test |
Looks great! Merging. |
@rintaro LGTM, feel free to merge! |
Thank you @nkcsgexi ! |
Implement SR-5738
As a groundwork, I modifiedCursorInfoResolver
andResolvedCursorInfo
so that it preserves surroundingASTNode
(direct child ofBraceStmt
).Also, introduced
CursorInfoKind::Middle
(naming TBD) which means the cursor is at middle of someting, like:As a ground work, added
SourceFile
toResolvedCursorInfo
struct. Also, letCursorInfoResolver
always setLoc
toResolvedCursorInfo
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 thatCallExpr
hasClosureExpr
as the last argument.