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

Scalafix rename that leads to a name collision breaks compilation #1695

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

jmcrawford45
Copy link

Fixes #1305

@@ -126,10 +143,20 @@ object ReplaceSymbolOps {
if sig.name != parent.value =>
Patch.empty // do nothing because it was a renamed symbol
case Some(parent) =>
val causesCollision = getGlobalImports(ctx.tree).exists { importStmt =>
Copy link
Author

Choose a reason for hiding this comment

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

It would be more performant to get the global imports from the start and manipulate them as patches are processed. We want to handle for foo.Bar => foo.Baz and bar.Foo => bar.Baz without breaking compilation, so we have to consider previous renames as well. Now that I think more on it, I guess for completeness we'd also want to handle non-import collisions. Open to suggestions on these.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Now that I think more on it, I guess for completeness we'd also want to handle non-import collisions. Open to suggestions on these.

Indeed, although this would be more complex as there is not enough information in the tree nor in semanticdb to list all locals for example. You'd have to start a presentation compiler instance (outside the scalafix API), just like ExplicitResultTypes, which would greatly increase code complexity and ease-of-use (as scalafix's Scala binary version must match the target's). Without going that way, there are some heuristics that could prevent non-import collisions (getting visibile members from super traits, getting local val names via the tree, etc), but it's a lot of work to cover them all.

Copy link
Collaborator

Choose a reason for hiding this comment

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

It would be more performant to get the global imports from the start

Indeed - is it possible to call getGlobalImports once outside the tree traversal?

Copy link
Collaborator

@bjaglin bjaglin left a comment

Choose a reason for hiding this comment

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

Welcome to the project and thanks a lot for putting this together! This feature has a lot of shortcomings, and this does remove one 👍

Don't forget to run scalafixAll before committing (see CI failure https://github.com/scalacenter/scalafix/actions/runs/3266135538/jobs/5369523474).

@@ -19,6 +21,20 @@ object ReplaceSymbolOps {
case _ => None
}
}
private def extractImports(stats: Seq[Stat]): Seq[Import] = {
stats
.takeWhile(_.is[Import])
Copy link
Collaborator

Choose a reason for hiding this comment

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

is this useful?

@@ -126,10 +143,20 @@ object ReplaceSymbolOps {
if sig.name != parent.value =>
Patch.empty // do nothing because it was a renamed symbol
case Some(parent) =>
val causesCollision = getGlobalImports(ctx.tree).exists { importStmt =>
Copy link
Collaborator

Choose a reason for hiding this comment

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

Now that I think more on it, I guess for completeness we'd also want to handle non-import collisions. Open to suggestions on these.

Indeed, although this would be more complex as there is not enough information in the tree nor in semanticdb to list all locals for example. You'd have to start a presentation compiler instance (outside the scalafix API), just like ExplicitResultTypes, which would greatly increase code complexity and ease-of-use (as scalafix's Scala binary version must match the target's). Without going that way, there are some heuristics that could prevent non-import collisions (getting visibile members from super traits, getting local val names via the tree, etc), but it's a lot of work to cover them all.

importStmt.importers.flatMap(_.importees).exists {
case Importee.Name(name) => name.value == to.signature.name
case Importee.Rename(_, rename) => rename.value == to.signature.name
case _ => false
Copy link
Collaborator

Choose a reason for hiding this comment

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

Wildcards can also cause collisions (although we can't tell using Scalafix APIs). This is probably outside the scope of this PR, but we could have an opt-in "safe" flag that disable global imports whenever it finds wildcard imports.

.collect { case i: Import => i }
}

@tailrec private final def getGlobalImports(ast: Tree): Seq[Import] =
Copy link
Collaborator

Choose a reason for hiding this comment

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

this could be more granular and collect relevant scoped imports, all the way to the global ones at the top

@@ -126,10 +143,20 @@ object ReplaceSymbolOps {
if sig.name != parent.value =>
Patch.empty // do nothing because it was a renamed symbol
case Some(parent) =>
val causesCollision = getGlobalImports(ctx.tree).exists { importStmt =>
Copy link
Collaborator

Choose a reason for hiding this comment

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

It would be more performant to get the global imports from the start

Indeed - is it possible to call getGlobalImports once outside the tree traversal?

@bjaglin
Copy link
Collaborator

bjaglin commented Dec 15, 2022

@jmcrawford45 will you have time to pursue this?

@bjaglin
Copy link
Collaborator

bjaglin commented Jun 4, 2023

@jmcrawford45 I would love to get that in, any chance you could address the outstanding comments? Thanks!

@bjaglin bjaglin marked this pull request as draft July 1, 2023 00:20
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.

Scalafix rename that leads to a name collision breaks compilation
2 participants