Related to Use Cases for Implicit Conversion Blackbox Macros.
Use cases
As @lihaoyi showed in the discussion linked above, some libraries (usually using metaprogramming) such as Source Code, Iron, Refined, Mill… use implicit conversions combined with macros.
For example, here is the conversion used by Iron:
implicit inline def autoRefine[A, C](inline value: A)(using inline constraint: Constraint[A, C]): A :| C =
macros.assertCondition(value, constraint.test(value), constraint.message)
IronType(value)
Here value
has to be inline
to be evaluable at compile-time and analyzed by macros.assertCondition
.
Here is a more minimal example.
types.scala
opaque type PosInt = Int
object PosInt:
inline def unsafe(inline value: Int): PosInt = value
macros.scala
import scala.quoted.*
def autoPosIntImpl(expr: Expr[Int])(using Quotes): Expr[PosInt] =
import quotes.reflect.report.errorAndAbort
expr.value match
case Some(value) if value > 0 => '{PosInt.unsafe($expr)}
case Some(value) => errorAndAbort(s"$value <= 0")
case None => errorAndAbort(
s"""Cannot retrieve value. Got:
|---
|${expr.show}
|---""".stripMargin
)
test.sc
//> using scala "3.4.0"
//> using file "types.scala"
//> using file "macros.scala"
import scala.language.implicitConversions
implicit inline def autoPosInt(inline value: Int): PosInt = ${autoPosIntImpl('value)}
val x: PosInt = 5 //Compiles
val y: PosInt = -5 //Error: -5 <= 0
Now using Conversion
:
//> using scala "3.4.0"
//> using file "types.scala"
//> using file "macros.scala"
//> using file "conversion.scala"
given Conversion[Int, PosInt] with
override def apply(value: Int): PosInt = ${autoPosIntImpl('value)}
val x: PosInt = 5
This does not work:
[error] Cannot retrieve value. Got:
[error] ---
[error] value
[error] ---
[error] override inline def apply(value: Int): PosInt = ${autoPosIntImpl('value)}
And we cannot mark value
as inline
:
[error] Cannot override non-inline parameter with an inline parameter
[error] override inline def apply(inline value: Int): PosInt = ${autoPosIntImpl('value)}
As far as I know, there is no replacement for implicit inline def
and an alternative must be available before deprecating implicit def
.
Proposed solution: InlineConversion
We cannot change the definition of Conversion#apply
to mark its parameter as inline
as it would break existing code. An alternative would be to add a similar mechanism but inline:
trait InlineConversion[-T, +U]:
inline def apply(inline value: T): U
I implemented it with an inline implicit def
(which could be replaced by the same mechanism as Conversion
):
conversion.scala
import scala.language.implicitConversions
trait InlineConversion[-T, +U]:
inline def apply(inline value: T): U
implicit inline def convert[T, U](inline t: T)(using inline conversion: InlineConversion[T, U]): U = conversion(t)
test.sc
//> using scala "3.4.0"
//> using file "types.scala"
//> using file "macros.scala"
//> using file "conversion.scala"
inline given InlineConversion[Int, PosInt] with
override inline def apply(inline value: Int): PosInt = ${autoPosIntImpl('value)}
val x: PosInt = 5 //Compiles
val y: PosInt = -5 //Error: -5 <= 0
There might be better alternatives but I think this one is a good starting point.