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

Code action: convert type to module #532

Open
wants to merge 9 commits into
base: master
Choose a base branch
from

Conversation

aspeddro
Copy link
Contributor

@aspeddro aspeddro commented Jul 29, 2022

Add basic support to convert type to module.

  • Support types inside module?

Current state:

  • Does not support types inside module
  • Does not support recursive types.

TODO:

  • Update changelog

Demo:

screencast-2022-07-29_20.43.26.mp4

Close #430

Copy link
Collaborator

@zth zth left a comment

Choose a reason for hiding this comment

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

Awesome! 😄 I'll let @cristianoc look at the OCaml parts more in depth. I noticed there's a few refactors - might be good to add a comment for each of those and detail what they do/why.

@@ -110,9 +110,20 @@ module IfThenElse = struct
| Some newExpr ->
let range = rangeOfLoc newExpr.pexp_loc in
let newText = printExpr ~range newExpr in
let uri = Uri.fromPath path |> Uri.toString in
Copy link
Contributor Author

Choose a reason for hiding this comment

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

To return uri scheme.

Copy link
Collaborator

@cristianoc cristianoc Aug 4, 2022

Choose a reason for hiding this comment

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

Forgot why this does not just return the string it starts with?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Was breaking code actions in neovim client because it was not returning uri scheme from LSP spec.

Example: Add type annotation
image

[{"title": "Add type annotation", "kind": "refactor.rewrite", "edit": {"documentChanges": [{
  "textDocument": {
  "version": null,
  "uri": "/home/pedro/Desktop/learning-rescript/src/intro/Codeaction.res"
  },
  "edits": [{
  "range": {"start": {"line": 20, "character": 5}, "end": {"line": 20, "character": 5}},
  "newText": ": int"
  }]
  }]}}]

In vscode it works without problem

Copy link
Collaborator

Choose a reason for hiding this comment

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

Sorry what I meant to ask is: what does this composition of functions do?
Let's put it into a single function with a name suggesting what it does, and use that function instead. So it's less likely that the same issue will pop up somewhere else by copy paste and change in future.

@cristianoc
Copy link
Collaborator

Haven't looked at the ocaml code that -- can do that soon.
I was first wondering about the big picture, perhaps @zth can help a bit there.
Trying to gauge what are going to be the actions of this kind supported in future, and how they play well with each other.
If we're just experiments and are happy to add/remove these kinds of things freely, then we can just go ahead, mark as experimental etc.

The questions that immediately come to mind are: is this too narrow a use case? Or is it general enough?
If we have 8 of these little things, how noisy is the UI going to get when lots of pieces of code trigger several actions etc.

So my question at the moment is: should we worry about these aspects already? Or just experiment first with a few features see how they feel and how they are used, knowing we might revise perhaps even heavily later?

@cristianoc
Copy link
Collaborator

cristianoc commented Jul 31, 2022

Also, should we have an inverse action? To move something out of a module?

@cristianoc
Copy link
Collaborator

Some usage questions: this does not seem to trigger an action:

type rec tree = Node({name: string, leaves: leaves}) | Empty
and leaves = {number: int, entries: array<tree>}

@cristianoc
Copy link
Collaborator

cristianoc commented Jul 31, 2022

And a bigger question: the most time consuming part is going around fix all the references to the type.
Should an attempt be made to automate this? As currently we automate a simple copy-paste.

@cristianoc
Copy link
Collaborator

cristianoc commented Jul 31, 2022

Notice we already have renaming working well across files, so fixing references might not be super difficult. Perhaps. One would have to try.

@cristianoc
Copy link
Collaborator

Just as a simple test.

Before:

type myType = This | That

let fun1 = (x: myType) => x

let fun2 = b => b ? This : That

Ohhh I was surprised to see that half of this is done automatically already:

module MyType = {
  type t = This | That
}

let fun1 = (x: MyType.t) => x

let fun2 = b => b ? This : That

What's left is the final step:

module MyType = {
  type t = This | That
}

let fun1 = (x: MyType.t) => x

let fun2 = b => b ? MyType.This : That

And one might or might not want to try to automate the final step.

@cristianoc
Copy link
Collaborator

Naming: should this be called "move type definition into its own module"?

@aspeddro
Copy link
Contributor Author

aspeddro commented Aug 3, 2022

Now support variant/record/object

type state = New | Unread | Read
type refState = state
type person = {"age": int, "name": string}
type user = {
  name: string,
  age: int,
}
and response = Yes | No

type myType = This | That

