Skip to content

Commit b623281

Browse files
authored
Merge pull request scala#78 from MasseGuillaume/cross2
Add more rules to CrossCompile
2 parents 6a1752f + 8e9e19f commit b623281

File tree

12 files changed

+126
-44
lines changed

12 files changed

+126
-44
lines changed

.travis.yml

-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ cache:
6666
- "$HOME/.sbt/boot/scala*"
6767
- "$HOME/.sbt/launchers"
6868
- "$HOME/.ivy2/cache"
69-
- "$HOME/.coursier"
7069

7170
before_cache:
7271
- du -h -d 1 $HOME/.ivy2/cache

compat/src/main/scala-2.11_2.12/scala/collection/compat/package.scala

+6
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ package object compat {
5454
def rangeUntil(until: K): T = fact.until(until)
5555
}
5656

57+
implicit class IteratorExtensionMethods[A](private val self: Iterator[A]) extends AnyVal {
58+
def sameElements[B >: A](that: IterableOnce[B]): Boolean = {
59+
self.sameElements(that.iterator)
60+
}
61+
}
62+
5763
implicit class TraversableOnceExtensionMethods[A](private val self: TraversableOnce[A]) extends AnyVal {
5864
def iterator: Iterator[A] = self.toIterator
5965
}

compat/src/test/scala/test/scala/collection/CollectionTest.scala

+11
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,15 @@ class CollectionTest {
5252
val xs = Iterator(1, 2, 3).iterator.toList
5353
assertEquals(List(1, 2, 3), xs)
5454
}
55+
56+
@Test
57+
def testSameElements: Unit = {
58+
val it1: Iterable[Int] = List(1)
59+
val it2: Iterable[Int] = List(1, 2, 3)
60+
val it3: Iterable[Int] = List(1, 2, 3)
61+
62+
assertTrue(it1.iterator.sameElements(it1))
63+
assertFalse(it1.iterator.sameElements(it2))
64+
assertTrue(it2.iterator.sameElements(it3))
65+
}
5566
}

scalafix/input/src/main/scala/fix/IterableSrc.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
rule = "scala:fix.NewCollections"
2+
rule = "scala:fix.CrossCompat"
33
*/
44
package fix
55

scalafix/input/src/main/scala/fix/TraversableSrc.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
rule = "scala:fix.NewCollections"
2+
rule = "scala:fix.CrossCompat"
33
*/
44
package fix
55

scalafix/output213/src/main/scala/fix/IterableSrc.scala renamed to scalafix/output212/src/main/scala/fix/IterableSrc.scala

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
package fix
55

6+
import scala.collection.compat._
67
class IterableSrc(it: Iterable[Int]) {
78
it.iterator.sameElements(it)
89
}

scalafix/output213/src/main/scala/fix/TraversableSrc.scala renamed to scalafix/output212/src/main/scala/fix/TraversableSrc.scala

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
package fix
55

