From f1a0885167dfc3b61eaf9bb2bc4b37575b34e9f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillaume=20Mass=C3=A9?= Date: Mon, 25 Jun 2018 14:22:24 +0200 Subject: [PATCH 1/5] Rewrite Map.zip -> Map.zip.toMap --- .../input/src/main/scala/fix/SetMapSrc.scala | 1 + .../output/src/main/scala/fix/SetMapSrc.scala | 1 + ...Scalacollectioncompat_newcollections.scala | 19 ++++++++++++++++--- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/scalafix/input/src/main/scala/fix/SetMapSrc.scala b/scalafix/input/src/main/scala/fix/SetMapSrc.scala index 0597c513..b0758173 100644 --- a/scalafix/input/src/main/scala/fix/SetMapSrc.scala +++ b/scalafix/input/src/main/scala/fix/SetMapSrc.scala @@ -9,4 +9,5 @@ class SetMapSrc(set: Set[Int], map: Map[Int, Int]) { (set + (2, 3)).map(x => x) set + (2, 3) - 4 map.mapValues(_ + 1) + map.zip(List()) } \ No newline at end of file diff --git a/scalafix/output/src/main/scala/fix/SetMapSrc.scala b/scalafix/output/src/main/scala/fix/SetMapSrc.scala index 978b0bfa..2661ed49 100644 --- a/scalafix/output/src/main/scala/fix/SetMapSrc.scala +++ b/scalafix/output/src/main/scala/fix/SetMapSrc.scala @@ -9,4 +9,5 @@ class SetMapSrc(set: Set[Int], map: Map[Int, Int]) { (set + 2 + 3).map(x => x) set + 2 + 3 - 4 map.mapValues(_ + 1).toMap + map.zip(List()).toMap } \ No newline at end of file diff --git a/scalafix/rules/src/main/scala/fix/Scalacollectioncompat_newcollections.scala b/scalafix/rules/src/main/scala/fix/Scalacollectioncompat_newcollections.scala index 4e0bc795..8caf9959 100644 --- a/scalafix/rules/src/main/scala/fix/Scalacollectioncompat_newcollections.scala +++ b/scalafix/rules/src/main/scala/fix/Scalacollectioncompat_newcollections.scala @@ -110,11 +110,17 @@ case class Scalacollectioncompat_newcollections(index: SemanticdbIndex) Symbol("_root_.scala.collection.mutable.SetLike.retain.") ) + val arrayBuilderMake = SymbolMatcher.normalized( Symbol("_root_.scala.collection.mutable.ArrayBuilder.make(Lscala/reflect/ClassTag;)Lscala/collection/mutable/ArrayBuilder;.") ) + val mapZip = + SymbolMatcher.exact( + Symbol("_root_.scala.collection.IterableLike#zip(Lscala/collection/GenIterable;Lscala/collection/generic/CanBuildFrom;)Ljava/lang/Object;.") + ) + def replaceMutableSet(ctx: RuleCtx) = ctx.tree.collect { case retainSet(n: Name) => @@ -278,10 +284,16 @@ case class Scalacollectioncompat_newcollections(index: SemanticdbIndex) case ap @ Term.Apply(at @ Term.ApplyType(Term.Select(lhs, arrayBuilderMake(_)), args), Nil) => val extraParens = ap.tokens.slice(at.tokens.size, ap.tokens.size) - ctx.removeTokens(extraParens) }.asPatch } + + def replaceMapZip(ctx: RuleCtx): Patch = { + ctx.tree.collect { + case ap @ Term.Apply(Term.Select(_, mapZip(_)), List(_)) => + ctx.addRight(ap, ".toMap") + }.asPatch + } def replaceMapMapValues(ctx: RuleCtx): Patch = { ctx.tree.collect { @@ -485,7 +497,8 @@ case class Scalacollectioncompat_newcollections(index: SemanticdbIndex) replaceMutSetMapPlus(ctx) + replaceMutMapUpdated(ctx) + replaceArrayBuilderMake(ctx) + - replaceMapMapValues(ctx) + - replaceIterableSameElements(ctx) + replaceIterableSameElements(ctx) + + replaceMapZip(ctx) + + replaceMapMapValues(ctx) } } From 878ab35b2ee83baf837a17bf87cc59d4085f12e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillaume=20Mass=C3=A9?= Date: Mon, 25 Jun 2018 16:07:06 +0200 Subject: [PATCH 2/5] Rewrite Map.zip. make sure we target Map --- .../input/src/main/scala/fix/SetMapSrc.scala | 1 + .../output/src/main/scala/fix/SetMapSrc.scala | 1 + ...Scalacollectioncompat_newcollections.scala | 24 ++++++++++++++++++- 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/scalafix/input/src/main/scala/fix/SetMapSrc.scala b/scalafix/input/src/main/scala/fix/SetMapSrc.scala index b0758173..842b0eec 100644 --- a/scalafix/input/src/main/scala/fix/SetMapSrc.scala +++ b/scalafix/input/src/main/scala/fix/SetMapSrc.scala @@ -10,4 +10,5 @@ class SetMapSrc(set: Set[Int], map: Map[Int, Int]) { set + (2, 3) - 4 map.mapValues(_ + 1) map.zip(List()) + List().zip(List()) } \ No newline at end of file diff --git a/scalafix/output/src/main/scala/fix/SetMapSrc.scala b/scalafix/output/src/main/scala/fix/SetMapSrc.scala index 2661ed49..282b69f5 100644 --- a/scalafix/output/src/main/scala/fix/SetMapSrc.scala +++ b/scalafix/output/src/main/scala/fix/SetMapSrc.scala @@ -10,4 +10,5 @@ class SetMapSrc(set: Set[Int], map: Map[Int, Int]) { set + 2 + 3 - 4 map.mapValues(_ + 1).toMap map.zip(List()).toMap + List().zip(List()) } \ No newline at end of file diff --git a/scalafix/rules/src/main/scala/fix/Scalacollectioncompat_newcollections.scala b/scalafix/rules/src/main/scala/fix/Scalacollectioncompat_newcollections.scala index 8caf9959..0d5e9f4c 100644 --- a/scalafix/rules/src/main/scala/fix/Scalacollectioncompat_newcollections.scala +++ b/scalafix/rules/src/main/scala/fix/Scalacollectioncompat_newcollections.scala @@ -26,6 +26,28 @@ case class Scalacollectioncompat_newcollections(index: SemanticdbIndex) close <- ctx.matchingParens.close(open) } yield (open, close) + // terms dont give us terms https://github.com/scalameta/scalameta/issues/1212 + // if we have a simple identifier, we can look at his definition at query it's type + // this should be improved in future version of scalameta + object TypeMatcher { + def apply(symbols: Symbol*)(implicit index: SemanticdbIndex): TypeMatcher = + new TypeMatcher(symbols: _*)(index) + } + + final class TypeMatcher(symbols: Symbol*)(implicit index: SemanticdbIndex) { + def unapply(tree: Tree): Boolean = { + index.denotation(tree) + .map(_.names.headOption.exists(n => symbols.exists(_ == n.symbol))) + .getOrElse(false) + } + } + + val CollectionMap: TypeMatcher = TypeMatcher( + Symbol("_root_.scala.collection.immutable.Map#"), + Symbol("_root_.scala.collection.mutable.Map#"), + Symbol("_root_.scala.Predef.Map#") + ) + def replaceSymbols(ctx: RuleCtx): Patch = { ctx.replaceSymbols( "scala.collection.LinearSeq" -> "scala.collection.immutable.List", @@ -290,7 +312,7 @@ case class Scalacollectioncompat_newcollections(index: SemanticdbIndex) def replaceMapZip(ctx: RuleCtx): Patch = { ctx.tree.collect { - case ap @ Term.Apply(Term.Select(_, mapZip(_)), List(_)) => + case ap @ Term.Apply(Term.Select(CollectionMap(), mapZip(_)), List(_)) => ctx.addRight(ap, ".toMap") }.asPatch } From a69a407e510888e075916f243ccdc29a64eb5a1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillaume=20Mass=C3=A9?= Date: Mon, 25 Jun 2018 17:03:04 +0200 Subject: [PATCH 3/5] Rewrite: TypeMatcher warning about hack --- .../scala/fix/Scalacollectioncompat_newcollections.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scalafix/rules/src/main/scala/fix/Scalacollectioncompat_newcollections.scala b/scalafix/rules/src/main/scala/fix/Scalacollectioncompat_newcollections.scala index 0d5e9f4c..0377292c 100644 --- a/scalafix/rules/src/main/scala/fix/Scalacollectioncompat_newcollections.scala +++ b/scalafix/rules/src/main/scala/fix/Scalacollectioncompat_newcollections.scala @@ -27,6 +27,8 @@ case class Scalacollectioncompat_newcollections(index: SemanticdbIndex) } yield (open, close) // terms dont give us terms https://github.com/scalameta/scalameta/issues/1212 + // WARNING: TOTAL HACK + // this is only to unblock us until Term.tpe is available: https://github.com/scalameta/scalameta/issues/1212 // if we have a simple identifier, we can look at his definition at query it's type // this should be improved in future version of scalameta object TypeMatcher { @@ -37,8 +39,7 @@ case class Scalacollectioncompat_newcollections(index: SemanticdbIndex) final class TypeMatcher(symbols: Symbol*)(implicit index: SemanticdbIndex) { def unapply(tree: Tree): Boolean = { index.denotation(tree) - .map(_.names.headOption.exists(n => symbols.exists(_ == n.symbol))) - .getOrElse(false) + .exists(_.names.headOption.exists(n => symbols.exists(_ == n.symbol))) } } From 2aef94231c309961fc81928558919302ce544f8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillaume=20Mass=C3=A9?= Date: Mon, 25 Jun 2018 13:32:18 +0200 Subject: [PATCH 4/5] Rewrite collection.{Map, Set}.+ and collection.Set.- This is incomplete since, it's blocked by https://github.com/scalameta/scalameta/issues/1212 (Add support to query type of a term) iset: immutable.Set[Int] cset: collecion.Set[Int] both have +/- implemented via SetLike given iset + 1 cset + 1 we know that + is from SetLike, but it's not possible to get the type of iset/cset Rewrite collection.Set.+ restrict type of lhs scalafix allow us to match on SetLike.+ but we cannot check the type of the expression on the lhs We can work arround this limitation if the lhs is a identifier (simple case). This allow us to document the expected type on the lhs and prepare the implementation once https://github.com/scalameta/scalameta/issues/1212 is resolved --- .../input/src/main/scala/fix/SetMapSrc.scala | 29 ++++++++--- .../output/src/main/scala/fix/SetMapSrc.scala | 29 ++++++++--- ...Scalacollectioncompat_newcollections.scala | 48 ++++++++++++++++++- 3 files changed, 91 insertions(+), 15 deletions(-) diff --git a/scalafix/input/src/main/scala/fix/SetMapSrc.scala b/scalafix/input/src/main/scala/fix/SetMapSrc.scala index 842b0eec..5ca97469 100644 --- a/scalafix/input/src/main/scala/fix/SetMapSrc.scala +++ b/scalafix/input/src/main/scala/fix/SetMapSrc.scala @@ -3,12 +3,27 @@ rule = "scala:fix.Scalacollectioncompat_newcollections" */ package fix -class SetMapSrc(set: Set[Int], map: Map[Int, Int]) { - set + (2, 3) - map + (2 -> 3, 3 -> 4) - (set + (2, 3)).map(x => x) - set + (2, 3) - 4 - map.mapValues(_ + 1) - map.zip(List()) +import scala.collection +import scala.collection.immutable +import scala.collection.mutable.{Map, Set} // Challenge to make sure the scoping is correct + +class SetMapSrc(iset: immutable.Set[Int], + cset: collection.Set[Int], + imap: immutable.Map[Int, Int], + cmap: collection.Map[Int, Int]) { + iset + (2, 3) + imap + (2 -> 3, 3 -> 4) + (iset + (2, 3)).toString + iset + (2, 3) - 4 + imap.mapValues(_ + 1) + iset + 1 + iset - 2 + cset + 1 + cset - 2 + cmap + (2 -> 3) + cmap + ((4, 5)) + imap + (2 -> 3) + imap + ((4, 5)) + imap.zip(List()) List().zip(List()) } \ No newline at end of file diff --git a/scalafix/output/src/main/scala/fix/SetMapSrc.scala b/scalafix/output/src/main/scala/fix/SetMapSrc.scala index 282b69f5..aca6678f 100644 --- a/scalafix/output/src/main/scala/fix/SetMapSrc.scala +++ b/scalafix/output/src/main/scala/fix/SetMapSrc.scala @@ -3,12 +3,27 @@ package fix -class SetMapSrc(set: Set[Int], map: Map[Int, Int]) { - set + 2 + 3 - map + (2 -> 3) + (3 -> 4) - (set + 2 + 3).map(x => x) - set + 2 + 3 - 4 - map.mapValues(_ + 1).toMap - map.zip(List()).toMap +import scala.collection +import scala.collection.immutable +import scala.collection.mutable.{Map, Set} // Challenge to make sure the scoping is correct + +class SetMapSrc(iset: immutable.Set[Int], + cset: collection.Set[Int], + imap: immutable.Map[Int, Int], + cmap: collection.Map[Int, Int]) { + iset + 2 + 3 + imap + (2 -> 3) + (3 -> 4) + (iset + 2 + 3).toString + iset + 2 + 3 - 4 + imap.mapValues(_ + 1).toMap + iset + 1 + iset - 2 + cset ++ _root_.scala.collection.Set(1) + cset -- _root_.scala.collection.Set(2) + cmap ++ _root_.scala.collection.Map(2 -> 3) + cmap ++ _root_.scala.collection.Map((4, 5)) + imap + (2 -> 3) + imap + ((4, 5)) + imap.zip(List()).toMap List().zip(List()) } \ No newline at end of file diff --git a/scalafix/rules/src/main/scala/fix/Scalacollectioncompat_newcollections.scala b/scalafix/rules/src/main/scala/fix/Scalacollectioncompat_newcollections.scala index 0377292c..502f9bfd 100644 --- a/scalafix/rules/src/main/scala/fix/Scalacollectioncompat_newcollections.scala +++ b/scalafix/rules/src/main/scala/fix/Scalacollectioncompat_newcollections.scala @@ -49,6 +49,8 @@ case class Scalacollectioncompat_newcollections(index: SemanticdbIndex) Symbol("_root_.scala.Predef.Map#") ) + val CollectionSet: TypeMatcher = TypeMatcher(Symbol("_root_.scala.collection.Set#")) + def replaceSymbols(ctx: RuleCtx): Patch = { ctx.replaceSymbols( "scala.collection.LinearSeq" -> "scala.collection.immutable.List", @@ -86,6 +88,18 @@ case class Scalacollectioncompat_newcollections(index: SemanticdbIndex) Symbol("_root_.scala.runtime.Tuple2Zipped.Ops.zipped."), Symbol("_root_.scala.runtime.Tuple3Zipped.Ops.zipped.") ) + val setPlus = + SymbolMatcher.exact( + Symbol("_root_.scala.collection.SetLike#`+`(Ljava/lang/Object;)Lscala/collection/Set;.") + ) + val setMinus = + SymbolMatcher.exact( + Symbol("_root_.scala.collection.SetLike#`-`(Ljava/lang/Object;)Lscala/collection/Set;.") + ) + val mapPlus = + SymbolMatcher.exact( + Symbol("_root_.scala.collection.MapLike#`+`(Lscala/Tuple2;)Lscala/collection/Map;.") + ) val setPlus2 = SymbolMatcher.exact( Symbol("_root_.scala.collection.SetLike#`+`(Ljava/lang/Object;Ljava/lang/Object;Lscala/collection/Seq;)Lscala/collection/Set;.") ) @@ -144,6 +158,9 @@ case class Scalacollectioncompat_newcollections(index: SemanticdbIndex) Symbol("_root_.scala.collection.IterableLike#zip(Lscala/collection/GenIterable;Lscala/collection/generic/CanBuildFrom;)Ljava/lang/Object;.") ) + def startsWithParens(tree: Tree): Boolean = + tree.tokens.headOption.map(_.is[Token.LeftParen]).getOrElse(false) + def replaceMutableSet(ctx: RuleCtx) = ctx.tree.collect { case retainSet(n: Name) => @@ -245,7 +262,7 @@ case class Scalacollectioncompat_newcollections(index: SemanticdbIndex) def replaceSetMapPlus2(ctx: RuleCtx): Patch = { def rewritePlus(ap: Term.ApplyInfix, lhs: Term, op: Term.Name, rhs1: Term, rhs2: Term): Patch = { val tokensToReplace = - if(ap.tokens.headOption.map(_.is[Token.LeftParen]).getOrElse(false)) { + if(startsWithParens(ap)) { // don't drop surrounding parens ap.tokens.slice(1, ap.tokens.size - 1) } else ap.tokens @@ -270,6 +287,34 @@ case class Scalacollectioncompat_newcollections(index: SemanticdbIndex) }.asPatch } + def replaceSetMapPlusMinus(ctx: RuleCtx): Patch = { + def rewriteOp(op: Tree, rhs: Tree, doubleOp: String, col0: String): Patch = { + val col = "_root_.scala.collection." + col0 + val callSite = + if (startsWithParens(rhs)) { + ctx.addLeft(rhs, col) + } + else { + ctx.addLeft(rhs, col + "(") + + ctx.addRight(rhs, ")") + } + + ctx.addRight(op, doubleOp) + callSite + } + + ctx.tree.collect { + case Term.ApplyInfix(CollectionSet(), op @ setPlus(_), Nil, List(rhs)) => + rewriteOp(op, rhs, "+", "Set") + + case Term.ApplyInfix(CollectionSet(), op @ setMinus(_), Nil, List(rhs)) => + rewriteOp(op, rhs, "-", "Set") + + case Term.ApplyInfix(_, op @ mapPlus(_), Nil, List(rhs)) => + rewriteOp(op, rhs, "+", "Map") + }.asPatch + } + + def replaceMutSetMapPlus(ctx: RuleCtx): Patch = { def rewriteMutPlus(lhs: Term, op: Term.Name): Patch = { ctx.addRight(lhs, ".clone()") + @@ -517,6 +562,7 @@ case class Scalacollectioncompat_newcollections(index: SemanticdbIndex) replaceMutableSet(ctx) + replaceSymbolicFold(ctx) + replaceSetMapPlus2(ctx) + + replaceSetMapPlusMinus(ctx) + replaceMutSetMapPlus(ctx) + replaceMutMapUpdated(ctx) + replaceArrayBuilderMake(ctx) + From e1ca895687a4fcdacb87166b568e75022211fb66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillaume=20Mass=C3=A9?= Date: Wed, 27 Jun 2018 11:56:05 +0200 Subject: [PATCH 5/5] Rewrite: Move Set/Map.{+,-} and Map.zip to Experimental rules. Terms don't have type in scalameta yet: https://github.com/scalameta/scalameta/issues/1212 The SymbolMatcher is not enought for those rules since we need to check the type of the lhs. One workarround is to jump to the symbol if we see an identifier on the lhs. for example: iset: immutable.Set[Int] // ... iset + 1 we can jump to the symbol of iset and get it's type. This is really fragile, since it's not going to work if we have aliases/subtyping. However, it's useful to create those rules, since when we are unblock by scalameta#1212 it's trivial to add the remaining piece --- .../src/main/scala/fix/ExperimentalSrc.scala | 27 ++++++ .../input/src/main/scala/fix/SetMapSrc.scala | 29 ++---- .../src/main/scala/fix/ExperimentalSrc.scala | 27 ++++++ .../output/src/main/scala/fix/SetMapSrc.scala | 29 ++---- .../src/main/scala/fix/Experimental.scala | 93 +++++++++++++++++++ ...Scalacollectioncompat_newcollections.scala | 54 +---------- 6 files changed, 160 insertions(+), 99 deletions(-) create mode 100644 scalafix/input/src/main/scala/fix/ExperimentalSrc.scala create mode 100644 scalafix/output/src/main/scala/fix/ExperimentalSrc.scala create mode 100644 scalafix/rules/src/main/scala/fix/Experimental.scala diff --git a/scalafix/input/src/main/scala/fix/ExperimentalSrc.scala b/scalafix/input/src/main/scala/fix/ExperimentalSrc.scala new file mode 100644 index 00000000..1cf22c35 --- /dev/null +++ b/scalafix/input/src/main/scala/fix/ExperimentalSrc.scala @@ -0,0 +1,27 @@ +/* +rule = "scala:fix.Experimental" + */ +package fix + +import scala.collection +import scala.collection.immutable +import scala.collection.mutable.{Map, Set} // Challenge to make sure the scoping is correct + +class ExperimentalSrc(iset: immutable.Set[Int], + cset: collection.Set[Int], + imap: immutable.Map[Int, Int], + cmap: collection.Map[Int, Int]) { + iset + 1 + iset - 2 + cset + 1 + cset - 2 + + cmap + (2 -> 3) + cmap + ((4, 5)) + imap + (2 -> 3) + imap + ((4, 5)) + + // Map.zip + imap.zip(List()) + List().zip(List()) +} \ No newline at end of file diff --git a/scalafix/input/src/main/scala/fix/SetMapSrc.scala b/scalafix/input/src/main/scala/fix/SetMapSrc.scala index 5ca97469..7f70e202 100644 --- a/scalafix/input/src/main/scala/fix/SetMapSrc.scala +++ b/scalafix/input/src/main/scala/fix/SetMapSrc.scala @@ -3,27 +3,10 @@ rule = "scala:fix.Scalacollectioncompat_newcollections" */ package fix -import scala.collection -import scala.collection.immutable -import scala.collection.mutable.{Map, Set} // Challenge to make sure the scoping is correct - -class SetMapSrc(iset: immutable.Set[Int], - cset: collection.Set[Int], - imap: immutable.Map[Int, Int], - cmap: collection.Map[Int, Int]) { - iset + (2, 3) - imap + (2 -> 3, 3 -> 4) - (iset + (2, 3)).toString - iset + (2, 3) - 4 - imap.mapValues(_ + 1) - iset + 1 - iset - 2 - cset + 1 - cset - 2 - cmap + (2 -> 3) - cmap + ((4, 5)) - imap + (2 -> 3) - imap + ((4, 5)) - imap.zip(List()) - List().zip(List()) +class SetMapSrc(set: Set[Int], map: Map[Int, Int]) { + set + (2, 3) + map + (2 -> 3, 3 -> 4) + (set + (2, 3)).toString + set + (2, 3) - 4 + map.mapValues(_ + 1) } \ No newline at end of file diff --git a/scalafix/output/src/main/scala/fix/ExperimentalSrc.scala b/scalafix/output/src/main/scala/fix/ExperimentalSrc.scala new file mode 100644 index 00000000..fbd96669 --- /dev/null +++ b/scalafix/output/src/main/scala/fix/ExperimentalSrc.scala @@ -0,0 +1,27 @@ + + + +package fix + +import scala.collection +import scala.collection.immutable +import scala.collection.mutable.{Map, Set} // Challenge to make sure the scoping is correct + +class ExperimentalSrc(iset: immutable.Set[Int], + cset: collection.Set[Int], + imap: immutable.Map[Int, Int], + cmap: collection.Map[Int, Int]) { + iset + 1 + iset - 2 + cset ++ _root_.scala.collection.Set(1) + cset -- _root_.scala.collection.Set(2) + + cmap ++ _root_.scala.collection.Map(2 -> 3) + cmap ++ _root_.scala.collection.Map((4, 5)) + imap + (2 -> 3) + imap + ((4, 5)) + + // Map.zip + imap.zip(List()).toMap + List().zip(List()) +} \ No newline at end of file diff --git a/scalafix/output/src/main/scala/fix/SetMapSrc.scala b/scalafix/output/src/main/scala/fix/SetMapSrc.scala index aca6678f..526be356 100644 --- a/scalafix/output/src/main/scala/fix/SetMapSrc.scala +++ b/scalafix/output/src/main/scala/fix/SetMapSrc.scala @@ -3,27 +3,10 @@ package fix -import scala.collection -import scala.collection.immutable -import scala.collection.mutable.{Map, Set} // Challenge to make sure the scoping is correct - -class SetMapSrc(iset: immutable.Set[Int], - cset: collection.Set[Int], - imap: immutable.Map[Int, Int], - cmap: collection.Map[Int, Int]) { - iset + 2 + 3 - imap + (2 -> 3) + (3 -> 4) - (iset + 2 + 3).toString - iset + 2 + 3 - 4 - imap.mapValues(_ + 1).toMap - iset + 1 - iset - 2 - cset ++ _root_.scala.collection.Set(1) - cset -- _root_.scala.collection.Set(2) - cmap ++ _root_.scala.collection.Map(2 -> 3) - cmap ++ _root_.scala.collection.Map((4, 5)) - imap + (2 -> 3) - imap + ((4, 5)) - imap.zip(List()).toMap - List().zip(List()) +class SetMapSrc(set: Set[Int], map: Map[Int, Int]) { + set + 2 + 3 + map + (2 -> 3) + (3 -> 4) + (set + 2 + 3).toString + set + 2 + 3 - 4 + map.mapValues(_ + 1).toMap } \ No newline at end of file diff --git a/scalafix/rules/src/main/scala/fix/Experimental.scala b/scalafix/rules/src/main/scala/fix/Experimental.scala new file mode 100644 index 00000000..0ffd9b11 --- /dev/null +++ b/scalafix/rules/src/main/scala/fix/Experimental.scala @@ -0,0 +1,93 @@ +package fix + +import scalafix._ +import scalafix.syntax._ +import scalafix.util._ +import scala.meta._ + +case class Experimental(index: SemanticdbIndex) extends SemanticRule(index, "Experimental") { + // WARNING: TOTAL HACK + // this is only to unblock us until Term.tpe is available: https://github.com/scalameta/scalameta/issues/1212 + // if we have a simple identifier, we can look at his definition at query it's type + // this should be improved in future version of scalameta + object TypeMatcher { + def apply(symbols: Symbol*)(implicit index: SemanticdbIndex): TypeMatcher = + new TypeMatcher(symbols: _*)(index) + } + + final class TypeMatcher(symbols: Symbol*)(implicit index: SemanticdbIndex) { + def unapply(tree: Tree): Boolean = { + index.denotation(tree) + .exists(_.names.headOption.exists(n => symbols.exists(_ == n.symbol))) + } + } + + val CollectionMap: TypeMatcher = TypeMatcher( + Symbol("_root_.scala.collection.immutable.Map#"), + Symbol("_root_.scala.collection.mutable.Map#"), + Symbol("_root_.scala.Predef.Map#") + ) + + val CollectionSet: TypeMatcher = TypeMatcher(Symbol("_root_.scala.collection.Set#")) + + val mapZip = + SymbolMatcher.exact( + Symbol("_root_.scala.collection.IterableLike#zip(Lscala/collection/GenIterable;Lscala/collection/generic/CanBuildFrom;)Ljava/lang/Object;.") + ) + + val mapPlus = + SymbolMatcher.exact( + Symbol("_root_.scala.collection.MapLike#`+`(Lscala/Tuple2;)Lscala/collection/Map;.") + ) + + val setPlus = + SymbolMatcher.exact( + Symbol("_root_.scala.collection.SetLike#`+`(Ljava/lang/Object;)Lscala/collection/Set;.") + ) + + val setMinus = + SymbolMatcher.exact( + Symbol("_root_.scala.collection.SetLike#`-`(Ljava/lang/Object;)Lscala/collection/Set;.") + ) + + def startsWithParens(tree: Tree): Boolean = + tree.tokens.headOption.map(_.is[Token.LeftParen]).getOrElse(false) + + def replaceMapZip(ctx: RuleCtx): Patch = { + ctx.tree.collect { + case ap @ Term.Apply(Term.Select(CollectionMap(), mapZip(_)), List(_)) => + ctx.addRight(ap, ".toMap") + }.asPatch + } + + def replaceSetMapPlusMinus(ctx: RuleCtx): Patch = { + def rewriteOp(op: Tree, rhs: Tree, doubleOp: String, col0: String): Patch = { + val col = "_root_.scala.collection." + col0 + val callSite = + if (startsWithParens(rhs)) { + ctx.addLeft(rhs, col) + } + else { + ctx.addLeft(rhs, col + "(") + + ctx.addRight(rhs, ")") + } + + ctx.addRight(op, doubleOp) + callSite + } + + ctx.tree.collect { + case Term.ApplyInfix(CollectionSet(), op @ setPlus(_), Nil, List(rhs)) => + rewriteOp(op, rhs, "+", "Set") + + case Term.ApplyInfix(CollectionSet(), op @ setMinus(_), Nil, List(rhs)) => + rewriteOp(op, rhs, "-", "Set") + + case Term.ApplyInfix(_, op @ mapPlus(_), Nil, List(rhs)) => + rewriteOp(op, rhs, "+", "Map") + }.asPatch + } + + override def fix(ctx: RuleCtx): Patch = + replaceSetMapPlusMinus(ctx) + + replaceMapZip(ctx) +} \ No newline at end of file diff --git a/scalafix/rules/src/main/scala/fix/Scalacollectioncompat_newcollections.scala b/scalafix/rules/src/main/scala/fix/Scalacollectioncompat_newcollections.scala index 502f9bfd..34ef7049 100644 --- a/scalafix/rules/src/main/scala/fix/Scalacollectioncompat_newcollections.scala +++ b/scalafix/rules/src/main/scala/fix/Scalacollectioncompat_newcollections.scala @@ -88,18 +88,6 @@ case class Scalacollectioncompat_newcollections(index: SemanticdbIndex) Symbol("_root_.scala.runtime.Tuple2Zipped.Ops.zipped."), Symbol("_root_.scala.runtime.Tuple3Zipped.Ops.zipped.") ) - val setPlus = - SymbolMatcher.exact( - Symbol("_root_.scala.collection.SetLike#`+`(Ljava/lang/Object;)Lscala/collection/Set;.") - ) - val setMinus = - SymbolMatcher.exact( - Symbol("_root_.scala.collection.SetLike#`-`(Ljava/lang/Object;)Lscala/collection/Set;.") - ) - val mapPlus = - SymbolMatcher.exact( - Symbol("_root_.scala.collection.MapLike#`+`(Lscala/Tuple2;)Lscala/collection/Map;.") - ) val setPlus2 = SymbolMatcher.exact( Symbol("_root_.scala.collection.SetLike#`+`(Ljava/lang/Object;Ljava/lang/Object;Lscala/collection/Seq;)Lscala/collection/Set;.") ) @@ -153,11 +141,6 @@ case class Scalacollectioncompat_newcollections(index: SemanticdbIndex) Symbol("_root_.scala.collection.mutable.ArrayBuilder.make(Lscala/reflect/ClassTag;)Lscala/collection/mutable/ArrayBuilder;.") ) - val mapZip = - SymbolMatcher.exact( - Symbol("_root_.scala.collection.IterableLike#zip(Lscala/collection/GenIterable;Lscala/collection/generic/CanBuildFrom;)Ljava/lang/Object;.") - ) - def startsWithParens(tree: Tree): Boolean = tree.tokens.headOption.map(_.is[Token.LeftParen]).getOrElse(false) @@ -287,34 +270,6 @@ case class Scalacollectioncompat_newcollections(index: SemanticdbIndex) }.asPatch } - def replaceSetMapPlusMinus(ctx: RuleCtx): Patch = { - def rewriteOp(op: Tree, rhs: Tree, doubleOp: String, col0: String): Patch = { - val col = "_root_.scala.collection." + col0 - val callSite = - if (startsWithParens(rhs)) { - ctx.addLeft(rhs, col) - } - else { - ctx.addLeft(rhs, col + "(") + - ctx.addRight(rhs, ")") - } - - ctx.addRight(op, doubleOp) + callSite - } - - ctx.tree.collect { - case Term.ApplyInfix(CollectionSet(), op @ setPlus(_), Nil, List(rhs)) => - rewriteOp(op, rhs, "+", "Set") - - case Term.ApplyInfix(CollectionSet(), op @ setMinus(_), Nil, List(rhs)) => - rewriteOp(op, rhs, "-", "Set") - - case Term.ApplyInfix(_, op @ mapPlus(_), Nil, List(rhs)) => - rewriteOp(op, rhs, "+", "Map") - }.asPatch - } - - def replaceMutSetMapPlus(ctx: RuleCtx): Patch = { def rewriteMutPlus(lhs: Term, op: Term.Name): Patch = { ctx.addRight(lhs, ".clone()") + @@ -356,12 +311,7 @@ case class Scalacollectioncompat_newcollections(index: SemanticdbIndex) }.asPatch } - def replaceMapZip(ctx: RuleCtx): Patch = { - ctx.tree.collect { - case ap @ Term.Apply(Term.Select(CollectionMap(), mapZip(_)), List(_)) => - ctx.addRight(ap, ".toMap") - }.asPatch - } + def replaceMapMapValues(ctx: RuleCtx): Patch = { ctx.tree.collect { @@ -562,12 +512,10 @@ case class Scalacollectioncompat_newcollections(index: SemanticdbIndex) replaceMutableSet(ctx) + replaceSymbolicFold(ctx) + replaceSetMapPlus2(ctx) + - replaceSetMapPlusMinus(ctx) + replaceMutSetMapPlus(ctx) + replaceMutMapUpdated(ctx) + replaceArrayBuilderMake(ctx) + replaceIterableSameElements(ctx) + - replaceMapZip(ctx) + replaceMapMapValues(ctx) } }