-
Notifications
You must be signed in to change notification settings - Fork 395
Code action for-comprehension: support overlapping param names #7784
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
base: main
Are you sure you want to change the base?
Conversation
A chain of map/flatMap can reuse the same name, e.g. using subsequent `.map(x => x + 1)`. After converting to for-comprehension, those duplicate names need to be renamed. This PR tries to handle some common usages (like pattern matching, anonymous functions), but some are complex enough to justify to not support them, and the code action just gives up. Resolves scalameta#4069
cac3c4d to
7c68f7b
Compare
tgodzik
left a comment
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 for contributing! Do you have a bit of time to work on it still? We could split the work to simplify the code action first, which might help to do the further work.
|
|
||
| import scala.meta.internal.metals.codeactions.FlatMapToForComprehensionCodeAction | ||
| import scala.meta.internal.metals.codeactions.RewriteBracesParensCodeAction | ||
| import scala.meta.internal.metals.codeactions.{ |
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 need to run sbt scalafixAll which should fix the imports and the scalafix check
tests/unit/src/test/scala/tests/codeactions/FilterMapToCollectCodeActionSuite.scala
Show resolved
Hide resolved
| nameGenerator: MetalsNames, | ||
| ): (List[Enumerator], Option[Term]) = { | ||
| val perhapsValueNameAndNextQual = termApply.args.headOption.flatMap { | ||
| def perhapsValueNameAndNextQual = termApply.args.headOption.flatMap { |
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 change needed?
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.
No, but I think it is a good change - the third case of the code below (case _ => // there is no interesting function in this termApply) does not use this value, so I think it is a good idea to defer walking the remaining tree until needed. The method is called only once.
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.
lazy val then maybe in case it's used more times in the future?
| ) | ||
| } else | ||
| Enumerator.Val( // when it is map | ||
| Enumerator.Val( // when it is map, |
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.
| Enumerator.Val( // when it is map, | |
| Enumerator.Val( // when it is map |
| case Term.Function(List(param), term) => | ||
| Some(Pat.Var(Term.Name(param.name.value)), term) | ||
| if (nameGenerator.isNameEncountered(param.name.value)) { | ||
| replaceNameInTermWithNewName(term, nameGenerator, param.name) |
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 would need to do something like (this is just an idea, not really an exact code to use.)
| replaceNameInTermWithNewName(term, nameGenerator, param.name) | |
| compilers.references( | |
| new l.ReferenceParams( | |
| textDocument, | |
| tree.pos.toLsp.getStart(), | |
| new l.ReferenceContext(false), | |
| ), | |
| EmptyCancelToken, | |
| noAdjustRange, | |
| ) |
Looks like we should use compilers.references instead, which can give us semantically correct references to the parameter. We can later check if a given Term.Name position is included in the results.
The compilcation is:
- we would want to do that in resolveCodeAction as we don't want to run it every time we click on a for comprehension, that would require a bit of a refactor. We want to first identify that a code action makes sense here and only then run it.
- compiler.references uses futures, which is fine for resolveCodeAction, but then we need to push Future to all the methods, which becomes complicated.
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 runs once per map/flatMap. Can compilers.references be used once to find all duplicates in their separate scopes, or it needs to be run multiple times?
If I understand you correctly, method replaceNameInTermWithNewName would still be used, the difference would be detection of naming scopes - instead of nameGenerator.isNameEncountered(param.name.value) probably
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.
replaceNameInTermWithNewName can just be much simpler, since we only need to search for Term.Name that has a position included in the results
compilers.references would need to be called for each name separately.
|
We could potentially move all the code to resolveCodeAction, but add codeAction method that would just check if we are in aplicable filter.map chain |
A chain of map/flatMap can reuse the same name, e.g. using subsequent
.map(x => x + 1). After converting to for-comprehension, those duplicate names need to be renamed.This PR tries to handle some common usages (like pattern matching and anonymous functions), but some are complex enough to justify not supporting them, and the code action just gives up.
See the test cases for supported examples where the renaming works.
Resolves #4069