Skip to content

Commit

Permalink
feat: implicit conversions
Browse files Browse the repository at this point in the history
  • Loading branch information
ingarabr committed Mar 25, 2023
1 parent 25b1eba commit 59daa38
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 1 deletion.
21 changes: 21 additions & 0 deletions input/src/main/scala-3/fix/ImplicitConversionsTest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
rule = ImplicitConversions
*/

package fix

import scala.language.implicitConversions

// format: off
object ImplicitConversionsTest {
implicit def implicitDefWithFunctionArg1(x: Int): String = x.toString
implicit def implicitDefWithFunctionArg2(x: (Int, Long)): String = x.toString

implicit val implicitValWithFunctionType1: Long => String = _.toString
implicit val implicitValWithFunctionType2: (Int, Long) => String = (i, l ) => i.toString

def defWithImplicitFunctionType1(x: Int)(implicit conv: Int => String): String = x
def defWithImplicitFunctionType2(x: Int)(implicit conv: (Int, Long) => String): String = x
def defWithImplicitFunctionType3(x: Int)(using conv: (Int, Long) => String): String = x
}
// format: on
17 changes: 17 additions & 0 deletions output/src/main/scala-3/fix/ImplicitConversionsTest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package fix

import scala.language.implicitConversions

// format: off
object ImplicitConversionsTest {
implicit val implicitDefWithFunctionArg1: Conversion[Int, String] = (x: Int) => x.toString
implicit val implicitDefWithFunctionArg2: Conversion[(Int, Long), String] = (x: (Int, Long)) => x.toString

implicit val implicitValWithFunctionType1: Conversion[Long, String] = _.toString
implicit val implicitValWithFunctionType2: Conversion[(Int, Long), String] = (i, l ) => i.toString

def defWithImplicitFunctionType1(x: Int)(implicit conv: Conversion[Int, String]): String = x
def defWithImplicitFunctionType2(x: Int)(implicit conv: Conversion[(Int, Long), String]): String = x
def defWithImplicitFunctionType3(x: Int)(using conv: Conversion[(Int, Long), String]): String = x
}
// format: on
3 changes: 2 additions & 1 deletion rules/src/main/resources/META-INF/services/scalafix.v1.Rule
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ fix.SemiAuto
fix.GivenAndUsing
fix.PackageObjectExport
fix.DropModThis
fix.WildcardInitializer
fix.WildcardInitializer
fix.ImplicitConversions
102 changes: 102 additions & 0 deletions rules/src/main/scala/fix/ImplicitConversions.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* Copyright 2022 Arktekk
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package fix

import fix.ImplicitConversions._
import scalafix.v1.{Patch, SemanticDocument, SemanticRule}

import scala.meta._
import scala.meta.tokens.Token

class ImplicitConversions extends SemanticRule("ImplicitConversions") {

override def fix(implicit doc: SemanticDocument): Patch = {

doc.tree.collect {

case ImplicitDefWithFunctionArg(d, inType, outType) =>
(for {
pathDefToVal <- d.tokens.find(_.is[Token.KwDef]).map(Patch.replaceToken(_, "val"))
eqSignToken <- d.tokens.find(_.is[Token.Equals])
argTokens = d.tokens.dropWhile(_.isNot[Token.LeftParen]).dropRightWhile(_.isNot[Token.RightParen])
} yield pathDefToVal +
Patch.removeTokens(argTokens) +
Patch.addRight(eqSignToken, s" ${argTokens.toString()} =>") +
Patch.replaceTree(outType, conversionCode(inType, outType))).asPatch

case ImplicitValWithFunctionType(ts) =>
Patch.replaceTree(ts, conversionCode(ts.params, ts.res))

case DefWithImplicitFunctionType(typeFunctions) =>
typeFunctions.foldRight(Patch.empty) { case (tf, patches) =>
patches + Patch.replaceTree(tf, conversionCode(tf.params, tf.res))
}
}.asPatch
}

private def conversionCode(inType: Type, retType: Type): String =
conversionCode(inType :: Nil, retType)

private def conversionCode(inType: List[Type], retType: Type): String = {
val inStr = inType match {
case one :: Nil => one.toString()
case many => many.map(_.toString()).mkString("(", ", ", ")")
}
s"Conversion[$inStr, $retType]"
}
}

object ImplicitConversions {

object ImplicitValWithFunctionType {
def unapply(tree: Tree): Option[Type.Function] =
tree match {
case Defn.Val(mods, _, Some(ts: Type.Function), _) if mods.exists(_.is[Mod.Implicit]) => Some(ts)
case _ => None
}
}
object ImplicitDefWithFunctionArg {
def unapply(tree: Tree): Option[(Defn.Def, Type, Type)] =
tree match {
case d @ Defn.Def(mods, Term.Name(_), _, (firstParam :: Nil) :: Nil, Some(outType: Type.Name), _)
if mods.exists(_.is[Mod.Implicit]) && firstParam.mods.forall(_.isNot[Mod.Implicit]) =>
firstParam.decltpe.map(inType => (d, inType, outType))
case _ => None
}
}

object DefWithImplicitFunctionType {
def unapply(tree: Tree): Option[List[Type.Function]] = tree match {
case Defn.Def(_, _, _, LastImplicitTypeFunctionParams(typeFunctions), _, _) => Some(typeFunctions)
case _ => None
}

object LastImplicitTypeFunctionParams {
def unapply(params: List[List[Term.Param]]): Option[List[Type.Function]] = {
val lastParams = params.lastOption
if (lastParams.exists(_.exists(_.mods.exists(a => a.is[Mod.Implicit] || a.is[Mod.Using])))) {
val tfs = lastParams.toList
.flatMap(_.map(_.decltpe))
.collect { case Some(tf: Type.Function) => tf }
if (tfs.isEmpty) None
else Some(tfs)
} else None
}
}
}

}

0 comments on commit 59daa38

Please sign in to comment.