-
Notifications
You must be signed in to change notification settings - Fork 333
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
Infer method #6877
base: main
Are you sure you want to change the base?
Infer method #6877
Conversation
metals/src/main/scala/scala/meta/internal/metals/codeactions/CreateNewSymbol.scala
Show resolved
Hide resolved
/** | ||
* Return the missing method | ||
*/ | ||
public abstract CompletableFuture<List<TextEdit>> insertInferredMethod(OffsetParams params); |
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 reuse the codeAction interface, it should be universal for all actions
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.
is there an example by any chance? Otherwise I will pick this up at the end, after making sure the cases I handled in the tests work running interactively (not all do yet!)
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 was supposed to quickly create an example, but then figured we can refactor the old methods in #6881
It's more complicated for the older action, but for anything new(as soon as my PR is merged) it should be as easy as:
- Add a new CodeActionId string
- Implement maybeCodeActionId from CodeAction trait
- Add handling of new id to codeAction method
- Add id to list of supported ones
- Use codeAction method from compilers in handleCommand
Feel free to work on the meat of the issue in the compiler, we can get back to it later or I can fix that myself.
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.
right, I seem to have achieved what I wanted! Things are working now 🌞
metals/src/main/scala/scala/meta/internal/metals/codeactions/CreateNewSymbol.scala
Show resolved
Hide resolved
@tgodzik I have managed to handle the issues I experienced by running my feature interactively. Meanwhile, I have a question: but for debugging the issue I need the pprint.log of the tree and the position and possibly the original code. In my experience I have had a bunch of issues with inferType and now I see why the metals.log didn't have any hints about what caused them (not necessarily the state of my project, but possibly because not all cases are handled xD) |
@tgodzik also: And could I get a hint on how to pass the MiMa action? I can fix the scalafix one. What else is needed to merge this in? |
For sure it's always better to add reports, we do have a utility for that with ReportContext and we can add any data there. Also, we should make sure
I would think workspace edit opens a file, but maybe it's a problem with uri? I can check |
We should be ok once we use the codeAction interface. I can update it all before merging |
I can't seem to make it work at all with |
you were right, there was an issue with me not handling |
Found out that this issue happens only when I restart metals. Editing a source without an open buffer works fine on a fresh session. Thanks! |
Okay, I am removing the draft from this PR then. |
Is it ok if I push the changes related to the new codeAction inteface? That's now merged to main |
sure, please go ahead: I am planning to only change the codebase for review feedback for now |
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 tighten the scope of this PR I think, code action like this should really only work within a single file as otherwise it will become much more complex.
@@ -158,211 +159,215 @@ public abstract CompletableFuture<List<AutoImportsResult>> autoImports(String na | |||
public abstract CompletableFuture<List<TextEdit>> insertInferredType(OffsetParams params); | |||
|
|||
/** | |||
* Return the text edits for inlining a value. |
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.
Any chance not to reformat the file? It makes it difficult to see what actually changed
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.
oh, I think metals github action runs automatically before push (I had to apply to be able to push). Maybe you can try difftastic or ignore whitespaces in PR
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.
It's fine, but I think the formatting was done by the editor actually, but maybe we never run any formatting on it before 🤔 Would probably be cleaner to have that as a separate commit because otherwise any blame in the file will point at you change 😅
Nil | ||
} | ||
// nonExistent(param1, param2) | ||
// val a: Int = nonExistent(param1, param2) TODO |
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.
Is it still TODO?
} | ||
|
||
val uriTextEditPairs = typedTree match { | ||
// case errorMethod if errorMethod.toString().contains("lol") => |
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.
Might be worth removing any comments that no longer make sense
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.
sorry, I tired myself out while reiterating over this change.
I will take a little time to address your feedback: thanks for that!
Ident(containingMethod), | ||
arguments | ||
) :: _ => | ||
val methodSymbol = context.lookupSymbol(containingMethod, _ => 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.
Would be able to extract some methods? Might be easier to review
paramsString = argumentsString(arguments).getOrElse(""), | ||
retType = None, | ||
postProcess = method => { | ||
if ( |
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 like this could be extracted, isn't it the same as below and above?
mtags/src/main/scala-2/scala/meta/internal/pc/InferredMethodProvider.scala
Outdated
Show resolved
Hide resolved
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 work! I added a few comments.
There also seems to be an unhandled case:
ObjectName.otherMethodWithoutParens
we do show the code action but fail to do anything. We should either not show the method (we can use trees for that and not show if we are in a select statement without and apply parent) or handle it in the compiler
metals/src/main/scala/scala/meta/internal/metals/ServerCommands.scala
Outdated
Show resolved
Hide resolved
metals/src/main/scala/scala/meta/internal/metals/codeactions/CreateNewSymbol.scala
Outdated
Show resolved
Hide resolved
/** | ||
* Return the missing method | ||
*/ | ||
public abstract CompletableFuture<WorkspaceEdit> insertInferredMethod(OffsetParams params); |
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.
This should have default implementation for Mima to pass, but I will change it later on to use codeAction, so don't worry about 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.
yes please, I haven't dived in the codeAction method sure that it would be easier for you to adapt.
tests/cross/src/test/scala/tests/pc/InsertInferredMethodSuite.scala
Outdated
Show resolved
Hide resolved
|
||
// doesn't work currently, User(1) is not being typed | ||
checkEdit( | ||
"custom-type-advanced".ignore, |
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.
TODO -> reminder to create an issue when we merge
mtags/src/main/scala-2/scala/meta/internal/pc/InferredMethodProvider.scala
Show resolved
Hide resolved
mtags/src/main/scala-2/scala/meta/internal/pc/InferredMethodProvider.scala
Outdated
Show resolved
Hide resolved
.asInstanceOf[NullaryMethodType] | ||
.resultType | ||
else containerSymbol.symbol.tpe) match { | ||
case TypeRef(_, classSymbol, _) => |
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.
Should we also make sure it's the same file 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.
not necessary because the check is in makeClassMethodEdits
which I made sure to call for each case that infers a method.
// this gives me the position of the class | ||
typedTreeAt(containerClass.symbol.pos) match { | ||
// class case | ||
case ClassDef( |
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 use ImplDef superclass for both ClassDef and ModuleDef?
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.
it seems a good idea but a simple ClassDef -> ImplDef didn't compile.
I will look into the other issues you have highlighted since this feels minor and requires digging time.
*/ | ||
private def inferEditPosition(t: Template): Position = { | ||
// get text via reflection because Template could be in a different file | ||
val text = t.pos.source.content.map(_.toString).mkString |
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.
Since we only support single file we can just use text from the OffsetParams
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 tested things a bit more and looks a couple of cases are not tested and do not work. I think overall we should reduce the cases handled to 4:
otherMethod(errorMethod(param))
errorMethod(param)
ident.errorMethod(param)
method(errorMethod)
with no select
anything else seems very hard to handle and we can make this PR more focused. We can also make sure that this code action only shows up for these cases.
I hope you are not too discouraged. This is a pretty hard one, which is why I probably abandoned this after the initial tries.
Let me know if you need help, I can help rework this PR a bit
arguments | ||
) :: _ => | ||
makeEditsForListApply(arguments, containingMethod, errorMethod) | ||
// List(1,2,3).map(myFn) |
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.
This and the one below does not work unfortunately, it's not really possible to get a type of List(1,2,3).map the current implementation will just produce:
def otherMethod(arg0: Int, arg1: Int, arg2: Int) = ???
which is not valid. We can't even know how many parameters it will have. And also this might be a much more complex expression.
position = None | ||
) | ||
// List(1, 2, 3).map(nonExistent) | ||
// List((1, 2)).map{case (x,y) => otherMethod(x,y)} |
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.
Is this actually the snippet this handles? Looks like this is a simple apply containingMethod(arguments)
The same as the one above
I don't see any check for being inside another method.
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.
yes, this is handling both List cases: both the non argument case, and the unpacking case
It is alright: it is a mix of I had more time before because I was on holiday and lost a bit of enthusiasm because the action will not be as useful as I needed it to be. I am happy to wrap the PR up with your feedback though: I will reduce the cases and polish, possibly during the weekend. |
@tgodzik it turned out that handling the |
some tests that I believe are unrelated are failing. Shall look into fixing those once I push to my fork.
It now handles adding methods to class and object, lambda-0 case, map(nonExistent) with and without arguments fix: make action handleCommand with infer method also select action on the ScalacDiagnostic.NotAMember fix issues found by running interactively with scala 2.13.15 move from TextEdit to WorkspaceEdit to allow edit in other files than current when adding a file to a class, we may have the class imported but defined in another file. TextEdit only works for current edits, while we need to add the method stub in the other file. WorkspaceEdit seems to be the way to go here. satisfy scalafix fix issues with action for class methods and extra curly brackets remove unnecessary java formatting
this is to restrict the scope of this action, since it is unclear what would happen for defining method for libraries we are using
Co-authored-by: Tomasz Godzik <[email protected]>
logger.warning( | ||
s"This case ($c) is not currently supported by the infer-method code action." | ||
) | ||
throw new RuntimeException() |
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 use DisplayableException
similar to inlineValue
does it.
signature( | ||
name = errorMethod.name.toString(), | ||
paramsString = | ||
Option(argumentsString(arguments.take(1)).getOrElse("")), |
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.
What if we have a method like:
object DoubleList{
def apply(a : Int) = List( (a,b) )
}
class DoubList{
def map(f: (Int, Int) => String) = ???
}
DoubleList(1).map(nonExistent)
would create a wrong method. We can't know what is the type used in a method based on the arguments of the preceding apply.
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.
A very hacky way around it would be to create a function that take in everything like
def myErrorFunction(a: Any) : Nothing
and with that rerun the presentation compiler to get the actual type of map. We do a similar thing with updating inferred type : https://github.com/scalameta/metals/blob/main/mtags/src/main/scala-2/scala/meta/internal/pc/InferredTypeProvider.scala#L89C9-L89C26
But maybe we could do it later, for now I would remove these cases and maybe document everything unsupported in a separate issue?
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.
ignored the cases, thanks
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.
Thanks!
unimplemented("other-than-method") | ||
} | ||
case _ => | ||
unimplemented("other-than-ident-or-select") |
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 could add better explanations here and use the DisplayableError which would then show up for the users.
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.
this code is just a reference to find it back in code, I will make the logger into the DisplayableError.
I am unsure what would be a better explanation than saying "the case x is not currently supported for the action".
There could be many cases unsupported for these bits (it would take time to list them and not sure how that would help a user who is likely just disappointed things don't work 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.
I think it's enough to make it into a sentence, so that it's clear what is not supported.
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.
okay I applied the changes.
@tgodzik I think I have addressed all the comments. I think the last step is for you to address the MiMa issue and merge it when it is ready, no? |
I think we are almost ready, I will add some final touches next week most likely related to the code actions interface. Thanks for your hard work! |
This attempts to increase the capabilities of the "Create New Symbol" action to allow new method definition.
Supported only Scala 2 for cases shown in tests: tests/cross/src/test/scala/tests/pc/InsertInferredMethodSuite.scala
Related discussion and feature request: scalameta/metals-feature-requests#298