-
Notifications
You must be signed in to change notification settings - Fork 185
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,6 +10,8 @@ import scalafix.patch.Patch | |
import scalafix.patch.Patch.internal.ReplaceSymbol | ||
import scalafix.syntax._ | ||
import scalafix.v0._ | ||
import scala.annotation.tailrec | ||
|
||
|
||
object ReplaceSymbolOps { | ||
private object Select { | ||
|
@@ -19,6 +21,20 @@ object ReplaceSymbolOps { | |
case _ => None | ||
} | ||
} | ||
private def extractImports(stats: Seq[Stat]): Seq[Import] = { | ||
stats | ||
.takeWhile(_.is[Import]) | ||
.collect { case i: Import => i } | ||
} | ||
|
||
@tailrec private final def getGlobalImports(ast: Tree): Seq[Import] = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
ast match { | ||
case Pkg(_, Seq(pkg: Pkg)) => getGlobalImports(pkg) | ||
case Source(Seq(pkg: Pkg)) => getGlobalImports(pkg) | ||
case Pkg(_, stats) => extractImports(stats) | ||
case Source(stats) => extractImports(stats) | ||
case _ => Nil | ||
} | ||
|
||
def naiveMoveSymbolPatch( | ||
moveSymbols: Seq[ReplaceSymbol] | ||
|
@@ -92,6 +108,7 @@ object ReplaceSymbolOps { | |
case _ => None | ||
} | ||
} | ||
|
||
val patches = ctx.tree.collect { case n @ Move(to) => | ||
// was this written as `to = "blah"` instead of `to = _root_.blah` | ||
val isSelected = to match { | ||
|
@@ -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 => | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Indeed - is it possible to call |
||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
} | ||
} | ||
val addImport = | ||
if (n.isDefinition) Patch.empty | ||
if (n.isDefinition || causesCollision) Patch.empty | ||
else ctx.addGlobalImport(to) | ||
addImport + ctx.replaceTree(n, to.signature.name) | ||
if(causesCollision) | ||
addImport + ctx.replaceTree(n, to.owner.syntax + to.signature.name) | ||
else | ||
addImport + ctx.replaceTree(n, to.signature.name) | ||
case _ => | ||
Patch.empty | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package com.geirsson | ||
|
||
import scala.collection.immutable.TreeMap | ||
|
||
package object immutable { | ||
type SortedMap[A, B] = TreeMap[A, B] | ||
|
||
object SortedMap { | ||
def empty[A : Ordering, B]: SortedMap[A, B] = TreeMap.empty[A, B] | ||
} | ||
} |
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 useful?