let fun1 = (x: myType) => x
let fun2 = b => b ? This : That
let fun3 = b => b ? {name: "Lhs", age: 2} : {name: "Rhs", age: 3}
let fun4 = b => b ? Yes : No
let me: person = {
  "age": 5,
  "name": "Big ReScript",
}
module State = {
  type t = New | Unread | Read
}
module RefState = {
  type t = State.t
}
module Person = {
  type t = {"age": int, "name": string}
}
module User = {
  type t = {
    name: string,
    age: int,
  }
  and response = Yes | No
}
module MyType = {
  type t = This | That
}

let fun1 = (x: MyType.t) => x
let fun2 = b => b ? MyType.This : That
let fun3 = b => b ? {User.name: "Lhs", age: 2} : {User.name: "Rhs", age: 3}
let fun4 = b => b ? User.Yes : No
let me: Person.t = {
  "age": 5,
  "name": "Big ReScript",
}

analysis/src/Xform.ml Outdated Show resolved Hide resolved
@cristianoc
Copy link
Collaborator

Looks like this is making steady progress.
Keep on going, and feel free to ping when more feedback is required.

@zth
Copy link
Collaborator

zth commented Aug 3, 2022

I haven't forgotten about the high level questions @cristianoc , will get back with them soon.

@zth
Copy link
Collaborator

zth commented Aug 3, 2022

Here are a few thoughts from me:

  • I'm not worried about having too many code actions at this point. That's primarily because I think the way code actions are used is more about learning that certain actions exist in certain contexts, and then using the ones who fit the workflow you personally like. This might be a bit different if we end up having a lot of code actions, and I don't think we should have a bunch of borderline useless actions just for having actions. But at the point we're at now, with code actions in the extension being in its infancy, I think we can afford to experiment some. Even if that means removing things eventually.
  • For this particular code action, I'd like to ask ourselves this - is this a code pattern we'd recommend (modules with a type t to encapsulate related types/functions etc)? Personally, I'm inclined to say yes. t is a pretty strong convention, and also what pipe autocompletion is based on. If this is a pattern we recommend, making it as easy as possible to work with is a goal imo. And for this particular pattern, one of the annoying things of moving a type to its own module is the refactoring part, which this action goes some (all?) of the way to solve.

Thoughts?

@cristianoc
Copy link
Collaborator

Agreed.
Just one small comment. Recommendation sounds a bit strong. In that you don't want a bureaucratic one module per type.
But, there are many cases where it makes sense.
So perhaps a bit more nuanced.

@zth
Copy link
Collaborator

zth commented Aug 3, 2022

Agreed. Just one small comment. Recommendation sounds a bit strong. In that you don't want a bureaucratic one module per type. But, there are many cases where it makes sense. So perhaps a bit more nuanced.

Yes, I agree. And I only really mean using t as the main type of a module. Anyways, with that said I think going ahead with this would be nice. Again, we can always call it experimental to start with.

@cristianoc
Copy link
Collaborator

Great. Let's move on with this.
Seems pretty much ready already.

@aspeddro
Copy link
Contributor Author

aspeddro commented Aug 3, 2022

It's ready for review.

Copy link
Collaborator

@cristianoc cristianoc left a comment

Choose a reason for hiding this comment

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

This looks great.
Left some comments, nothing substantial.
There's a "ready for review" comment deleted so not sure if more changes are planned.


let xform ~path ~pos ~codeActions ~printStructureItem structure ~debug =
let result = ref None in
let newTypeName = "t" in
Copy link
Collaborator

Choose a reason for hiding this comment

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

Feels like this logic should be factored out into its own easy to modify little local module.

| Some (newStructureItem, references, modName) ->
let range = rangeOfLoc newStructureItem.pstr_loc in
let newText = printStructureItem ~range newStructureItem in
let uri = Uri.fromPath path |> Uri.toString in
Copy link
Collaborator

Choose a reason for hiding this comment

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

Another instance of this.
If this does something useful, it should probably be moved into a standalone function inside Uri.

@@ -310,5 +462,7 @@ let extractCodeActions ~path ~pos ~currentFile ~debug =
AddTypeAnnotation.xform ~path ~pos ~full ~structure ~codeActions ~debug;
IfThenElse.xform ~pos ~codeActions ~printExpr ~path structure;
AddBracesToFn.xform ~pos ~codeActions ~path ~printStructureItem structure;
TypeToModule.xform ~path ~pos ~codeActions ~printStructureItem structure
Copy link
Collaborator

Choose a reason for hiding this comment

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

We have quite a few of these.
Need a little sanity check:

  • that they don't do repeat work in a way that hurts perf (iterating several times is probably ok while copying would not)
  • that no work is done unless the action fires

This is just about reviewing the code and check that things are OK.

@aspeddro
Copy link
Contributor Author

aspeddro commented Aug 6, 2022

It will take some time to get ready. There are more cases I need to check.

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.

Feature request: Code action to convert type to module
3 participants