Skip to content

Add scala.compiletime.summonInline #8483

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
1 change: 1 addition & 0 deletions compiler/test/dotty/tools/dotc/CompilationTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ class CompilationTests extends ParallelTesting {
compileDir("tests/neg-custom-args/adhoc-extension", defaultOptions.and("-strict", "-feature", "-Xfatal-warnings")),
compileFile("tests/neg/i7575.scala", defaultOptions.and("-language:_")),
compileFile("tests/neg-custom-args/kind-projector.scala", defaultOptions.and("-Ykind-projector")),
compileFile("tests/neg-custom-args/typeclass-derivation2.scala", defaultOptions.and("-Yerased-terms")),
).checkExpectedErrors()
}

Expand Down
17 changes: 5 additions & 12 deletions docs/docs/reference/contextual/derivation.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ worked out example of such a library, see [shapeless 3](https://github.com/miles
#### How to write a type class `derived` method using low level mechanisms

The low-level method we will use to implement a type class `derived` method in this example exploits three new
type-level constructs in Dotty: inline methods, inline matches, and implicit searches via `summonFrom`. Given this definition of the
type-level constructs in Dotty: inline methods, inline matches, and implicit searches via `summonInline` or `summonFrom`. Given this definition of the
`Eq` type class,


Expand Down Expand Up @@ -194,17 +194,14 @@ call sites (for instance the compiler generated instance definitions in the comp

The body of this method (1) first materializes the `Eq` instances for all the child types of type the instance is
being derived for. This is either all the branches of a sum type or all the fields of a product type. The
implementation of `summonAll` is `inline` and uses Dotty's `summonFrom` construct to collect the instances as a
implementation of `summonAll` is `inline` and uses Dotty's `summonInline` construct to collect the instances as a
`List`,

```scala
inline def summonAll[T]: T = summonFrom {
case t: T => t
}

inline def summonAll[T <: Tuple]: List[Eq[_]] = inline erasedValue[T] match {
case _: Unit => Nil
case _: (t *: ts) => summon[Eq[t]] :: summonAll[ts]
case _: (t *: ts) => summonInline[Eq[t]] :: summonAll[ts]
}
```

Expand Down Expand Up @@ -244,15 +241,11 @@ Pulling this all together we have the following complete implementation,

```scala
import scala.deriving._
import scala.compiletime.{erasedValue, summonFrom}

inline def summon[T]: T = summonFrom {
case t: T => t
}
import scala.compiletime.{erasedValue, summonInline}

inline def summonAll[T <: Tuple]: List[Eq[_]] = inline erasedValue[T] match {
case _: Unit => Nil
case _: (t *: ts) => summon[Eq[t]] :: summonAll[ts]
case _: (t *: ts) => summonInline[Eq[t]] :: summonAll[ts]
}

trait Eq[T] {
Expand Down
15 changes: 12 additions & 3 deletions docs/docs/reference/metaprogramming/inline.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,9 @@ power(expr, 10)
```

Parameters of inline methods can have an `inline` modifier as well. This means
that actual arguments to these parameters will be inlined in the body of the
`inline def`. `inline` parameters have call semantics equivalent to by-name parameters
but allow for duplication of the code in the argument. It is usually useful when constant
that actual arguments to these parameters will be inlined in the body of the
`inline def`. `inline` parameters have call semantics equivalent to by-name parameters
but allow for duplication of the code in the argument. It is usually useful when constant
values need to be propagated to allow further optimizations/reductions.

The following example shows the difference in translation between by-value, by-name and `inline`
Expand Down Expand Up @@ -552,6 +552,15 @@ inline def f: Any = summonFrom {
}
```

## `summonInline`

The shorthand `summonInline` provides a simple way to write a `summon` that is delayed until the call is inlined.
```scala
inline def summonInline[T] <: T = summonFrom {
case t: T => t
}
```

### Reference

For more info, see [PR #4768](https://github.com/lampepfl/dotty/pull/4768),
Expand Down
12 changes: 12 additions & 0 deletions library/src/scala/compiletime/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,18 @@ package object compiletime {
*/
inline def summonFrom[T](f: Nothing => T) <: T = ???


/** Summon a given value of type `T`. Usually, the argument is not passed explicitly.
* The summoning is delayed until the call has been fully inlined.
*
* @tparam T the type of the value to be summoned
* @return the given value typed as the provided type parameter
*/
inline def summonInline[T] <: T = summonFrom {
case t: T => t
}


/** Succesor of a natural number where zero is the type 0 and successors are reduced as if the definition was
*
* type S[N <: Int] <: Int = N match {
Expand Down
6 changes: 2 additions & 4 deletions tests/neg-custom-args/typeclass-derivation2.scala
Original file line number Diff line number Diff line change
Expand Up @@ -210,12 +210,10 @@ trait Show[T] {
def show(x: T): String
}
object Show {
import scala.compiletime.{erasedValue, error, summonFrom}
import scala.compiletime.{erasedValue, error, summonInline}
import TypeLevel._

inline def tryShow[T](x: T): String = summonFrom {
case s: Show[T] => s.show(x)
}
inline def tryShow[T](x: T): String = summonInline[Show[T]].show(x)

inline def showElems[Elems <: Tuple](elems: Mirror, n: Int): List[String] =
inline erasedValue[Elems] match {
Expand Down
12 changes: 3 additions & 9 deletions tests/pending/pos/summonFrom.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ object summonFroms {
object invariant {
case class Box[T](value: T)
implicit val box: Box[Int] = Box(0)
inline def unbox <: Any = summonFrom {
case b: Box[t] => b.value
}
inline def unbox <: Any = summonInline[Box[t]].value
val i: Int = unbox
val i2 = unbox
val i3: Int = i2
Expand All @@ -15,9 +13,7 @@ object summonFroms {
object covariant {
case class Box[+T](value: T)
implicit val box: Box[Int] = Box(0)
inline def unbox <: Any = summonFrom {
case b: Box[t] => b.value
}
inline def unbox <: Any = summonInline[Box[t]].value
val i: Int = unbox
val i2 = unbox
val i3: Int = i2
Expand All @@ -26,9 +22,7 @@ object summonFroms {
object contravariant {
case class TrashCan[-T](trash: T => Unit)
implicit val trashCan: TrashCan[Int] = TrashCan { i => ; }
inline def trash <: Nothing => Unit = summonFrom {
case c: TrashCan[t] => c.trash
}
inline def trash <: Nothing => Unit = summonInline[TrashCan[t]].trash
val t1: Int => Unit = trash
val t2 = trash
val t3: Int => Unit = t2
Expand Down
6 changes: 2 additions & 4 deletions tests/pos-macros/i7853/JsonEncoder_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,11 @@ trait JsonEncoder[T] {
}

object JsonEncoder {
import scala.compiletime.{erasedValue, summonFrom}
import scala.compiletime.{erasedValue, summonInline}
import compiletime._
import scala.deriving._

inline def encodeElem[T](elem: T): String = summonFrom {
case encoder: JsonEncoder[T] => encoder.encode(elem)
}
inline def encodeElem[T](elem: T): String = summonInline[JsonEncoder[T]].encode(elem)

inline def encodeElems[Elems <: Tuple](idx: Int)(value: Any): List[String] =
inline erasedValue[Elems] match {
Expand Down
2 changes: 0 additions & 2 deletions tests/pos-macros/i7853/SummonJsonEncoderTest_2.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import scala.deriving._
import scala.quoted._

import scala.compiletime.{erasedValue, summonFrom}
import JsonEncoder.{given _, _}

object SummonJsonEncoderTest {
Expand Down
12 changes: 3 additions & 9 deletions tests/pos-special/typeclass-scaling.scala
Original file line number Diff line number Diff line change
Expand Up @@ -218,9 +218,7 @@ object typeclasses {
import compiletime._
import scala.deriving._

inline def tryEql[TT](x: TT, y: TT): Boolean = summonFrom {
case eq: Eq[TT] => eq.eql(x, y)
}
inline def tryEql[TT](x: TT, y: TT): Boolean = summonInline[Eq[TT]].eql(x, y)

inline def eqlElems[Elems <: Tuple](n: Int)(x: Any, y: Any): Boolean =
inline erasedValue[Elems] match {
Expand Down Expand Up @@ -275,9 +273,7 @@ object typeclasses {

def nextInt(buf: mutable.ListBuffer[Int]): Int = try buf.head finally buf.trimStart(1)

inline def tryPickle[T](buf: mutable.ListBuffer[Int], x: T): Unit = summonFrom {
case pkl: Pickler[T] => pkl.pickle(buf, x)
}
inline def tryPickle[T](buf: mutable.ListBuffer[Int], x: T): Unit = summonInline[Pickler[T]].pickle(buf, x)

inline def pickleElems[Elems <: Tuple](n: Int)(buf: mutable.ListBuffer[Int], x: Any): Unit =
inline erasedValue[Elems] match {
Expand All @@ -298,9 +294,7 @@ object typeclasses {
case _: Unit =>
}

inline def tryUnpickle[T](buf: mutable.ListBuffer[Int]): T = summonFrom {
case pkl: Pickler[T] => pkl.unpickle(buf)
}
inline def tryUnpickle[T](buf: mutable.ListBuffer[Int]): T = summonInline[Pickler[T]].unpickle(buf)

inline def unpickleElems[Elems <: Tuple](n: Int)(buf: mutable.ListBuffer[Int], elems: ArrayProduct): Unit =
inline erasedValue[Elems] match {
Expand Down
18 changes: 5 additions & 13 deletions tests/run-custom-args/typeclass-derivation2.scala
Original file line number Diff line number Diff line change
Expand Up @@ -227,9 +227,7 @@ object Eq {
import scala.compiletime.{erasedValue, error, summonFrom}
import TypeLevel._

inline def tryEql[T](x: T, y: T) = summonFrom {
case eq: Eq[T] => eq.eql(x, y)
}
inline def tryEql[T](x: T, y: T) = summonInline[Eq[T]].eql(x, y)

inline def eqlElems[Elems <: Tuple](xm: Mirror, ym: Mirror, n: Int): Boolean =
inline erasedValue[Elems] match {
Expand Down Expand Up @@ -283,14 +281,12 @@ trait Pickler[T] {
}

object Pickler {
import scala.compiletime.{erasedValue, constValue, error, summonFrom}
import scala.compiletime.{erasedValue, constValue, error, summonInline}
import TypeLevel._

def nextInt(buf: mutable.ListBuffer[Int]): Int = try buf.head finally buf.trimStart(1)

inline def tryPickle[T](buf: mutable.ListBuffer[Int], x: T): Unit = summonFrom {
case pkl: Pickler[T] => pkl.pickle(buf, x)
}
inline def tryPickle[T](buf: mutable.ListBuffer[Int], x: T): Unit = summonInline[Pickler[T]].pickle(buf, x)

inline def pickleElems[Elems <: Tuple](buf: mutable.ListBuffer[Int], elems: Mirror, n: Int): Unit =
inline erasedValue[Elems] match {
Expand Down Expand Up @@ -321,9 +317,7 @@ object Pickler {
case _: Unit =>
}

inline def tryUnpickle[T](buf: mutable.ListBuffer[Int]): T = summonFrom {
case pkl: Pickler[T] => pkl.unpickle(buf)
}
inline def tryUnpickle[T](buf: mutable.ListBuffer[Int]): T = summonInline[Pickler[T]].unpickle(buf)

inline def unpickleElems[Elems <: Tuple](buf: mutable.ListBuffer[Int], elems: Array[AnyRef], n: Int): Unit =
inline erasedValue[Elems] match {
Expand Down Expand Up @@ -382,9 +376,7 @@ object Show {
import scala.compiletime.{erasedValue, error, summonFrom}
import TypeLevel._

inline def tryShow[T](x: T): String = summonFrom {
case s: Show[T] => s.show(x)
}
inline def tryShow[T](x: T): String = summonInline[Show[T]].show(x)

inline def showElems[Elems <: Tuple](elems: Mirror, n: Int): List[String] =
inline erasedValue[Elems] match {
Expand Down
23 changes: 7 additions & 16 deletions tests/run-custom-args/typeclass-derivation2c.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import scala.collection.mutable
import scala.annotation.tailrec
import scala.compiletime.summonFrom
import scala.compiletime.summonInline

// Simulation of an alternative typeclass derivation scheme proposed in #6153

Expand Down Expand Up @@ -55,9 +55,7 @@ object Deriving {
type CaseLabel <: String

/** The represented value */
inline def singletonValue = summonFrom {
case ev: ValueOf[T] => ev.value
}
inline def singletonValue = summonInline[ValueOf[T]].value
}
}

Expand Down Expand Up @@ -213,9 +211,7 @@ trait Eq[T] {
object Eq {
import scala.compiletime.erasedValue

inline def tryEql[T](x: T, y: T) = summonFrom {
case eq: Eq[T] => eq.eql(x, y)
}
inline def tryEql[T](x: T, y: T) = summonInline[Eq[T]].eql(x, y)

inline def eqlElems[Elems <: Tuple](n: Int)(x: Any, y: Any): Boolean =
inline erasedValue[Elems] match {
Expand Down Expand Up @@ -268,9 +264,8 @@ object Pickler {

def nextInt(buf: mutable.ListBuffer[Int]): Int = try buf.head finally buf.trimStart(1)

inline def tryPickle[T](buf: mutable.ListBuffer[Int], x: T): Unit = summonFrom {
case pkl: Pickler[T] => pkl.pickle(buf, x)
}
inline def tryPickle[T](buf: mutable.ListBuffer[Int], x: T): Unit =
summonInline[Pickler[T]].pickle(buf, x)

inline def pickleElems[Elems <: Tuple](n: Int)(buf: mutable.ListBuffer[Int], x: Any): Unit =
inline erasedValue[Elems] match {
Expand All @@ -293,9 +288,7 @@ object Pickler {
}
else pickleCases[T](g, n + 1)(buf, x, ord)

inline def tryUnpickle[T](buf: mutable.ListBuffer[Int]): T = summonFrom {
case pkl: Pickler[T] => pkl.unpickle(buf)
}
inline def tryUnpickle[T](buf: mutable.ListBuffer[Int]): T = summonInline[Pickler[T]].unpickle(buf)

inline def unpickleElems[Elems <: Tuple](n: Int)(buf: mutable.ListBuffer[Int], elems: Array[AnyRef]): Unit =
inline erasedValue[Elems] match {
Expand Down Expand Up @@ -358,9 +351,7 @@ trait Show[T] {
object Show {
import scala.compiletime.{erasedValue, constValue}

inline def tryShow[T](x: T): String = summonFrom {
case s: Show[T] => s.show(x)
}
inline def tryShow[T](x: T): String = summonInline[Show[T]].show(x)

inline def showElems[Elems <: Tuple, Labels <: Tuple](n: Int)(x: Any): List[String] =
inline erasedValue[Elems] match {
Expand Down
8 changes: 2 additions & 6 deletions tests/run/typeclass-derivation-doc-example.scala
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import scala.deriving._
import scala.compiletime.{erasedValue, summonFrom}

inline def summon[T]: T = summonFrom {
case t: T => t
}
import scala.compiletime.{erasedValue, summonInline}

inline def summonAll[T <: Tuple]: List[Eq[_]] = inline erasedValue[T] match {
case _: Unit => Nil
case _: (t *: ts) => summon[Eq[t]] :: summonAll[ts]
case _: (t *: ts) => summonInline[Eq[t]] :: summonAll[ts]
}

trait Eq[T] {
Expand Down
4 changes: 1 addition & 3 deletions tests/run/typeclass-derivation1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,7 @@ object Deriving {
}

object Eq {
inline def tryEq[T](x: T, y: T) = summonFrom {
case eq: Eq[T] => eq.equals(x, y)
}
inline def tryEq[T](x: T, y: T) = summonInline[Eq[T]].equals(x, y)

inline def deriveForSum[Alts <: Tuple](x: Any, y: Any): Boolean = inline erasedValue[Alts] match {
case _: (alt *: alts1) =>
Expand Down
Loading