-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Add error code to diagnostics about unused code #19780
Conversation
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.
The CI failed because you skipped source position in imports report method.
Also, please rebase the PR as the org has changed to scala/scala3
I think that next step could be defining the rest of the quick actions.
case class UnusedSymbol(pos: SrcPos, name: Name, warnType: WarnTypes) | ||
enum UnusedSymbol: | ||
case Symbol(tree: tpd.NamedDefTree, warnType: WarnTypes) | ||
case ImportSelector(pos: SrcPos, name: 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.
Do we really need to distinguish them ?
Maybe we could model it more like
case class UnusedSymbol(defnTree: SrcPos, namePos: SrcPos, warnType: WarnTypes)
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 agree that it is simpler that way but here is why we might want to pass the whole tree rather than the pair of SrcPos
(at least for imports). The thing is that with imports we have bunch of different cases:
- single, regular import
import java.io.File
- multiple import
import java.io.{File, FileDescriptor}
- import with rename
import java.io.{File as JFile}
- multiple imports with renames
In case of a multiple import there might be a case when only one of the selectors should be removed. This means going from: import java.io.{File, FileDescriptor}
to import java.io.{File}
.
With the tree passed there one could do some better analysis in the method that will create a removal action (by e.g. pattern matching).
def imports(pos: SourcePosition)(using Context): UnusedSymbol= new UnusedSymbol(pos, i"unused import", List.empty) | ||
def localDefs(tree: tpd.NamedDefTree)(using Context): UnusedSymbol = new UnusedSymbol(tree.namePos, i"unused local definition", createRemoveLocalDefAction(tree)) | ||
def explicitParams(tree: tpd.NamedDefTree)(using Context): UnusedSymbol = new UnusedSymbol(tree.namePos, i"unused explicit parameter", List.empty) | ||
def implicitParams(tree: tpd.NamedDefTree)(using Context): UnusedSymbol = new UnusedSymbol(tree.namePos, i"unused implicit parameter", List.empty) | ||
def privateMembers(tree: tpd.NamedDefTree)(using Context): UnusedSymbol = new UnusedSymbol(tree.namePos, i"unused private member", List.empty) | ||
def patVars(tree: tpd.NamedDefTree)(using Context): UnusedSymbol = new UnusedSymbol(tree.namePos, i"unused pattern variable", List.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.
This is note to myself. It leads to at least 4 different scenarios.
- patVars should rename the variable to
_
, - privateMembers, localDefs, explicitParams can just remove whole definition site,
- implicitParams may require removal of parentheses used to define them if we remove the last case,
- imports should also remove whole import line when we remove last import selector
ef56f16
to
b11265a
Compare
| 123 | ||
| } | ||
|""".stripMargin , | ||
afterPhase = "checkUnusedPostInlining" |
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.
thought: In theory there are also other cases with variable definitions e.g.:
val List(a, b) = List(1, 2)
but they are not being detected at the moment
def explicitParams(treePos: SourcePosition)(using Context): UnusedSymbol = new UnusedSymbol(treePos, i"unused explicit parameter", List.empty) | ||
def implicitParams(treePos: SourcePosition)(using Context): UnusedSymbol = new UnusedSymbol(treePos, i"unused implicit parameter", List.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.
thought: those two will also be problematic when it comes to create automatic actions because one will also have to update call-sites.
Also, they are only reported for classes not methods.
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, we'd need to know all call sites, and it is an expensive operation or even not possible if it is called in different modules.
What seems like a way of handling this is finding the references on the tooling site, and then compute the diagnostic for the call sites. That would require us to compute additional replacements on tooling site.
Maybe we should consider adding something like we have in lsp: additionalEdits
which could be populated later on ?
This won't solve the issue with recomputing the edit, but would provide a nice API to handle this case. Another problem that arises it that there is a specific scenario that will fail if we choose this approach:
- API module in lets say 3.5.0 where we want to remove implicit param in definition.
- Different module in 3.6.0 which is using api with removed implicit params laeding to an error.
If we let tooling do the refactor, we have to take changes in internal AST structure into consideration, unless we rely on some other parser like Scalameta.
This leads to another idea, which in my opinion can work:
Lets add actionable diagnostic to remove extra param in call site, and then after removing implicit param in definition, we can run extra actionable diagnostics on all call sites. This would solve an issue with different versions.
Sorry for the review to take that long, but it seems like we've aligned the solution. |
b29de63
to
bde33f0
Compare
As discussed in DMs I stripped the PR from any implementation of actionable diagnostics for unused symbols so it can be merged faster. Actionable diagnostics will be implemented as a follow-up. In the meantime merging this PR will unblock other work in metals as we need to have the errorCode set in stone before we start using it. |
@@ -37,5 +38,6 @@ enum MessageKind: | |||
case PatternMatchExhaustivity => "Pattern Match Exhaustivity" | |||
case MatchCaseUnreachable => "Match case Unreachable" | |||
case PotentialIssue => "Potential Issue" | |||
case UnusedSymbol => "Unused symbol" |
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.
If you could change this to "Unused Symbol" as it seems we define them in Pascal case, you can also take the chance to change Match Case Unreachable. Tests will fail after this change.
54a4cc1
to
00d6eae
Compare
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.
For the context. This change is required to filter unused diagnostic in non-hacky
manner (without using regex).
With this change, it will allow us e.g. to add special highlighting for unused names.
Co-authored-by: ghostbuster91 <[email protected]> [Cherry-picked 3fdb292][modified]
No description provided.