Skip to content

Add more rules to CrossCompile #78

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

Merged
merged 1 commit into from
Jul 2, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ cache:
- "$HOME/.sbt/boot/scala*"
- "$HOME/.sbt/launchers"
- "$HOME/.ivy2/cache"
- "$HOME/.coursier"

before_cache:
- du -h -d 1 $HOME/.ivy2/cache
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ package object compat {
def rangeUntil(until: K): T = fact.until(until)
}

implicit class IteratorExtensionMethods[A](private val self: Iterator[A]) extends AnyVal {
def sameElements[B >: A](that: IterableOnce[B]): Boolean = {
self.sameElements(that.iterator)
}
}

implicit class TraversableOnceExtensionMethods[A](private val self: TraversableOnce[A]) extends AnyVal {
def iterator: Iterator[A] = self.toIterator
}
Expand Down
11 changes: 11 additions & 0 deletions compat/src/test/scala/test/scala/collection/CollectionTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,15 @@ class CollectionTest {
val xs = Iterator(1, 2, 3).iterator.toList
assertEquals(List(1, 2, 3), xs)
}

@Test
def testSameElements: Unit = {
val it1: Iterable[Int] = List(1)
val it2: Iterable[Int] = List(1, 2, 3)
val it3: Iterable[Int] = List(1, 2, 3)

assertTrue(it1.iterator.sameElements(it1))
assertFalse(it1.iterator.sameElements(it2))
assertTrue(it2.iterator.sameElements(it3))
}
}
2 changes: 1 addition & 1 deletion scalafix/input/src/main/scala/fix/IterableSrc.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
rule = "scala:fix.NewCollections"
rule = "scala:fix.CrossCompat"
*/
package fix

Expand Down
2 changes: 1 addition & 1 deletion scalafix/input/src/main/scala/fix/TraversableSrc.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
rule = "scala:fix.NewCollections"
rule = "scala:fix.CrossCompat"
*/
package fix

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

package fix

