Skip to content

[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

Merged
merged 1 commit into from
Jul 26, 2018

Conversation

mohitathwani
Copy link
Contributor

Enables local refactoring on a class declaration to generate member wise initializer

Resolves SR-7292.

@mohitathwani mohitathwani changed the title Setting up the process to solve SR-7292 [WIP] Generating member wise initializer with local refactoring Jun 4, 2018
@mohitathwani
Copy link
Contributor Author

@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)
Copy link
Contributor

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.

@mohitathwani
Copy link
Contributor Author

mohitathwani commented Jun 6, 2018

TODO List:

  • Test case for situation where no members exist in a class
  • Should work for structs as well
  • Check to see if a member-wise init already exists. If yes, do nothing.
  • Figure out where exactly the generated init should be placed in the source file

@mohitathwani
Copy link
Contributor Author

@harlanhaskins @nkcsgexi Is it safe to assume that this generated initializer should be inserted right after the declaration of the last stored property?

@mohitathwani
Copy link
Contributor Author

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
parentPatternBinding->getEndLoc().getAdvancedLoc(1);

and while coming up with test cases, I see that an input of

class Person {
  var firstName: String!
  var lastName: String!
  let age: Int = 2
  var address: String?
  var a = 3, b = 1234567
}

yields the output

class Person {
  var firstName: String!
  var lastName: String!
  let age: Int = 2
  var address: String?
  var a = 3, b = 1
internal init(firstName: String?, lastName: String?, address: String?) {
self.firstName = firstName
self.lastName = lastName
self.address = address
}
234567
}

Our assumption of getEndLoc() on the pattern binding is not the end of the line. So I'm wondering how to go about determining the location of where to generate the init. Maybe after the opening brace?

@nkcsgexi
Copy link
Contributor

@mohitathwani, Could you remove the relics from previous PR (generate doc-comment boiler template)?

@mohitathwani
Copy link
Contributor Author

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

@mohitathwani
Copy link
Contributor Author

mohitathwani commented Jun 12, 2018

So I tried getting the Stmt from varDecl->getParentPatternStmt() thinking that I would be able to get to the end of the line that contains the pattern binding but it turn's out it returns NULL

@mohitathwani
Copy link
Contributor Author

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

@mohitathwani
Copy link
Contributor Author

@nkcsgexi @harlanhaskins I think I'm read for a smoke test...

@nkcsgexi
Copy link
Contributor

Sure! @swift-ci please smoke test

@mohitathwani
Copy link
Contributor Author

Updated code and all tests passing locally...

