Skip to content

Commit b84027f

Browse files
committed
feat: implicit conversions
1 parent 25b1eba commit b84027f

File tree

4 files changed

+103
-1
lines changed

4 files changed

+103
-1
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
rule = ImplicitConversions
3+
*/
4+
5+
package fix
6+
7+
import scala.language.implicitConversions
8+
9+
// format: off
10+
object ImplicitConversionsTest {
11+
implicit def conv1(x: Int): String = x.toString
12+
13+
implicit val conv2: Long => String = _.toString
14+
15+
def foo(x: Int)(implicit conv: Int => String): String = x
16+
17+
def foo2(x: Int)(implicit conv: (Int, Int) => String): String = x
18+
}
19+
// format: on
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package fix
2+
3+
import scala.language.implicitConversions
4+
5+
// format: off
6+
object ImplicitConversionsTest {
7+
implicit val conv1: Conversion[Int, String] = (x: Int) => x.toString
8+
9+
implicit val conv2: Conversion[Long, String] = _.toString
10+
11+
def foo(x: Int)(implicit conv: Conversion[Int, String]): String = x
12+
13+
def foo2(x: Int)(implicit conv: Conversion[(Int, Int), String]): String = x
14+
}
15+
// format: on

rules/src/main/resources/META-INF/services/scalafix.v1.Rule

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ fix.SemiAuto
22
fix.GivenAndUsing
33
fix.PackageObjectExport
44
fix.DropModThis
5-
fix.WildcardInitializer
5+
fix.WildcardInitializer
6+
fix.ImplicitConversions
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package fix
2+
3+
import fix.ImplicitConversions.LastImplicitTypeFunctionParams
4+
import scalafix.v1.{Patch, SemanticDocument, SemanticRule}
5+
6+
import scala.meta._
7+
import scala.meta.tokens.Token
8+
9+
class ImplicitConversions extends SemanticRule("ImplicitConversions") {
10+
11+
override def fix(implicit doc: SemanticDocument): Patch = {
12+
13+
doc.tree.collect {
14+
case Defn.Def(_, _, _, LastImplicitTypeFunctionParams(typeFunctions), _, _) =>
15+
typeFunctions.foldRight(Patch.empty) { case (tf, patches) =>
16+
patches + Patch.replaceTree(tf, conversionCode(tf.params, tf.res))
17+
}
18+
19+
case d @ Defn.Def(mods, Term.Name(_), _, (firstParam :: Nil) :: Nil, ot @ Some(outType: Type.Name), _)
20+
if mods.exists(_.is[Mod.Implicit]) && firstParam.mods.forall(_.isNot[Mod.Implicit]) =>
21+
(for {
22+
defkw <- d.tokens.find(_.is[Token.KwDef]).map(Patch.replaceToken(_, "val"))
23+
eqSign <- d.tokens.find(_.is[Token.Equals])
24+
returnTypeTree <- ot
25+
retType <- firstParam.decltpe
26+
argTokens = d.tokens.dropWhile(_.isNot[Token.LeftParen]).dropRightWhile(_.isNot[Token.RightParen])
27+
} yield defkw +
28+
Patch.removeTokens(argTokens) +
29+
Patch.addRight(eqSign, s" ${argTokens.toString()} =>") +
30+
Patch.replaceTree(returnTypeTree, conversionCode(retType, outType))).asPatch
31+
32+
case Defn.Val(mods, _, Some(ts: Type.Function), _) if mods.exists(_.is[Mod.Implicit]) =>
33+
ts.params match {
34+
case inType :: Nil => Patch.replaceTree(ts, conversionCode(inType, ts.res))
35+
case _ => Patch.empty
36+
}
37+
}.asPatch
38+
}
39+
40+
private def conversionCode(inType: Type, retType: Type): String =
41+
conversionCode(inType :: Nil, retType)
42+
43+
private def conversionCode(inType: List[Type], retType: Type): String = {
44+
val inStr = inType match {
45+
case one :: Nil => one.toString()
46+
case many => many.map(_.toString()).mkString("(", ", ", ")")
47+
}
48+
s"Conversion[$inStr, $retType]"
49+
}
50+
}
51+
52+
object ImplicitConversions {
53+
54+
object LastImplicitTypeFunctionParams {
55+
def unapply(params: List[List[Term.Param]]): Option[List[Type.Function]] = {
56+
val lastParams = params.lastOption
57+
if (lastParams.exists(_.exists(_.mods.exists(_.is[Mod.Implicit])))) {
58+
val tfs = lastParams.toList
59+
.flatMap(_.map(_.decltpe))
60+
.collect { case Some(tf: Type.Function) => tf }
61+
if (tfs.isEmpty) None
62+
else Some(tfs)
63+
} else None
64+
}
65+
}
66+
67+
}

0 commit comments

Comments
 (0)