import scala.collection.compat._
class IterableSrc(it: Iterable[Int]) {
it.iterator.sameElements(it)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

package fix

import scala.collection.compat._
object TraversableSrc {
def foo(xs: Iterable[(Int, String)], ys: List[Int]): Unit = {
xs.to(List)
Expand Down
14 changes: 10 additions & 4 deletions scalafix/rules/src/main/scala/fix/CanBuildFrom.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import scalafix._
import scalafix.util._
import scala.meta._

import scala.collection.mutable

object CanBuildFrom {
def apply(paramss: List[List[Term.Param]],
body: Term,
Expand Down Expand Up @@ -77,7 +79,8 @@ object CanBuildFromNothing {
ctx: RuleCtx,
collectionCanBuildFrom: SymbolMatcher,
nothing: SymbolMatcher,
toTpe: SymbolMatcher)(implicit index: SemanticdbIndex): Patch = {
toTpe: SymbolMatcher,
handledTo: mutable.Set[Tree])(implicit index: SemanticdbIndex): Patch = {
paramss.flatten.collect{
case
Term.Param(
Expand All @@ -97,7 +100,7 @@ object CanBuildFromNothing {
)
),
_
) => new CanBuildFromNothing(param, tpe, t, cct, cc, body, ctx, toTpe)
) => new CanBuildFromNothing(param, tpe, t, cct, cc, body, ctx, toTpe, handledTo)
}.map(_.toFactory).asPatch
}
}
Expand All @@ -118,7 +121,8 @@ case class CanBuildFromNothing(param: Name,
cc: Type,
body: Term,
ctx: RuleCtx,
toTpe: SymbolMatcher) {
toTpe: SymbolMatcher,
handledTo: mutable.Set[Tree]) {
def toFactory(implicit index: SemanticdbIndex): Patch = {
val matchCbf = SymbolMatcher.exact(ctx.index.symbol(param).get)

Expand All @@ -127,7 +131,7 @@ case class CanBuildFromNothing(param: Name,
ctx.replaceTree(tree, Term.Select(cbf2, Term.Name("newBuilder")).syntax)

// don't patch cbf.apply twice (cbf.apply and cbf.apply())
val visitedCbfCalls = scala.collection.mutable.Set[Tree]()
val visitedCbfCalls = mutable.Set[Tree]()

val cbfCalls =
body.collect {
Expand All @@ -153,6 +157,8 @@ case class CanBuildFromNothing(param: Name,
body.collect {
case ap @ Term.ApplyType(Term.Select(e, to @ toTpe(_)), List(cc2 @ matchCC(_))) =>

handledTo += to
Copy link
Contributor

Choose a reason for hiding this comment

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

How is this code related to the .sameElements rewrite rule?

Copy link
Contributor

Choose a reason for hiding this comment

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

Can you explain the list of changes performed by this commit in the commit message?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

CanBuildFrom and .to[C] => .to(C) are now in the same Rule. You need to guard the .to rewrite from the one in CanBuildFrom. This PR fix #75 and does more.


// e.to[CC](*cbf*) extract implicit parameter
val synth = ctx.index.synthetics.find(_.position.end == ap.pos.end).get
val Term.Apply(_, List(implicitCbf)) = synth.text.parse[Term].get
Expand Down
8 changes: 7 additions & 1 deletion scalafix/rules/src/main/scala/fix/CrossCompat.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,10 @@ package fix

import scalafix._

case class CrossCompat(index: SemanticdbIndex) extends SemanticRule(index, "CrossCompat") with Stable212Base
case class CrossCompat(index: SemanticdbIndex)
extends SemanticRule(index, "CrossCompat")
with CrossCompatibility
with Stable212Base {

def isCrossCompatible: Boolean = true
}
34 changes: 9 additions & 25 deletions scalafix/rules/src/main/scala/fix/NewCollections.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@ import scalafix.util._
import scala.meta._

// Not 2.12 Cross-Compatible
case class NewCollections(index: SemanticdbIndex) extends SemanticRule(index, "NewCollections") with Stable212Base {
case class NewCollections(index: SemanticdbIndex)
extends SemanticRule(index, "NewCollections")
with Stable212Base {


def isCrossCompatible: Boolean = false

// == Symbols ==
val iterableSameElement = exact("_root_.scala.collection.IterableLike#sameElements(Lscala/collection/GenIterable;)Z.")
val iterator = normalized("_root_.scala.collection.TraversableLike.toIterator.")

val tupleZipped = normalized(
"_root_.scala.runtime.Tuple2Zipped.Ops.zipped.",
"_root_.scala.runtime.Tuple3Zipped.Ops.zipped."
Expand Down Expand Up @@ -100,18 +105,6 @@ case class NewCollections(index: SemanticdbIndex) extends SemanticRule(index, "N
}.asPatch
}

def replaceToList(ctx: RuleCtx): Patch = {
ctx.tree.collect {
case iterator(t: Name) =>
ctx.replaceTree(t, "iterator")

case t @ toTpe(n: Name) =>
trailingBrackets(n, ctx).map { case (open, close) =>
ctx.replaceToken(open, "(") + ctx.replaceToken(close, ")")
}.asPatch
}.asPatch
}

def replaceTupleZipped(ctx: RuleCtx): Patch = {
ctx.tree.collect {
case tupleZipped(Term.Select(Term.Tuple(args), name)) =>
Expand Down Expand Up @@ -153,13 +146,6 @@ case class NewCollections(index: SemanticdbIndex) extends SemanticRule(index, "N
}.asPatch
}

def replaceIterableSameElements(ctx: RuleCtx): Patch = {
ctx.tree.collect {
case Term.Apply(Term.Select(lhs, iterableSameElement(_)), List(_)) =>
ctx.addRight(lhs, ".iterator")
}.asPatch
}

def replaceBreakout(ctx: RuleCtx): Patch = {
import Breakout._

Expand Down Expand Up @@ -216,12 +202,10 @@ case class NewCollections(index: SemanticdbIndex) extends SemanticRule(index, "N

override def fix(ctx: RuleCtx): Patch = {
super.fix(ctx) +
replaceToList(ctx) +
replaceSymbols(ctx) +
replaceTupleZipped(ctx) +
replaceMutableMap(ctx) +
replaceMutableSet(ctx) +
replaceBreakout(ctx) +
replaceIterableSameElements(ctx)
replaceBreakout(ctx)
}
}
87 changes: 76 additions & 11 deletions scalafix/rules/src/main/scala/fix/Stable212Base.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,20 @@ import scalafix._
import scalafix.util._
import scala.meta._

import scala.collection.mutable

trait CrossCompatibility {
def isCrossCompatible: Boolean
}

// 2.12 Cross-Compatible
trait Stable212Base { self: SemanticRule =>
trait Stable212Base extends CrossCompatibility { self: SemanticRule =>

// Two rules triggers the same rewrite TraversableLike.to and CanBuildFrom
// we keep track of what is handled in CanBuildFrom and guard against TraversableLike.to
val handledTo = mutable.Set[Tree]()

// == Symbols ==
// == Symbols ==
def foldSymbol(isLeft: Boolean): SymbolMatcher = {
val op =
if (isLeft) "/:"
Expand All @@ -16,9 +26,11 @@ trait Stable212Base { self: SemanticRule =>
normalized(s"_root_.scala.collection.TraversableOnce.`$op`.")
}

val iterator = normalized("_root_.scala.collection.TraversableLike.toIterator.")
val toTpe = normalized("_root_.scala.collection.TraversableLike.to.")
val copyToBuffer = normalized("_root_.scala.collection.TraversableOnce.copyToBuffer.")
val arrayBuilderMake = normalized("_root_.scala.collection.mutable.ArrayBuilder.make(Lscala/reflect/ClassTag;)Lscala/collection/mutable/ArrayBuilder;.")
val iterableSameElement = exact("_root_.scala.collection.IterableLike#sameElements(Lscala/collection/GenIterable;)Z.")
val collectionCanBuildFrom = exact("_root_.scala.collection.generic.CanBuildFrom#")
val collectionCanBuildFromImport = exact("_root_.scala.collection.generic.CanBuildFrom.;_root_.scala.collection.generic.CanBuildFrom#")
val nothing = exact("_root_.scala.Nothing#")
Expand All @@ -30,14 +42,48 @@ trait Stable212Base { self: SemanticRule =>
val foldLeftSymbol = foldSymbol(isLeft = true)
val foldRightSymbol = foldSymbol(isLeft = false)

val traversable = exact(
"_root_.scala.package.Traversable#",
"_root_.scala.collection.Traversable#",
"_root_.scala.package.Iterable#",
"_root_.scala.collection.Iterable#"
)

// == Rules ==

def replaceIterableSameElements(ctx: RuleCtx): Patch = {
ctx.tree.collect {
case Term.Apply(Term.Select(lhs, iterableSameElement(_)), List(_)) =>
ctx.addRight(lhs, ".iterator")
}.asPatch
}


def replaceSymbols0(ctx: RuleCtx): Patch = {
ctx.replaceSymbols(
"scala.collection.LinearSeq" -> "scala.collection.immutable.List",
"scala.Traversable" -> "scala.Iterable",
"scala.collection.Traversable" -> "scala.collection.Iterable"
)
val traversableToIterable =
ctx.replaceSymbols(
"scala.Traversable" -> "scala.Iterable",
"scala.collection.Traversable" -> "scala.collection.Iterable"
)

val linearSeqToList =
ctx.replaceSymbols(
"scala.collection.LinearSeq" -> "scala.collection.immutable.List",
)

import scala.meta.contrib._
val hasTraversable =
ctx.tree.exists {
case traversable(_) => true
case _ => false

}

val compatImport =
if (hasTraversable) addCompatImport(ctx)
else Patch.empty

traversableToIterable + linearSeqToList + compatImport
}

def replaceSymbolicFold(ctx: RuleCtx): Patch = {
Expand Down Expand Up @@ -123,7 +169,7 @@ trait Stable212Base { self: SemanticRule =>
val useSites =
ctx.tree.collect {
case Defn.Def(_, _, _, paramss, _, body) =>
CanBuildFromNothing(paramss, body, ctx, collectionCanBuildFrom, nothing, toTpe) +
CanBuildFromNothing(paramss, body, ctx, collectionCanBuildFrom, nothing, toTpe, handledTo) +
CanBuildFrom(paramss, body, ctx, collectionCanBuildFrom, nothing)
}.asPatch

Expand All @@ -133,22 +179,41 @@ trait Stable212Base { self: SemanticRule =>
ctx.removeImportee(i)
}.asPatch

val compatImport =
ctx.addGlobalImport(importer"scala.collection.compat._")
val compatImport = addCompatImport(ctx)

if (useSites.nonEmpty) useSites + imports + compatImport
else Patch.empty
}

def replaceToList(ctx: RuleCtx): Patch = {
ctx.tree.collect {
case iterator(t: Name) =>
ctx.replaceTree(t, "iterator")

case t @ toTpe(n: Name) if !handledTo.contains(n) =>
trailingBrackets(n, ctx).map { case (open, close) =>
ctx.replaceToken(open, "(") + ctx.replaceToken(close, ")")
}.asPatch
}.asPatch
}


def addCompatImport(ctx: RuleCtx): Patch = {
if (isCrossCompatible) ctx.addGlobalImport(importer"scala.collection.compat._")
else Patch.empty
}

override def fix(ctx: RuleCtx): Patch = {
replaceSymbols0(ctx) +
replaceCanBuildFrom(ctx) +
replaceToList(ctx) +
replaceCopyToBuffer(ctx) +
replaceSymbolicFold(ctx) +
replaceSetMapPlus2(ctx) +
replaceMutSetMapPlus(ctx) +
replaceMutMapUpdated(ctx) +
replaceArrayBuilderMake(ctx)
replaceArrayBuilderMake(ctx) +
replaceIterableSameElements(ctx)
}

}
3 changes: 3 additions & 0 deletions scalafix/tests/src/test/scala/fix/ScalafixTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,8 @@ class ScalafixTests
AbsolutePath(BuildInfo.output213FailureSourceroot)
)
) {

runAllTests()
// to run only one test:
// testsToRun.filter(_.filename.toNIO.getFileName.toString == "IterableSrc.scala" ).foreach(runOn)
}