SourceManager &SM) {

swift::SourceLoc targetLocation =
classDecl->getBraces().Start.getAdvancedLoc(1);
Copy link
Member

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().

if (!parentPatternBinding) continue;

auto varDeclIndex =
parentPatternBinding->getPatternEntryIndexForVarDecl(varDecl);
Copy link
Member

Choose a reason for hiding this comment

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

Indent

Copy link
Contributor

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.

if (auto classDecl = canGenerateMemberwiseInit(CursorInfo)) {
generateMemberwiseInit(classDecl, EditConsumer, SM);
}
else {
Copy link
Member

@rintaro rintaro Jun 20, 2018

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;


if (auto init = varDecl->getParentPatternBinding()->getInit(varDeclIndex)) {
if (init->getStartLoc().isValid())
continue;
Copy link
Member

Choose a reason for hiding this comment

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

Indent

EditConsumer.accept(SM, targetLocation, "\ninternal init(");

for (size_t i = 0; i < memberTypeVector.size(); i++) {
EditConsumer.accept(SM, targetLocation, memberNameVector[i] + ": " + memberTypeVector[i]);
Copy link
Member

Choose a reason for hiding this comment

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

80 columns, please.

ClassDecl* canGenerateMemberwiseInit(ResolvedCursorInfo CursorInfo) {

ClassDecl *classDecl = dyn_cast<ClassDecl>(CursorInfo.ValueD);
if (!classDecl || classDecl->getStoredProperties().empty()) {
Copy link
Member

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?

Copy link
Contributor

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.

@mohitathwani
Copy link
Contributor Author

@nkcsgexi @rintaro I've incorporated your suggested changes.

@rintaro
Copy link
Member

rintaro commented Jun 22, 2018

@swift-ci Please smoke test

@mohitathwani mohitathwani force-pushed the SR-7292 branch 2 times, most recently from edfe6af to ca62cbc Compare June 22, 2018 15:09
@mohitathwani
Copy link
Contributor Author

@rintaro @nkcsgexi Since all tests passed, I went ahead and squashed all commits in to one

@harlanhaskins
Copy link
Contributor

@swift-ci please smoke test

@mohitathwani
Copy link
Contributor Author

So what's the next step now?

@harlanhaskins
Copy link
Contributor

Seems ready to merge. Deferring to @nkcsgexi

@harlanhaskins
Copy link
Contributor

@nkcsgexi Good to merge?

@nkcsgexi
Copy link
Contributor

@mohitathwani @harlanhaskins I'd like to do another round of review today 😄

@mohitathwani
Copy link
Contributor Author

@nkcsgexi let me know if anything needs to be done from my side...

Copy link
Contributor

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

Great progress, @mohitathwani ! Some structural comments are inline.


std::string memberType = varDecl->getType().getString();
memberTypeVector.push_back(memberType);
}
Copy link
Contributor

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.

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

Copy link
Contributor

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.

bool RefactoringActionMemberwiseInitLocalRefactoring::
isApplicable(ResolvedCursorInfo Tok, DiagnosticEngine &Diag) {
if (Tok.Kind != CursorInfoKind::ValueRef && !Tok.ValueD)
return false;
Copy link
Contributor

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.

@mohitathwani
Copy link
Contributor Author

@nkcsgexi Thanks! Give me a day or two to get this done :)

@mohitathwani
Copy link
Contributor Author

@nkcsgexi Let me know your feedback...

@mohitathwani
Copy link
Contributor Author

@nkcsgexi have you had any chance to look into this?

Copy link
Contributor

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

@mohitathwani some minor comments remain. :)

@@ -2602,6 +2602,108 @@ bool RefactoringActionLocalizeString::performChange() {
return false;
}

void generateMemberwiseInit(SourceEditConsumer &EditConsumer,
Copy link
Contributor

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.

}
}

ClassDecl* collectMembersForInit(ResolvedCursorInfo CursorInfo,
Copy link
Contributor

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.

@@ -2602,6 +2602,108 @@ bool RefactoringActionLocalizeString::performChange() {
return false;
}

void generateMemberwiseInit(SourceEditConsumer &EditConsumer,
SourceManager &SM,
SmallVector<std::string, 5>& memberNameVector,
Copy link
Contributor

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

void generateMemberwiseInit(SourceEditConsumer &EditConsumer,
SourceManager &SM,
SmallVector<std::string, 5>& memberNameVector,
SmallVector<std::string, 5>& memberTypeVector,
Copy link
Contributor

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>&

SourceManager &SM,
SmallVector<std::string, 5>& memberNameVector,
SmallVector<std::string, 5>& memberTypeVector,
swift::SourceLoc targetLocation) {
Copy link
Contributor

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.

if (!parentPatternBinding) continue;

auto varDeclIndex =
parentPatternBinding->getPatternEntryIndexForVarDecl(varDecl);
Copy link
Contributor

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.


for (auto varDecl : classDecl->getStoredProperties()) {
auto parentPatternBinding = varDecl->getParentPatternBinding();
if (!parentPatternBinding) continue;
Copy link
Contributor

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.

auto classDecl = collectMembersForInit(Tok, memberNameVector,
memberTypeVector);

return classDecl && memberTypeVector.size() > 0 &&
Copy link
Contributor

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.

return true;

swift::SourceLoc bracesStart = classDecl->getBraces().Start;
if (!bracesStart.isValid()) return true;
Copy link
Contributor

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.

if (!bracesStart.isValid()) return true;

swift::SourceLoc targetLocation = bracesStart.getAdvancedLoc(1);
if (!targetLocation.isValid()) return true;
Copy link
Contributor

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.

@mohitathwani
Copy link
Contributor Author

@nkcsgexi please have a look when you can :)

bool RefactoringActionMemberwiseInitLocalRefactoring::performChange() {

if (!CursorInfo.ValueD)
return true;
Copy link
Contributor

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.


swift::SourceLoc targetLocation = bracesStart.getAdvancedLoc(1);
if (!targetLocation.isValid())
return true;
Copy link
Contributor

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.

if (!classDecl)
return true;

swift::SourceLoc bracesStart = classDecl->getBraces().Start;
Copy link
Contributor

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.

@mohitathwani
Copy link
Contributor Author

@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 :)

@mohitathwani
Copy link
Contributor Author

@nkcsgexi any updates?

Copy link
Contributor

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

Looking good! One minor comment :)

memberTypeVector.push_back(memberType);
}

return targetLocation;
Copy link
Contributor

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.

Copy link

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())

SmallVectorImpl<std::string>& memberTypeVector,
SourceLoc targetLocation) {

if (memberTypeVector.size() > 0) {
Copy link
Contributor

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());

@mohitathwani
Copy link
Contributor Author

@nkcsgexi Here you go...

@mohitathwani
Copy link
Contributor Author

@nkcsgexi did you have a chance to look at this?

@nkcsgexi
Copy link
Contributor

ah, sorry about dropping this. I'll take another round of review today 😄


EditConsumer.accept(SM, targetLocation, "\ninternal init(");

for (size_t i = 0; i < memberTypeVector.size(); i++) {
Copy link
Contributor

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.


collectMembersForInit(Tok, memberNameVector, memberTypeVector);

return !memberTypeVector.empty();
Copy link
Contributor

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();.

}
}

EditConsumer.accept(SM, targetLocation, ") {\n\n");
Copy link
Contributor

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

"self." + varName + " = " + varName + "\n");
}

