Skip to content
Draft
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
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
}
}
}

}