6+
import scala.collection.compat._
67
object TraversableSrc {
78
def foo(xs: Iterable[(Int, String)], ys: List[Int]): Unit = {
89
xs.to(List)

scalafix/rules/src/main/scala/fix/CanBuildFrom.scala

+10-4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import scalafix._
44
import scalafix.util._
55
import scala.meta._
66

7+
import scala.collection.mutable
8+
79
object CanBuildFrom {
810
def apply(paramss: List[List[Term.Param]],
911
body: Term,
@@ -77,7 +79,8 @@ object CanBuildFromNothing {
7779
ctx: RuleCtx,
7880
collectionCanBuildFrom: SymbolMatcher,
7981
nothing: SymbolMatcher,
80-
toTpe: SymbolMatcher)(implicit index: SemanticdbIndex): Patch = {
82+
toTpe: SymbolMatcher,
83+
handledTo: mutable.Set[Tree])(implicit index: SemanticdbIndex): Patch = {
8184
paramss.flatten.collect{
8285
case
8386
Term.Param(
@@ -97,7 +100,7 @@ object CanBuildFromNothing {
97100
)
98101
),
99102
_
100-
) => new CanBuildFromNothing(param, tpe, t, cct, cc, body, ctx, toTpe)
103+
) => new CanBuildFromNothing(param, tpe, t, cct, cc, body, ctx, toTpe, handledTo)
101104
}.map(_.toFactory).asPatch
102105
}
103106
}
@@ -118,7 +121,8 @@ case class CanBuildFromNothing(param: Name,
118121
cc: Type,
119122
body: Term,
120123
ctx: RuleCtx,
121-
toTpe: SymbolMatcher) {
124+
toTpe: SymbolMatcher,
125+
handledTo: mutable.Set[Tree]) {
122126
def toFactory(implicit index: SemanticdbIndex): Patch = {
123127
val matchCbf = SymbolMatcher.exact(ctx.index.symbol(param).get)
124128

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

129133
// don't patch cbf.apply twice (cbf.apply and cbf.apply())
130-
val visitedCbfCalls = scala.collection.mutable.Set[Tree]()
134+
val visitedCbfCalls = mutable.Set[Tree]()
131135

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

160+
handledTo += to
161+
156162
// e.to[CC](*cbf*) extract implicit parameter
157163
val synth = ctx.index.synthetics.find(_.position.end == ap.pos.end).get
158164
val Term.Apply(_, List(implicitCbf)) = synth.text.parse[Term].get

scalafix/rules/src/main/scala/fix/CrossCompat.scala

+7-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,10 @@ package fix
22

33
import scalafix._
44

5-
case class CrossCompat(index: SemanticdbIndex) extends SemanticRule(index, "CrossCompat") with Stable212Base
5+
case class CrossCompat(index: SemanticdbIndex)
6+
extends SemanticRule(index, "CrossCompat")
7+
with CrossCompatibility
8+
with Stable212Base {
9+
10+
def isCrossCompatible: Boolean = true
11+
}

scalafix/rules/src/main/scala/fix/NewCollections.scala

+9-25
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,15 @@ import scalafix.util._
55
import scala.meta._
66

77
// Not 2.12 Cross-Compatible
8-
case class NewCollections(index: SemanticdbIndex) extends SemanticRule(index, "NewCollections") with Stable212Base {
8+
case class NewCollections(index: SemanticdbIndex)
9+
extends SemanticRule(index, "NewCollections")
10+
with Stable212Base {
11+
12+
13+
def isCrossCompatible: Boolean = false
14+
915
// == Symbols ==
10-
val iterableSameElement = exact("_root_.scala.collection.IterableLike#sameElements(Lscala/collection/GenIterable;)Z.")
11-
val iterator = normalized("_root_.scala.collection.TraversableLike.toIterator.")
16+
1217
val tupleZipped = normalized(
1318
"_root_.scala.runtime.Tuple2Zipped.Ops.zipped.",
1419
"_root_.scala.runtime.Tuple3Zipped.Ops.zipped."
@@ -100,18 +105,6 @@ case class NewCollections(index: SemanticdbIndex) extends SemanticRule(index, "N
100105
}.asPatch
101106
}
102107

103-
def replaceToList(ctx: RuleCtx): Patch = {
104-
ctx.tree.collect {
105-
case iterator(t: Name) =>
106-
ctx.replaceTree(t, "iterator")
107-
108-
case t @ toTpe(n: Name) =>
109-
trailingBrackets(n, ctx).map { case (open, close) =>
110-
ctx.replaceToken(open, "(") + ctx.replaceToken(close, ")")
111-
}.asPatch
112-
}.asPatch
113-
}
114-
115108
def replaceTupleZipped(ctx: RuleCtx): Patch = {
116109
ctx.tree.collect {
117110
case tupleZipped(Term.Select(Term.Tuple(args), name)) =>
@@ -153,13 +146,6 @@ case class NewCollections(index: SemanticdbIndex) extends SemanticRule(index, "N
153146
}.asPatch
154147
}
155148

156-
def replaceIterableSameElements(ctx: RuleCtx): Patch = {
157-
ctx.tree.collect {
158-
case Term.Apply(Term.Select(lhs, iterableSameElement(_)), List(_)) =>
159-
ctx.addRight(lhs, ".iterator")
160-
}.asPatch
161-
}
162-
163149
def replaceBreakout(ctx: RuleCtx): Patch = {
164150
import Breakout._
165151

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

217203
override def fix(ctx: RuleCtx): Patch = {
218204
super.fix(ctx) +
219-
replaceToList(ctx) +
220205
replaceSymbols(ctx) +
221206
replaceTupleZipped(ctx) +
222207
replaceMutableMap(ctx) +
223208
replaceMutableSet(ctx) +
224-
replaceBreakout(ctx) +
225-
replaceIterableSameElements(ctx)
209+
replaceBreakout(ctx)
226210
}
227211
}

scalafix/rules/src/main/scala/fix/Stable212Base.scala

+76-11
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,20 @@ import scalafix._
44
import scalafix.util._
55
import scala.meta._
66

7+
import scala.collection.mutable
8+
9+
trait CrossCompatibility {
10+
def isCrossCompatible: Boolean
11+
}
12+
713
// 2.12 Cross-Compatible
8-
trait Stable212Base { self: SemanticRule =>
14+
trait Stable212Base extends CrossCompatibility { self: SemanticRule =>
15+
16+
// Two rules triggers the same rewrite TraversableLike.to and CanBuildFrom
17+
// we keep track of what is handled in CanBuildFrom and guard against TraversableLike.to
18+
val handledTo = mutable.Set[Tree]()
919

10-
// == Symbols ==
20+
// == Symbols ==
1121
def foldSymbol(isLeft: Boolean): SymbolMatcher = {
1222
val op =
1323
if (isLeft) "/:"
@@ -16,9 +26,11 @@ trait Stable212Base { self: SemanticRule =>
1626
normalized(s"_root_.scala.collection.TraversableOnce.`$op`.")
1727
}
1828

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

45+
val traversable = exact(
46+
"_root_.scala.package.Traversable#",
47+
"_root_.scala.collection.Traversable#",
48+
"_root_.scala.package.Iterable#",
49+
"_root_.scala.collection.Iterable#"
50+
)
51+
3352
// == Rules ==
3453

54+
def replaceIterableSameElements(ctx: RuleCtx): Patch = {
55+
ctx.tree.collect {
56+
case Term.Apply(Term.Select(lhs, iterableSameElement(_)), List(_)) =>
57+
ctx.addRight(lhs, ".iterator")
58+
}.asPatch
59+
}
60+
61+
3562
def replaceSymbols0(ctx: RuleCtx): Patch = {
36-
ctx.replaceSymbols(
37-
"scala.collection.LinearSeq" -> "scala.collection.immutable.List",
38-
"scala.Traversable" -> "scala.Iterable",
39-
"scala.collection.Traversable" -> "scala.collection.Iterable"
40-
)
63+
val traversableToIterable =
64+
ctx.replaceSymbols(
65+
"scala.Traversable" -> "scala.Iterable",
66+
"scala.collection.Traversable" -> "scala.collection.Iterable"
67+
)
68+
69+
val linearSeqToList =
70+
ctx.replaceSymbols(
71+
"scala.collection.LinearSeq" -> "scala.collection.immutable.List",
72+
)
73+
74+
import scala.meta.contrib._
75+
val hasTraversable =
76+
ctx.tree.exists {
77+
case traversable(_) => true
78+
case _ => false
79+
80+
}
81+
82+
val compatImport =
83+
if (hasTraversable) addCompatImport(ctx)
84+
else Patch.empty
85+
86+
traversableToIterable + linearSeqToList + compatImport
4187
}
4288

4389
def replaceSymbolicFold(ctx: RuleCtx): Patch = {
@@ -123,7 +169,7 @@ trait Stable212Base { self: SemanticRule =>
123169
val useSites =
124170
ctx.tree.collect {
125171
case Defn.Def(_, _, _, paramss, _, body) =>
126-
CanBuildFromNothing(paramss, body, ctx, collectionCanBuildFrom, nothing, toTpe) +
172+
CanBuildFromNothing(paramss, body, ctx, collectionCanBuildFrom, nothing, toTpe, handledTo) +
127173
CanBuildFrom(paramss, body, ctx, collectionCanBuildFrom, nothing)
128174
}.asPatch
129175

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

136-
val compatImport =
137-
ctx.addGlobalImport(importer"scala.collection.compat._")
182+
val compatImport = addCompatImport(ctx)
138183

139184
if (useSites.nonEmpty) useSites + imports + compatImport
140185
else Patch.empty
141186
}
142187

188+
def replaceToList(ctx: RuleCtx): Patch = {
189+
ctx.tree.collect {
190+
case iterator(t: Name) =>
191+
ctx.replaceTree(t, "iterator")
192+
193+
case t @ toTpe(n: Name) if !handledTo.contains(n) =>
194+
trailingBrackets(n, ctx).map { case (open, close) =>
195+
ctx.replaceToken(open, "(") + ctx.replaceToken(close, ")")
196+
}.asPatch
197+
}.asPatch
198+
}
199+
200+
201+
def addCompatImport(ctx: RuleCtx): Patch = {
202+
if (isCrossCompatible) ctx.addGlobalImport(importer"scala.collection.compat._")
203+
else Patch.empty
204+
}
205+
143206
override def fix(ctx: RuleCtx): Patch = {
144207
replaceSymbols0(ctx) +
145208
replaceCanBuildFrom(ctx) +
209+
replaceToList(ctx) +
146210
replaceCopyToBuffer(ctx) +
147211
replaceSymbolicFold(ctx) +
148212
replaceSetMapPlus2(ctx) +
149213
replaceMutSetMapPlus(ctx) +
150214
replaceMutMapUpdated(ctx) +
151-
replaceArrayBuilderMake(ctx)
215+
replaceArrayBuilderMake(ctx) +
216+
replaceIterableSameElements(ctx)
152217
}
153218

154219
}

scalafix/tests/src/test/scala/fix/ScalafixTests.scala

+3
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,8 @@ class ScalafixTests
1414
AbsolutePath(BuildInfo.output213FailureSourceroot)
1515
)
1616
) {
17+
1718
runAllTests()
19+
// to run only one test:
20+
// testsToRun.filter(_.filename.toNIO.getFileName.toString == "IterableSrc.scala" ).foreach(runOn)
1821
}

0 commit comments

Comments
 (0)