EditConsumer.accept(SM, targetLocation, "\n}\n");
Copy link
Contributor

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.

isApplicable(ResolvedCursorInfo Tok, DiagnosticEngine &Diag) {

SmallVector<std::string, 5> memberNameVector;
SmallVector<std::string, 5> memberTypeVector;
Copy link
Contributor

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.

bool RefactoringActionMemberwiseInitLocalRefactoring::performChange() {

SmallVector<std::string, 5> memberNameVector;
SmallVector<std::string, 5> memberTypeVector;
Copy link
Contributor

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
Copy link
Contributor

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().

Copy link
Contributor Author

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?

Copy link
Contributor

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.

Copy link
Contributor Author

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?

Copy link
Contributor Author

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

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 figured out the problem, I was passing the -memberwise-init flag in the RUN command

@mohitathwani
Copy link
Contributor Author

@nkcsgexi whenever you have time :)

Copy link
Contributor

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

Looking great! Only one minor thing before merging.

SmallVectorImpl<std::string>& memberNameVector,
SmallVectorImpl<std::string>& memberTypeVector) {

if (CursorInfo.Kind != CursorInfoKind::ValueRef && !CursorInfo.ValueD)
Copy link
Contributor

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.

Copy link
Contributor Author

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

@nkcsgexi
Copy link
Contributor

@swift-ci please smoke test

@nkcsgexi
Copy link
Contributor

All tests passed! Merging 🚢

@nkcsgexi nkcsgexi merged commit bff3f8a into swiftlang:master Jul 26, 2018
@mohitathwani
Copy link
Contributor Author

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 :)

@nkcsgexi
Copy link
Contributor

@mohitathwani Congrats on your first contribution to Swift! 🎉

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.

5 participants