-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[WIP] Generating member wise initializer with local refactoring #16983
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
Conversation
@harlanhaskins @nkcsgexi I have taken all of my code from the previous PR and moved it to this branch. I figured it would be a lot faster to modify and update code to suit it to solve SR-7292 |
@@ -50,6 +50,8 @@ CURSOR_REFACTORING(ConvertToDoCatch, "Convert To Do/Catch", convert.do.catch) | |||
|
|||
CURSOR_REFACTORING(TrailingClosure, "Convert To Trailing Closure", trailingclosure) | |||
|
|||
CURSOR_REFACTORING(MemberWiseInitLocalRefactoring, "Generate Member Wise Initializer", member.wise.init.local.refactoring) |
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'd use either Memberwise
or Member-wise
. There might even be a better word than that.
TODO List:
|
@harlanhaskins @nkcsgexi Is it safe to assume that this generated initializer should be inserted right after the declaration of the last stored property? |
Thanks @harlanhaskins @nkcsgexi for the help yesterday! I'm working on wrapping up the PR and came across an interesting condition. Yesterday we figured out out the targetLocation of the generated init by using and while coming up with test cases, I see that an input of
yields the output
Our assumption of |
@mohitathwani, Could you remove the relics from previous PR (generate doc-comment boiler template)? |
@nkcsgexi @harlanhaskins atm I'm trying to figure out the line ending of the parent pattern binding so that I can calculate the target location properly... Any pointers? |
So I tried getting the |
@nkcsgexi @harlanhaskins I noticed something interesting today at work. I conformed a class to a protocol and saw that Xcode gave me a auto fix it pop up saying generate protocol stub, I clicked on it and the protocol method stubs were inserted right after the opening brace on the next line. So should I also go down that route? |
@nkcsgexi @harlanhaskins I think I'm read for a smoke test... |
Sure! @swift-ci please smoke test |
Updated code and all tests passing locally... |
lib/IDE/Refactoring.cpp
Outdated
SourceManager &SM) { | ||
|
||
swift::SourceLoc targetLocation = | ||
classDecl->getBraces().Start.getAdvancedLoc(1); |
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.
Indent.
Also, please check getBraces().Start
is isValid()
.
lib/IDE/Refactoring.cpp
Outdated
if (!parentPatternBinding) continue; | ||
|
||
auto varDeclIndex = | ||
parentPatternBinding->getPatternEntryIndexForVarDecl(varDecl); |
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.
Indent
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.
Indentation, as Rintaro mentioned above.
lib/IDE/Refactoring.cpp
Outdated
if (auto classDecl = canGenerateMemberwiseInit(CursorInfo)) { | ||
generateMemberwiseInit(classDecl, EditConsumer, SM); | ||
} | ||
else { |
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 use } else {
style.
Also, we prefer early return. In this case:
auto classDecl = canGenerateMemberwiseInit(CursorInfo)
if (!classDecl)
return true;
generateMemberwiseInit(classDecl, EditConsumer, SM);
return false;
lib/IDE/Refactoring.cpp
Outdated
|
||
if (auto init = varDecl->getParentPatternBinding()->getInit(varDeclIndex)) { | ||
if (init->getStartLoc().isValid()) | ||
continue; |
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.
Indent
lib/IDE/Refactoring.cpp
Outdated
EditConsumer.accept(SM, targetLocation, "\ninternal init("); | ||
|
||
for (size_t i = 0; i < memberTypeVector.size(); i++) { | ||
EditConsumer.accept(SM, targetLocation, memberNameVector[i] + ": " + memberTypeVector[i]); |
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.
80 columns, please.
lib/IDE/Refactoring.cpp
Outdated
ClassDecl* canGenerateMemberwiseInit(ResolvedCursorInfo CursorInfo) { | ||
|
||
ClassDecl *classDecl = dyn_cast<ClassDecl>(CursorInfo.ValueD); | ||
if (!classDecl || classDecl->getStoredProperties().empty()) { |
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.
@nkcsgexi is there a way to determine this cursor is at actually the declaration?
For example, what if typealias Alias = /* cursorinfo here->*/TargetClass
and TargetClass
is declared in other file?
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.
ResolvedCursorInfo.IsRef
indicates whether the cursor is on a reference or a declaration name. I think this refactoring should be only available when ResolvedCursorInfo.IsRef
is false.
@swift-ci Please smoke test |
edfe6af
to
ca62cbc
Compare
@swift-ci please smoke test |
So what's the next step now? |
Seems ready to merge. Deferring to @nkcsgexi |
@nkcsgexi Good to merge? |
@mohitathwani @harlanhaskins I'd like to do another round of review today 😄 |
@nkcsgexi let me know if anything needs to be done from my side... |
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.
Great progress, @mohitathwani ! Some structural comments are inline.
|
||
std::string memberType = varDecl->getType().getString(); | ||
memberTypeVector.push_back(memberType); | ||
} |
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 move this for-loop into canGenerateMemberwiseInit
? Instead of returning ClassDecl*
, canGenerateMemberwiseInit
should populate memberTypeVector
and memberNameVector
. isApplicable
returns true only when these two vectors aren't empty. This way ensures that whenever the refactoring is available, we are going to change the users' codebase after applying it.
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 not clear about this. If I move the said for loop into canGenerateMemberwiseInit
, memberTypeVector
and memberNameVector
will be local to that function. When performChange
is called, it checks if canGeneratememberWiseInit
returns a valid object and then calls generateMemberwiseInit
which needs these vectors to emit the generated initializer. So my question is should we pass in references to the two vectors so that they are available out side the scope of the function and is it okay to have these vectors be populated twice?
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.
@mohitathwani yeah, you're right! We should pass memberTypeVector
and memberTypeVector
as reference to canGenerateMemberwiseInit
(maybe we should rename it to colllectMemberForInit
. isApplicable
will check whether these vectors are empty and performChange
will use these two vectors for actual code change.
lib/IDE/Refactoring.cpp
Outdated
bool RefactoringActionMemberwiseInitLocalRefactoring:: | ||
isApplicable(ResolvedCursorInfo Tok, DiagnosticEngine &Diag) { | ||
if (Tok.Kind != CursorInfoKind::ValueRef && !Tok.ValueD) | ||
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.
We can move this if statement to canGenerateMemberwiseInit
too. isApplicable
should be a simple wrapper of canGenerateMemberwiseInit
.
@nkcsgexi Thanks! Give me a day or two to get this done :) |
@nkcsgexi Let me know your feedback... |
@nkcsgexi have you had any chance to look into this? |
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.
@mohitathwani some minor comments remain. :)
lib/IDE/Refactoring.cpp
Outdated
@@ -2602,6 +2602,108 @@ bool RefactoringActionLocalizeString::performChange() { | |||
return false; | |||
} | |||
|
|||
void generateMemberwiseInit(SourceEditConsumer &EditConsumer, |
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.
mark this function as static.
lib/IDE/Refactoring.cpp
Outdated
} | ||
} | ||
|
||
ClassDecl* collectMembersForInit(ResolvedCursorInfo CursorInfo, |
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.
mark this function as static.
lib/IDE/Refactoring.cpp
Outdated
@@ -2602,6 +2602,108 @@ bool RefactoringActionLocalizeString::performChange() { | |||
return false; | |||
} | |||
|
|||
void generateMemberwiseInit(SourceEditConsumer &EditConsumer, | |||
SourceManager &SM, | |||
SmallVector<std::string, 5>& memberNameVector, |
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.
Instead of using SmallVector<std::string, 5>&
, use SmallVectorImpl<std::string>&
.
lib/IDE/Refactoring.cpp
Outdated
void generateMemberwiseInit(SourceEditConsumer &EditConsumer, | ||
SourceManager &SM, | ||
SmallVector<std::string, 5>& memberNameVector, | ||
SmallVector<std::string, 5>& memberTypeVector, |
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, use SmallVectorImpl<std::string>&
lib/IDE/Refactoring.cpp
Outdated
SourceManager &SM, | ||
SmallVector<std::string, 5>& memberNameVector, | ||
SmallVector<std::string, 5>& memberTypeVector, | ||
swift::SourceLoc targetLocation) { |
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 should not need swift::
here.
lib/IDE/Refactoring.cpp
Outdated
if (!parentPatternBinding) continue; | ||
|
||
auto varDeclIndex = | ||
parentPatternBinding->getPatternEntryIndexForVarDecl(varDecl); |
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.
Indentation, as Rintaro mentioned above.
lib/IDE/Refactoring.cpp
Outdated
|
||
for (auto varDecl : classDecl->getStoredProperties()) { | ||
auto parentPatternBinding = varDecl->getParentPatternBinding(); | ||
if (!parentPatternBinding) continue; |
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.
Move continue;
to the next line with proper indentation.
lib/IDE/Refactoring.cpp
Outdated
auto classDecl = collectMembersForInit(Tok, memberNameVector, | ||
memberTypeVector); | ||
|
||
return classDecl && memberTypeVector.size() > 0 && |
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 need to check !memberTypeVector .empty()
here to answer the isApplicable
question.
lib/IDE/Refactoring.cpp
Outdated
return true; | ||
|
||
swift::SourceLoc bracesStart = classDecl->getBraces().Start; | ||
if (!bracesStart.isValid()) return true; |
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.
Move return true;
to a new line.
lib/IDE/Refactoring.cpp
Outdated
if (!bracesStart.isValid()) return true; | ||
|
||
swift::SourceLoc targetLocation = bracesStart.getAdvancedLoc(1); | ||
if (!targetLocation.isValid()) return true; |
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.
Move return true; to a new line.
@nkcsgexi please have a look when you can :) |
lib/IDE/Refactoring.cpp
Outdated
bool RefactoringActionMemberwiseInitLocalRefactoring::performChange() { | ||
|
||
if (!CursorInfo.ValueD) | ||
return true; |
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.
These two lines aren't necessary. collectMembersForInit
should check it.
lib/IDE/Refactoring.cpp
Outdated
|
||
swift::SourceLoc targetLocation = bracesStart.getAdvancedLoc(1); | ||
if (!targetLocation.isValid()) | ||
return true; |
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.
Move these two source loc checks into collectMembersForInit
and return targetLocation
instead of classDecl
from collectMembersForInit
. The behavior should be a non-empty memberNameVector
implies valid location for inserting the initializer and we're sure to perform change in users' code.
lib/IDE/Refactoring.cpp
Outdated
if (!classDecl) | ||
return true; | ||
|
||
swift::SourceLoc bracesStart = classDecl->getBraces().Start; |
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 don't need swift::
here and other places.
@nkcsgexi Pushed latest changes... @dndydon Thanks for your encouragement and your company at the Swift summit. I would recommend you read this and check out my branch on your local machine and follow along the changes I've made. I'm not sure if there are any more refactoring tools to be built, but if @nkcsgexi has some more ideas then I am happy to collaborate :) |
@nkcsgexi any updates? |
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.
Looking good! One minor comment :)
lib/IDE/Refactoring.cpp
Outdated
memberTypeVector.push_back(memberType); | ||
} | ||
|
||
return targetLocation; |
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 ensure we return invalid SourceLoc
if memberNameVector
or memberTypeVector
is empty? Currently, we may return valid location even if we don't need to insert anything.
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 a bit confused by one line in:
bool RefactoringActionMemberwiseInitLocalRefactoring::performChange()
.
This line: if (targetLocation.isInvalid())
I think should be: if (targetLocation.isApplicable())
lib/IDE/Refactoring.cpp
Outdated
SmallVectorImpl<std::string>& memberTypeVector, | ||
SourceLoc targetLocation) { | ||
|
||
if (memberTypeVector.size() > 0) { |
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.
Address the previous comment and we don't need to check memberTypeVector
isn't empty here. Instead, we can add:
assert(!memberTypeVector.empty());
assert(memberTypeVector.size() == memberNameVector.size());
@nkcsgexi Here you go... |
@nkcsgexi did you have a chance to look at this? |
ah, sorry about dropping this. I'll take another round of review today 😄 |
lib/IDE/Refactoring.cpp
Outdated
|
||
EditConsumer.accept(SM, targetLocation, "\ninternal init("); | ||
|
||
for (size_t i = 0; i < memberTypeVector.size(); i++) { |
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.
Change this line to for (size_t i = 0, n = memberTypeVector.size(); i < n; i++) {
. We don't need to call size()
every time we evaluate the loop condition.
lib/IDE/Refactoring.cpp
Outdated
|
||
collectMembersForInit(Tok, memberNameVector, memberTypeVector); | ||
|
||
return !memberTypeVector.empty(); |
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 can merge these two lines to: return collectMembersForInit(Tok, memberNameVector, memberTypeVector).valid();
.
lib/IDE/Refactoring.cpp
Outdated
} | ||
} | ||
|
||
EditConsumer.accept(SM, targetLocation, ") {\n\n"); |
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 need one "\n" here
lib/IDE/Refactoring.cpp
Outdated
"self." + varName + " = " + varName + "\n"); | ||
} | ||
|
||
EditConsumer.accept(SM, targetLocation, "\n}\n"); |
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 don't need the first "\n" in the string literal.
lib/IDE/Refactoring.cpp
Outdated
isApplicable(ResolvedCursorInfo Tok, DiagnosticEngine &Diag) { | ||
|
||
SmallVector<std::string, 5> memberNameVector; | ||
SmallVector<std::string, 5> memberTypeVector; |
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.
Use SmallVector<std::string, 8>
instead of SmallVector<std::string, 5>
here and other places.
lib/IDE/Refactoring.cpp
Outdated
bool RefactoringActionMemberwiseInitLocalRefactoring::performChange() { | ||
|
||
SmallVector<std::string, 5> memberNameVector; | ||
SmallVector<std::string, 5> memberTypeVector; |
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.
Use SmallVector<std::string, 8> instead of SmallVector<std::string, 5> here and other places.
|
||
// RUN: %empty-directory(%t.result) | ||
// RUN: %refactor -memberwise-init -source-filename %s -pos=1:8 > %t.result/class_members.swift | ||
// RUN: diff -u %S/Outputs/class_members/class_members.swift.expected %t.result/class_members.swift |
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 you also add an applicable test in test/refactoring/RefactoringKind
? This refactoring shouldn't be available in value reference cases, like when cursor points to Person in _ = Person()
.
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.
@nkcsgexi should this test be written in basic.swift
file in /RefactoringKind/
or should I create a new file?
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 a new test file will be better.
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.
@nkcsgexi So I have a file
class Person {
var firstName: String!
var lastName: String!
var age: Int!
var planet = "Earth", solarSystem = "Milky Way"
var avgHeight = 175
}
let _ = Person()
// RUN: %refactor -memberwise-init -source-filename %s -pos=9:10 | %FileCheck %s -check-prefix=CHECK-NONE
// CHECK-NONE: Action begins
// CHECK-NONE-NEXT: Action ends
and this seems to be causing an assertion failure:
Assertion failed: (RealOffset+Size <= Buffer.size() && "Invalid location"), function RemoveText, file /Users/Labs/Documents/swift-source/llvm/tools/clang/lib/Rewrite/Rewriter.cpp, line 55.
Any idea what's going on? Am I missing some LLVM command or something?
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.
@nkcsgexi the above assertion seems to be failing for the the extract.swift
file in /RefactoringKind/
as well...
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 figured out the problem, I was passing the -memberwise-init
flag in the RUN
command
@nkcsgexi whenever you have time :) |
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.
Looking great! Only one minor thing before merging.
lib/IDE/Refactoring.cpp
Outdated
SmallVectorImpl<std::string>& memberNameVector, | ||
SmallVectorImpl<std::string>& memberTypeVector) { | ||
|
||
if (CursorInfo.Kind != CursorInfoKind::ValueRef && !CursorInfo.ValueD) |
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 don't need the first condition. if (!CursorInfo.ValueD)
is sufficient 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.
@nkcsgexi made the change and squashed the commits
@swift-ci please smoke test |
All tests passed! Merging 🚢 |
Thanks to @harlanhaskins @nkcsgexi for helping me out and guiding me with this PR and @rintaro and @dndydon for your code reviews... Appreciate all the help I received :) |
@mohitathwani Congrats on your first contribution to Swift! 🎉 |
Enables local refactoring on a class declaration to generate member wise initializer
Resolves SR-7292.