Skip to content
This repository was archived by the owner on Dec 22, 2021. It is now read-only.
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
26 changes: 20 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,20 +58,34 @@ To use it, add the [scalafix](https://scalacenter.github.io/scalafix/) sbt plugi
to your build, as explained in
[its documentation](https://scalacenter.github.io/scalafix/#Installation).

Then run the following sbt task on your project:
Two situations are supported: (1) migrating a 2.12 code base to a 2.12 code base that
uses the collection strawman as a library (instead of the standard collections), and
(2) migrating a 2.12 code base to 2.13 code base.

The migration tool is not exhaustive and we will continue to improve
it over time. If you encounter a use case that’s not supported, please
report it as described in the
[contributing documentation](CONTRIBUTING.md#migration-tool).

#### Migrating a 2.12 code base to a 2.12 code base that uses the collection strawman as a library

Run the following sbt task on your project:

~~~
> scalafix github:scala/collection-strawman/v0
> scalafix https://github.com/scala/collection-strawman/raw/master/scalafix/2.12/rules/src/main/scala/fix/Collectionstrawman_v0.scala
~~~

In essence, the migration tool changes the imports in your source code
so that the strawman definitions are imported. It also rewrites
expressions that use an API that is different in the strawman.

The migration tool is not exhaustive and we will continue to improve
it over time. If you encounter a use case that’s not supported, please
report it as described in the
[contributing documentation](CONTRIBUTING.md#migration-tool).
#### Migrating a 2.12 code base to 2.13 code base

Run the following sbt task on your project:

~~~
> scalafix https://github.com/scala/collection-strawman/raw/master/scalafix/2.13/rules/src/main/scala/fix/Collectionstrawman_v0.scala
~~~

### Additional Operations

Expand Down
12 changes: 10 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ import org.scalajs.sbtplugin.cross.CrossProject
val dotty = settingKey[String]("dotty version")
dotty in ThisBuild := "0.6.0-bin-20171212-9de3905-NIGHTLY"

val collectionsScalaVersionSettings = Seq(
scalaVersion := "2.13.0-M2",
crossScalaVersions := scalaVersion.value :: "2.12.4" :: dotty.value :: Nil
)

val commonSettings = Seq(
organization := "ch.epfl.scala",
version := "0.9.0-SNAPSHOT",
scalaVersion := "2.13.0-M2",
crossScalaVersions := scalaVersion.value :: "2.12.4" :: dotty.value :: Nil,
scalaVersion := "2.12.4",
scalacOptions ++= Seq("-deprecation", "-feature", "-unchecked", "-language:higherKinds"/*, "-opt:l:classpath"*/),
scalacOptions ++= {
if (!isDotty.value)
Expand Down Expand Up @@ -74,6 +78,7 @@ def crossProj(id: String, base: File) =
val collections =
crossProj("collections", file("collections"))
.settings(
collectionsScalaVersionSettings,
name := "collection-strawman",
scalacOptions += "-Yno-imports"
)
Expand All @@ -85,6 +90,7 @@ val `collections-contrib` =
crossProj("collections-contrib", file("collections-contrib"))
.dependsOn(collections)
.settings(
collectionsScalaVersionSettings,
name := "collections-contrib"
)

Expand Down Expand Up @@ -112,6 +118,7 @@ val junit = project.in(file("test") / "junit")
.dependsOn(collectionsJVM)
.settings(commonSettings ++ disablePublishing)
.settings(
collectionsScalaVersionSettings,
fork in Test := true,
javaOptions in Test += "-Xss1M",
libraryDependencies ++= Seq(
Expand All @@ -129,6 +136,7 @@ val scalacheck = project.in(file("test") / "scalacheck")
// Dotty 0.3.0-RC1 crashes when trying to compile this project
.settings(disableDotty)
.settings(
collectionsScalaVersionSettings,
fork in Test := true,
javaOptions in Test += "-Xss1M",
libraryDependencies ++= Seq(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Also, a side-effect of the changes in this file is that the project can be imported without errors in IntelliJ.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
rule = "scala:fix.Collectionstrawman_v0"
*/
package fix

object Collectionstrawman_v0_Stream {
Stream(1, 2, 3)
1 #:: 2 #:: 3 #:: Stream.Empty
val isEmpty: Stream[_] => Boolean = {
case Stream.Empty => true
case x #:: xs => false
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
rule = "scala:fix.Collectionstrawman_v0"
*/
package fix

object Collectionstrawman_v0_Traversable {
def foo(xs: Traversable[(Int, String)], ys: List[Int]): Unit = {
xs.to[List]
xs.to[Set]
xs.toIterator
ys.iterator
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
rule = "scala:fix.Collectionstrawman_v0"
*/
package fix

import scala.language.postfixOps
object Collectionstrawman_v0_Tuple2Zipped {
def zipped(xs: List[Int], ys: List[Int]): Unit = {
(xs, ys).zipped
(xs,ys).zipped
((xs, ys) zipped)
(((xs) , (ys)).zipped)
(xs, // foo
ys).zipped
/* a */(/* b */ xs /* c */, /* d */ ys /* e */)/* f */./* g */zipped/* h */
(coll(1), coll(2)).zipped
(List(1, 2, 3), Stream.from(1)).zipped
}
def coll(x: Int): List[Int] = ???
}

object Collectionstrawman_v0_Tuple3Zipped {
def zipped(xs: List[Int], ys: List[Int], zs: List[Int]): Unit = {
(xs, ys, zs).zipped
(xs,ys,zs).zipped
((xs, ys, zs) zipped)
(((xs) , (ys) , (zs)).zipped)
(xs, // foo
ys, // bar
zs).zipped
/* a */(/* b */ xs /* c */, /* d */ ys /* e */, /* f */ zs /* g */)/* h */./* i */zipped/* j */
(coll(1), coll(2), coll(3)).zipped
(List(1, 2, 3), Set(1, 2, 3), Stream.from(1)).zipped
}
def coll(x: Int): List[Int] = ???
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
rule = "scala:fix.Collectionstrawman_v0"
*/
package fix

import scala.collection.mutable

class Collectionstrawman_v0_copyToBuffer(xs: List[Int], b: mutable.Buffer[Int]) {

xs.copyToBuffer(b)
(xs ++ xs).copyToBuffer(b)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package fix

object Collectionstrawman_v0_Stream {
LazyList(1, 2, 3)
1 #:: 2 #:: 3 #:: LazyList.Empty
val isEmpty: LazyList[_] => Boolean = {
case LazyList.Empty => true
case x #:: xs => false
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package fix

object Collectionstrawman_v0_Traversable {
def foo(xs: Iterable[(Int, String)], ys: List[Int]): Unit = {
xs.to(List)
xs.to(Set)
xs.iterator()
ys.iterator()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package fix

import scala.language.postfixOps
object Collectionstrawman_v0_Tuple2Zipped {
def zipped(xs: List[Int], ys: List[Int]): Unit = {
xs.lazyZip(ys)
xs.lazyZip(ys)
(xs.lazyZip(ys) )
((xs).lazyZip((ys)))
xs.lazyZip(// foo
ys)
/* a *//* b */ xs /* c */.lazyZip(/* d */ ys /* e */)/* f *//* g *//* h */
coll(1).lazyZip(coll(2))
List(1, 2, 3).lazyZip(LazyList.from(1))
}
def coll(x: Int): List[Int] = ???
}

object Collectionstrawman_v0_Tuple3Zipped {
def zipped(xs: List[Int], ys: List[Int], zs: List[Int]): Unit = {
xs.lazyZip(ys).lazyZip(zs)
xs.lazyZip(ys).lazyZip(zs)
(xs.lazyZip(ys).lazyZip(zs) )
((xs).lazyZip((ys)).lazyZip((zs)))
xs.lazyZip(// foo
ys).lazyZip(// bar
zs)
/* a *//* b */ xs /* c */.lazyZip(/* d */ ys /* e */).lazyZip(/* f */ zs /* g */)/* h *//* i *//* j */
coll(1).lazyZip(coll(2)).lazyZip(coll(3))
List(1, 2, 3).lazyZip(Set(1, 2, 3)).lazyZip(LazyList.from(1))
}
def coll(x: Int): List[Int] = ???
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package fix

import scala.collection.mutable

class Collectionstrawman_v0_copyToBuffer(xs: List[Int], b: mutable.Buffer[Int]) {

b ++= xs
b ++= xs ++ xs

}
105 changes: 105 additions & 0 deletions scalafix/2.13/rules/src/main/scala/fix/Collectionstrawman_v0.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package fix

import scalafix._
import scalafix.syntax._
import scalafix.util._
import scala.meta._

case class Collectionstrawman_v0(index: SemanticdbIndex)
extends SemanticRule(index, "Collectionstrawman_v0") {

def replaceSymbols(ctx: RuleCtx): Patch = {
ctx.replaceSymbols(
"scala.Stream" -> "scala.LazyList",
"scala.collection.immutable.Stream" -> "scala.collection.immutable.LazyList",
"scala.Traversable" -> "scala.Iterable",
"scala.collection.Traversable" -> "scala.collection.Iterable",
"scala.TraversableOnce" -> "scala.IterableOnce",
"scala.collection.TraversableOnce" -> "scala.collection.IterableOnce"
)
}

val toTpe = SymbolMatcher.normalized(
Symbol("_root_.scala.collection.TraversableLike.to.")
)
val iterator = SymbolMatcher.normalized(
Symbol("_root_.scala.collection.LinearSeqLike.iterator."),
Symbol("_root_.scala.collection.TraversableLike.toIterator.")
)
val tupleZipped = SymbolMatcher.normalized(
Symbol("_root_.scala.runtime.Tuple2Zipped.Ops.zipped."),
Symbol("_root_.scala.runtime.Tuple3Zipped.Ops.zipped.")
)

def replaceToList(ctx: RuleCtx) =
ctx.tree.collect {
case iterator(t: Name) =>
ctx.replaceTree(t, "iterator()")
case toTpe(n: Name) =>
(for {
name <- n.tokens.lastOption
open <- ctx.tokenList.find(name)(t => t.is[Token.LeftBracket])
close <- ctx.matchingParens.close(open.asInstanceOf[Token.LeftBracket])
} yield
ctx.replaceToken(open, "(") +
ctx.replaceToken(close, ")")
).asPatch
}.asPatch

def replaceTupleZipped(ctx: RuleCtx) =
ctx.tree.collect {
case tupleZipped(Term.Select(Term.Tuple(args), name)) =>
val removeTokensPatch =
(for {
zipped <- name.tokens.headOption
closeTuple <- ctx.tokenList.leading(zipped).find(_.is[Token.RightParen])
openTuple <- ctx.matchingParens.open(closeTuple.asInstanceOf[Token.RightParen])
maybeDot = ctx.tokenList.slice(closeTuple, zipped).find(_.is[Token.Dot])
} yield {
ctx.removeToken(openTuple) +
maybeDot.map(ctx.removeToken).asPatch +
ctx.removeToken(zipped)
}).asPatch

def removeSurroundingWhiteSpaces(tk: Token) =
(ctx.tokenList.trailing(tk).takeWhile(_.is[Token.Space]).map(ctx.removeToken) ++
ctx.tokenList.leading(tk).takeWhile(_.is[Token.Space]).map(ctx.removeToken)).asPatch

val commas =
for {
(prev, next) <- args.zip(args.tail)
tokensBetweenArgs = ctx.tokenList.slice(prev.tokens.last, next.tokens.head)
comma <- tokensBetweenArgs.find(_.is[Token.Comma])
} yield comma

val replaceCommasPatch = commas match {
case head :: tail =>
ctx.replaceToken(head, ".lazyZip(") +
removeSurroundingWhiteSpaces(head) ++
tail.map { comma =>
ctx.replaceToken(comma, ").lazyZip(") +
removeSurroundingWhiteSpaces(comma)
}
case _ => Patch.empty
}

removeTokensPatch + replaceCommasPatch
}.asPatch

val copyToBuffer = SymbolMatcher.normalized(
Symbol("_root_.scala.collection.TraversableOnce.copyToBuffer.")
)

def replaceCopyToBuffer(ctx: RuleCtx): Patch =
ctx.tree.collect {
case t @ q"${copyToBuffer(Term.Select(collection, _))}($buffer)" =>
Copy link
Contributor

Choose a reason for hiding this comment

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

Pro tip, you can put the collection qualifier like this to avoid Term.Select

@ val q"$qual.${q"foo"}(..$arg)" = q"qual.foo(1, 2)"
qual: Term = Term.Name("qual")
arg: List[Term] = List(Lit.Int(1), Lit.Int(2))

People have different tastes however, I personally prefer Term.Select :)

ctx.replaceTree(t, q"$buffer ++= $collection".syntax)
}.asPatch

override def fix(ctx: RuleCtx): Patch = {
replaceToList(ctx) +
replaceSymbols(ctx) +
replaceTupleZipped(ctx) +
replaceCopyToBuffer(ctx)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package fix

import scala.meta._
import scalafix.testkit._
import scalafix._

class Collectionstrawman_Tests
extends SemanticRuleSuite(
SemanticdbIndex.load(Classpath(AbsolutePath(BuildInfo.inputClassdirectory))),
AbsolutePath(BuildInfo.inputSourceroot),
Seq(AbsolutePath(BuildInfo.outputSourceroot))
) {
override def assertNoDiff(a: String, b: String, c: String) = {
super.assertNoDiff(a, b, c)
}
runAllTests()
}
Loading