Skip to content

Remove implicit conversion from liftable types to Expr[T] #4129

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
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
29 changes: 15 additions & 14 deletions docs/docs/reference/principled-meta-programming.md
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,7 @@ The compiler takes an environment that maps variable names to Scala `Expr`s.

def compile(e: Exp, env: Map[String, Expr[Int]]): Expr[Int] = e match {
case Num(n) =>
n
n.toExpr
case Plus(e1, e2) =>
’(~compile(e1, env) + ~compile(e2, env))
case Var(x) =>
Expand All @@ -520,24 +520,25 @@ Running `compile(letExp, Map())` would yield the following Scala code:

’{ val y = 3; (2 + y) + 4 }

The body of the first clause, `case Num(n) => n`, looks suspicious. `n`
is declared as an `Int`, yet the result of `compile` is declared to be
`Expr[Int]`. Shouldn’t `n` be quoted? In fact this would not
The body of the first clause, `case Num(n) => n.toExpr`, looks suspicious. `n`
is declared as an `Int`, yet it is conveted to an `Expr[Int]` with `toExpr`.
Shouldn’t `n` be quoted? In fact this would not
work since replacing `n` by `’n` in the clause would not be phase
correct.

What happens instead "under the hood" is an implicit conversion: `n`
is expanded to `scala.quoted.Expr.toExpr(n)`. The `toExpr` conversion
is defined in the companion object of class `Expr` as follows:
What happens instead "under the hood" is an extension method `toExpr` is added: `n.toExpr`
is expanded to `new scala.quoted.LiftExprOps(n).toExpr`. The `toExpr` extension
is defined in the companion object of class `Liftable` as follows:

object Expr {
implicit def toExpr[T](x: T)(implicit ev: Liftable[T]): Expr[T] =
ev.toExpr(x)
package object quoted {
implicit class LiftExprOps[T](val x: T) extends AnyVal {
def toExpr(implicit ev: Liftable[T]): Expr[T] = ev.toExpr(x)
}
}

The conversion says that values of types implementing the `Liftable`
type class can be converted ("lifted") automatically to `Expr`
values. Dotty comes with instance definitions of `Liftable` for
The extension says that values of types implementing the `Liftable` type class can be
converted ("lifted") to `Expr` values using `toExpr` when `scala.quoted._` is imported.
Dotty comes with instance definitions of `Liftable` for
several types including `Boolean`, `String`, and all primitive number
types. For example, `Int` values can be converted to `Expr[Int]`
values by wrapping the value in a `Literal` tree node. This makes use
Expand Down Expand Up @@ -570,7 +571,7 @@ a `List` is liftable if its element type is:

implicit def ListIsLiftable[T: Liftable]: Liftable[List[T]] = new {
def toExpr(xs: List[T]): Expr[List[T]] = xs match {
case x :: xs1 => ’(~implicitly[Liftable[T]].toExpr(x) :: ~toExpr(xs1))
case x :: xs1 => ’(~x.toExpr :: ~toExpr(xs1))
case Nil => ’(Nil: List[T])
}
}
Expand Down
4 changes: 1 addition & 3 deletions library/src/scala/quoted/Expr.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ sealed abstract class Expr[T] {
}

object Expr {
implicit def toExpr[T](x: T)(implicit ev: Liftable[T]): Expr[T] =
ev.toExpr(x)

implicit class AsFunction[T, U](private val f: Expr[T => U]) extends AnyVal {
def apply(x: Expr[T]): Expr[U] = new Exprs.FunctionAppliedTo[T, U](f, x)
Expand All @@ -25,7 +23,7 @@ object Expr {
object Exprs {
/** An Expr backed by a pickled TASTY tree */
final class TastyExpr[T](val tasty: Pickled, val args: Seq[Any]) extends Expr[T] {
override def toString(): String = s"Expr(<pickled>)"
override def toString: String = s"Expr(<pickled>)"
}

/** An Expr backed by a value.
Expand Down
7 changes: 1 addition & 6 deletions library/src/scala/quoted/Liftable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import scala.quoted.Exprs.ValueExpr
* without going through an explicit `'(...)` operation.
*/
abstract class Liftable[T] {
implicit def toExpr(x: T): Expr[T]
def toExpr(x: T): Expr[T]
}

/** Some liftable base types. To be completed with at least all types
Expand All @@ -15,11 +15,6 @@ abstract class Liftable[T] {
* gives an alternative implementation using just the basic staging system.
*/
object Liftable {

implicit class LiftExprOps[T](val x: T) extends AnyVal {
def toExpr(implicit liftable: Liftable[T]): Expr[T] = liftable.toExpr(x)
}

implicit def BooleanIsLiftable: Liftable[Boolean] = (x: Boolean) => new ValueExpr(x)
implicit def ByteLiftable: Liftable[Byte] = (x: Byte) => new ValueExpr(x)
implicit def CharIsLiftable: Liftable[Char] = (x: Char) => new ValueExpr(x)
Expand Down
2 changes: 1 addition & 1 deletion library/src/scala/quoted/Type.scala
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,4 @@ object Types {
final class TreeType[Tree](val tree: Tree) extends quoted.Type[Any] {
override def toString: String = s"Type(<raw>)"
}
}
}
9 changes: 9 additions & 0 deletions library/src/scala/quoted/package.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package scala

package object quoted {

implicit class LiftExprOps[T](val x: T) extends AnyVal {
def toExpr(implicit ev: Liftable[T]): Expr[T] = ev.toExpr(x)
}

}
2 changes: 1 addition & 1 deletion tests/pos/i3898/quoted_1.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import scala.quoted._
object Macro {
inline def ff(args: Any*): String = ~impl('(args))
def impl(args: Expr[Seq[Any]]): Expr[String] = ""
def impl(args: Expr[Seq[Any]]): Expr[String] = '("")
}
2 changes: 1 addition & 1 deletion tests/pos/i3898b/quoted_1.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import scala.quoted._
object Macro {
inline def ff(x: Int, inline y: Int): String = ~impl('(x))
def impl(x: Expr[Int]): Expr[String] = ""
def impl(x: Expr[Int]): Expr[String] = '("")
}
2 changes: 1 addition & 1 deletion tests/pos/i3898c/quoted_1.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import scala.quoted._
object Macro {
inline def ff(x: Int, inline y: Int): String = ~impl('(x))
def impl(x: Expr[Int]): Expr[String] = ""
def impl(x: Expr[Int]): Expr[String] = '("")
}
2 changes: 1 addition & 1 deletion tests/pos/i3916/Macro_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ object FInterpolation {
}

def fInterpolation(sc: StringContext, args: Seq[Expr[Any]]): Expr[String] = {
val str: Expr[String] = sc.parts.mkString("")
val str: Expr[String] = sc.parts.mkString("").toExpr
val args1: Expr[Seq[Any]] = liftSeq(args)
'{ (~str).format(~args1: _*) }
}
Expand Down
2 changes: 1 addition & 1 deletion tests/pos/i4023b/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import scala.quoted._
object Macro {
inline def ff[T](implicit t: Type[T]): Int = ~impl[T]
def impl[T]: Expr[Int] = 4
def impl[T]: Expr[Int] = '(4)
}
3 changes: 2 additions & 1 deletion tests/pos/quote-0.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import scala.quoted._

import dotty.tools.dotc.quoted.Toolbox._

class Test {
Expand All @@ -11,7 +12,7 @@ class Test {
def assertImpl(expr: Expr[Boolean]) =
'{ if !(~expr) then throw new AssertionError(s"failed assertion: ${~showExpr(expr)}") }

def showExpr[T](expr: Expr[T]): Expr[String] = expr.toString
def showExpr[T](expr: Expr[T]): Expr[String] = expr.toString.toExpr

inline def power(inline n: Int, x: Double) = ~powerCode(n, '(x))

Expand Down
2 changes: 1 addition & 1 deletion tests/pos/quote-interpolator-core/quoted_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ object FInterpolation {
}

def fInterpolation(sc: StringContext, args: Seq[Expr[Any]]): Expr[String] = {
val str: Expr[String] = sc.parts.mkString("")
val str: Expr[String] = sc.parts.mkString("").toExpr
val args1: Expr[Seq[Any]] = liftSeq(args)
'{ (~str).format(~args1: _*) }
}
Expand Down
1 change: 0 additions & 1 deletion tests/pos/quote-lift.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import scala.quoted._

object Test {
'{ ~(1: Expr[Int]) }

'{ ~implicitly[Liftable[Int]].toExpr(1) }

Expand Down
20 changes: 10 additions & 10 deletions tests/pos/quote-liftable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ object Test {
}
}

(true: Expr[Boolean])
(1: Expr[Byte])
('a': Expr[Char])
(1: Expr[Short])
(1: Expr[Int])
(1L: Expr[Long])
(1.0f: Expr[Float])
(1.0: Expr[Double])
("abc": Expr[String])
true.toExpr
1.toExpr
'a'.toExpr
1.toExpr
1.toExpr
1L.toExpr
1.0f.toExpr
1.0.toExpr
"abc".toExpr

val xs: Expr[List[Int]] = 1 :: 2 :: 3 :: Nil
val xs: Expr[List[Int]] = (1 :: 2 :: 3 :: Nil).toExpr
}
3 changes: 2 additions & 1 deletion tests/run-with-compiler/i4044b.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import scala.quoted._

import dotty.tools.dotc.quoted.Toolbox._

sealed abstract class VarRef[T] {
Expand All @@ -21,7 +22,7 @@ object VarRef {

object Test {
def main(args: Array[String]): Unit = {
val q = VarRef(4)(varRef => '{ ~varRef.update(3); ~varRef.expr })
val q = VarRef('(4))(varRef => '{ ~varRef.update('(3)); ~varRef.expr })
println(q.show)
}
}
19 changes: 9 additions & 10 deletions tests/run-with-compiler/quote-lib.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import liftable.Exprs._
object Test {
def main(args: Array[String]): Unit = {

val liftedUnit: Expr[Unit] = ()
val liftedUnit: Expr[Unit] = '()

letVal('(1))(a => '{ ~a + 1 }).show
letLazyVal('(1))(a => '{ ~a + 1 }).show
Expand All @@ -21,18 +21,18 @@ object Test {
liftedWhile('(true))('{ println(1) }).show
liftedDoWhile('{ println(1) })('(true)).show

val t1: Expr[Tuple1[Int]] = Tuple1(4)
val t2: Expr[(Int, Int)] = (2, 3)
val t3: Expr[(Int, Int, Int)] = (2, 3, 4)
val t4: Expr[(Int, Int, Int, Int)] = (2, 3, 4, 5)
val t1: Expr[Tuple1[Int]] = Tuple1(4).toExpr
val t2: Expr[(Int, Int)] = (2, 3).toExpr
val t3: Expr[(Int, Int, Int)] = (2, 3, 4).toExpr
val t4: Expr[(Int, Int, Int, Int)] = (2, 3, 4, 5).toExpr

val list: List[Int] = List(1, 2, 3)
val liftedList: Expr[List[Int]] = list
val liftedList: Expr[List[Int]] = list.toExpr

liftedList.foldLeft[Int](0)('{ (acc: Int, x: Int) => acc + x }).show
liftedList.foldLeft[Int](0.toExpr)('{ (acc: Int, x: Int) => acc + x }).show
liftedList.foreach('{ (x: Int) => println(x) }).show

list.unrolledFoldLeft[Int](0)('{ (acc: Int, x: Int) => acc + x }).show
list.unrolledFoldLeft[Int](0.toExpr)('{ (acc: Int, x: Int) => acc + x }).show
list.unrolledForeach('{ (x: Int) => println(x) }).show

println("quote lib ok")
Expand All @@ -42,7 +42,6 @@ object Test {

package liftable {
import scala.quoted.Liftable
import scala.quoted.Liftable._
import scala.reflect.ClassTag

object Exprs {
Expand Down Expand Up @@ -113,7 +112,7 @@ package liftable {
}
def unrolledForeach(f: Expr[T => Unit]): Expr[Unit] = list match {
case x :: xs => '{ (~f).apply(~x.toExpr); ~xs.unrolledForeach(f) }
case Nil => ()
case Nil => '()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import dotty.tools.dotc.quoted.Toolbox._
object Test {

def main(args: Array[String]): Unit = {
(3: Expr[Int]) match { case Constant(n) => println(n) }
3.toExpr match { case Constant(n) => println(n) }
'(4) match { case Constant(n) => println(n) }
'("abc") match { case Constant(n) => println(n) }
'(null) match { case Constant(n) => println(n) }
Expand Down
4 changes: 2 additions & 2 deletions tests/run-with-compiler/quote-run-constants-extract-2.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ object Test {

def main(args: Array[String]): Unit = {
// 2 is a lifted constant
println(power(2, 3.0).show)
println(power(2, 3.0).run)
println(power(2.toExpr, 3.0.toExpr).show)
println(power(2.toExpr, 3.0.toExpr).run)
}

def power(n: Expr[Int], x: Expr[Double]): Expr[Double] = {
Expand Down
4 changes: 2 additions & 2 deletions tests/run-with-compiler/quote-run-constants-extract-3.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ object Test {

def main(args: Array[String]): Unit = {
// 2 is a lifted constant
println(power(2, 3.0).show)
println(power(2, 3.0).run)
println(power(2.toExpr, 3.0.toExpr).show)
println(power(2.toExpr, 3.0.toExpr).run)
}

def power(n: Expr[Int], x: Expr[Double]): Expr[Double] = {
Expand Down
4 changes: 2 additions & 2 deletions tests/run-with-compiler/quote-run-constants-extract-4.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ object Test {
def main(args: Array[String]): Unit = {
// n is a lifted constant
val n = 2
println(power(n, 4.0).show)
println(power(n, 4.0).run)
println(power(n.toExpr, 4.0.toExpr).show)
println(power(n.toExpr, 4.0.toExpr).run)
}

def power(n: Expr[Int], x: Expr[Double]): Expr[Double] = {
Expand Down
4 changes: 2 additions & 2 deletions tests/run-with-compiler/quote-run-constants-extract-5.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ object Test {

def main(args: Array[String]): Unit = {
// n is a constant in a quote
println(power('(2), 5.0).show)
println(power('(2), 5.0).run)
println(power('(2), 5.0.toExpr).show)
println(power('(2), 5.0.toExpr).run)
}

def power(n: Expr[Int], x: Expr[Double]): Expr[Double] = {
Expand Down
4 changes: 2 additions & 2 deletions tests/run-with-compiler/quote-run-constants-extract-6.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ object Test {
def main(args: Array[String]): Unit = {
// n2 is clearly not a constant
val n2 = '{ println("foo"); 2 }
println(power(n2, 6.0).show)
println(power(n2, 6.0).run)
println(power(n2, 6.0.toExpr).show)
println(power(n2, 6.0.toExpr).run)
}

def power(n: Expr[Int], x: Expr[Double]): Expr[Double] = {
Expand Down
52 changes: 26 additions & 26 deletions tests/run-with-compiler/quote-run-constants.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,35 +8,35 @@ object Test {
def run[T](expr: Expr[T]): Unit = println(expr.run)
def show[T](expr: Expr[T]): Unit = println(expr.show)

run(true)
run('a')
run('\n')
run('"')
run('\'')
run('\\')
run(1)
run(2)
run(3L)
run(4.0f)
run(5.0d)
run("xyz")
run(true.toExpr)
run('a'.toExpr)
run('\n'.toExpr)
run('"'.toExpr)
run('\''.toExpr)
run('\\'.toExpr)
run(1.toExpr)
run(2.toExpr)
run(3L.toExpr)
run(4.0f.toExpr)
run(5.0d.toExpr)
run("xyz".toExpr)

println("======")

show(true)
show('a')
show('\n')
show('"')
show('\'')
show('\\')
show(1)
show(2)
show(3L)
show(4.0f)
show(5.0d)
show("xyz")
show("\n\\\"'")
show(true.toExpr)
show('a'.toExpr)
show('\n'.toExpr)
show('"'.toExpr)
show('\''.toExpr)
show('\\'.toExpr)
show(1.toExpr)
show(2.toExpr)
show(3L.toExpr)
show(4.0f.toExpr)
show(5.0d.toExpr)
show("xyz".toExpr)
show("\n\\\"'".toExpr)
show("""abc
xyz""")
xyz""".toExpr)
}
}
Loading