From 865ad2936cdd89af7de0222f5ecc7a511e8b895a Mon Sep 17 00:00:00 2001 From: Aggelos Biboudis Date: Wed, 15 Jan 2020 23:51:26 +0100 Subject: [PATCH 1/8] Fix #8007: Add test file --- tests/run-macros/i8007/Macro_1.scala | 36 ++++++++++++++++++++++++++++ tests/run-macros/i8007/Test_2.scala | 6 +++++ 2 files changed, 42 insertions(+) create mode 100644 tests/run-macros/i8007/Macro_1.scala create mode 100644 tests/run-macros/i8007/Test_2.scala diff --git a/tests/run-macros/i8007/Macro_1.scala b/tests/run-macros/i8007/Macro_1.scala new file mode 100644 index 000000000000..35680285736b --- /dev/null +++ b/tests/run-macros/i8007/Macro_1.scala @@ -0,0 +1,36 @@ +import scala.deriving._ +import scala.quoted._ +import scala.quoted.matching._ +import scala.compiletime.{erasedValue, summonFrom, constValue} + +object Macro { + case class Person(name: String, age: Int) + + // Summon a mirror for a particular type + inline def summonMirror[T]: Option[Mirror.Of[T]] = + summonFrom { + case given m: Mirror.Of[T] => Some(m) + case _ => None + } + + // Get fields from a mirror: + inline def mirrorFields[Fields <: Tuple]: List[String] = + inline erasedValue[Fields] match { + case _: (field *: fields) => constValue[field].toString :: mirrorFields[fields] + case _ => Nil + } + + inline def usingSummonFrom[T](value: =>T): String = + ${ usingSummonFromImpl('value, summonMirror[T]) } + + def usingSummonFromImpl[T: Type](value: Expr[T], m: Option[Mirror.Of[T]])(given qctx: QuoteContext): Expr[String] = { + import qctx.tasty.{_, given} + val theMirror = m match { case Some(mirror) => mirror } + + theMirror match { + case m: Mirror.ProductOf[T] => println("it's a product: " + mirrorFields[m.MirroredElemLabels]) + } + + '{ "Doesn't matter" } + } +} \ No newline at end of file diff --git a/tests/run-macros/i8007/Test_2.scala b/tests/run-macros/i8007/Test_2.scala new file mode 100644 index 000000000000..e61277547a7a --- /dev/null +++ b/tests/run-macros/i8007/Test_2.scala @@ -0,0 +1,6 @@ +import Macro._ + +@main def Test() = { + val list = usingSummonFrom[Person](Person("Test", 23)) + println(list) +} \ No newline at end of file From 315071cc46e6424ecd3ed79a67a3d759df4b6874 Mon Sep 17 00:00:00 2001 From: Aggelos Biboudis Date: Thu, 16 Jan 2020 15:38:25 +0100 Subject: [PATCH 2/8] Update regression to use macros and summonExpr --- tests/run-macros/i8007.check | 1 + tests/run-macros/i8007/Macro_1.scala | 39 ++++++++++++++-------------- 2 files changed, 20 insertions(+), 20 deletions(-) create mode 100644 tests/run-macros/i8007.check diff --git a/tests/run-macros/i8007.check b/tests/run-macros/i8007.check new file mode 100644 index 000000000000..cb83c5d6edf8 --- /dev/null +++ b/tests/run-macros/i8007.check @@ -0,0 +1 @@ +List("name", "age") \ No newline at end of file diff --git a/tests/run-macros/i8007/Macro_1.scala b/tests/run-macros/i8007/Macro_1.scala index 35680285736b..6706138f4ace 100644 --- a/tests/run-macros/i8007/Macro_1.scala +++ b/tests/run-macros/i8007/Macro_1.scala @@ -6,31 +6,30 @@ import scala.compiletime.{erasedValue, summonFrom, constValue} object Macro { case class Person(name: String, age: Int) - // Summon a mirror for a particular type - inline def summonMirror[T]: Option[Mirror.Of[T]] = - summonFrom { - case given m: Mirror.Of[T] => Some(m) - case _ => None + def mirrorFields[T](t: Type[T])(given qctx: QuoteContext): List[String] = + t match { + case '[$field *: $fields] => field.show :: mirrorFields(fields) + case '[Unit] => Nil } - // Get fields from a mirror: - inline def mirrorFields[Fields <: Tuple]: List[String] = - inline erasedValue[Fields] match { - case _: (field *: fields) => constValue[field].toString :: mirrorFields[fields] - case _ => Nil - } - - inline def usingSummonFrom[T](value: =>T): String = - ${ usingSummonFromImpl('value, summonMirror[T]) } + inline def usingSummonFrom[T](value: =>T): List[String] = + ${ usingSummonFromImpl('value) } - def usingSummonFromImpl[T: Type](value: Expr[T], m: Option[Mirror.Of[T]])(given qctx: QuoteContext): Expr[String] = { + def usingSummonFromImpl[T: Type](value: Expr[T])(given qctx: QuoteContext): Expr[List[String]] = { import qctx.tasty.{_, given} - val theMirror = m match { case Some(mirror) => mirror } - theMirror match { - case m: Mirror.ProductOf[T] => println("it's a product: " + mirrorFields[m.MirroredElemLabels]) - } - '{ "Doesn't matter" } + val mirrorTpe = '[Mirror.Of[T]] + + summonExpr(given mirrorTpe).get match { + case '{ $m: Mirror.ProductOf[T] } => { + val typeMember = TypeSelect(m.unseal, "MirroredElemLabels") + + type TT + implicit val TT: quoted.Type[TT] = typeMember.tpe.seal.asInstanceOf[quoted.Type[TT]] + + Expr(mirrorFields('[TT])) + } + } } } \ No newline at end of file From 597d4cac058a1ad0a1abc894785505dc3d50d710 Mon Sep 17 00:00:00 2001 From: Aggelos Biboudis Date: Tue, 21 Jan 2020 13:25:15 +0100 Subject: [PATCH 3/8] Add regression that demonstrates a type class derived method with macros --- tests/run-macros/i8007.check | 4 +- tests/run-macros/i8007/Macro_1.scala | 22 +++++------ tests/run-macros/i8007/Macro_2.scala | 58 ++++++++++++++++++++++++++++ tests/run-macros/i8007/Test_2.scala | 6 --- tests/run-macros/i8007/Test_3.scala | 10 +++++ 5 files changed, 81 insertions(+), 19 deletions(-) create mode 100644 tests/run-macros/i8007/Macro_2.scala delete mode 100644 tests/run-macros/i8007/Test_2.scala create mode 100644 tests/run-macros/i8007/Test_3.scala diff --git a/tests/run-macros/i8007.check b/tests/run-macros/i8007.check index cb83c5d6edf8..2e2dbfdb3019 100644 --- a/tests/run-macros/i8007.check +++ b/tests/run-macros/i8007.check @@ -1 +1,3 @@ -List("name", "age") \ No newline at end of file +List("name", "age") + +Test 23 diff --git a/tests/run-macros/i8007/Macro_1.scala b/tests/run-macros/i8007/Macro_1.scala index 6706138f4ace..44d0c3d5824c 100644 --- a/tests/run-macros/i8007/Macro_1.scala +++ b/tests/run-macros/i8007/Macro_1.scala @@ -3,7 +3,7 @@ import scala.quoted._ import scala.quoted.matching._ import scala.compiletime.{erasedValue, summonFrom, constValue} -object Macro { +object Macro1 { case class Person(name: String, age: Int) def mirrorFields[T](t: Type[T])(given qctx: QuoteContext): List[String] = @@ -12,23 +12,21 @@ object Macro { case '[Unit] => Nil } - inline def usingSummonFrom[T](value: =>T): List[String] = - ${ usingSummonFromImpl('value) } + // Macro method 1 + // Demonstrates the use of quoted pattern matching + // over a refined type, extracting the tuple type + // for e.g., MirroredElemLabels + inline def test1[T](value: =>T): List[String] = + ${ test1Impl('value) } - def usingSummonFromImpl[T: Type](value: Expr[T])(given qctx: QuoteContext): Expr[List[String]] = { + def test1Impl[T: Type](value: Expr[T])(given qctx: QuoteContext): Expr[List[String]] = { import qctx.tasty.{_, given} - val mirrorTpe = '[Mirror.Of[T]] summonExpr(given mirrorTpe).get match { - case '{ $m: Mirror.ProductOf[T] } => { - val typeMember = TypeSelect(m.unseal, "MirroredElemLabels") - - type TT - implicit val TT: quoted.Type[TT] = typeMember.tpe.seal.asInstanceOf[quoted.Type[TT]] - - Expr(mirrorFields('[TT])) + case '{ $m: Mirror.ProductOf[T]{ type MirroredElemLabels = $t } } => { + Expr(mirrorFields(t)) } } } diff --git a/tests/run-macros/i8007/Macro_2.scala b/tests/run-macros/i8007/Macro_2.scala new file mode 100644 index 000000000000..97d6f5abb7c9 --- /dev/null +++ b/tests/run-macros/i8007/Macro_2.scala @@ -0,0 +1,58 @@ +import scala.deriving._ +import scala.quoted._ +import scala.quoted.matching._ +import scala.compiletime.{erasedValue, summonFrom, constValue} + +object Macro2 { + + def mirrorFields[T](t: Type[T])(given qctx: QuoteContext): List[String] = + t match { + case '[$field *: $fields] => field.show.substring(1, field.show.length-1) :: mirrorFields(fields) + case '[Unit] => Nil + } + + trait JsonEncoder[T] { + def encode(elem: T): String + } + + object JsonEncoder { + def emitJsonEncoder[T](body: T => String): JsonEncoder[T]= + new JsonEncoder[T] { + def encode(elem: T): String = body(elem) + } + + def derived[T: Type](ev: Expr[Mirror.Of[T]])(given qctx: QuoteContext): Expr[JsonEncoder[T]] = { + import qctx.tasty.{_, given} + + val fields = ev match { + case '{ $m: Mirror.ProductOf[T] { type MirroredElemLabels = $t } } => + mirrorFields(t) + } + + val body: Expr[T] => Expr[String] = elem => + fields.reverse.foldLeft(Expr("")){ (acc, field) => + val res = Select.unique(elem.unseal, field).seal + '{ $res.toString + " " + $acc } + } + + '{ + emitJsonEncoder((x: T) => ${body('x)}) + } + } + } + + inline def test2[T](value: =>T): Unit = ${ test2Impl('value) } + + def test2Impl[T: Type](value: Expr[T])(given qctx: QuoteContext): Expr[Unit] = { + import qctx.tasty.{_, given} + + val mirrorTpe = '[Mirror.Of[T]] + val mirrorExpr = summonExpr(given mirrorTpe).get + val derivedInstance = JsonEncoder.derived(mirrorExpr) + + '{ + val res = $derivedInstance.encode($value) + println(res) + } + } +} \ No newline at end of file diff --git a/tests/run-macros/i8007/Test_2.scala b/tests/run-macros/i8007/Test_2.scala deleted file mode 100644 index e61277547a7a..000000000000 --- a/tests/run-macros/i8007/Test_2.scala +++ /dev/null @@ -1,6 +0,0 @@ -import Macro._ - -@main def Test() = { - val list = usingSummonFrom[Person](Person("Test", 23)) - println(list) -} \ No newline at end of file diff --git a/tests/run-macros/i8007/Test_3.scala b/tests/run-macros/i8007/Test_3.scala new file mode 100644 index 000000000000..3569ebb57ac2 --- /dev/null +++ b/tests/run-macros/i8007/Test_3.scala @@ -0,0 +1,10 @@ +import Macro1._ +import Macro2._ + +@main def Test() = { + val list = test1(Person("Test", 23)) + println(list) + println + + test2(Person("Test", 23)) +} \ No newline at end of file From 53b5ddfdbeec63c82ca1554cb3ad2de0c88846a4 Mon Sep 17 00:00:00 2001 From: Aggelos Biboudis Date: Wed, 22 Jan 2020 16:20:09 +0100 Subject: [PATCH 4/8] Add macro-based implementation of the typeclass derivation example Co-authored-by: Fengyun Liu --- tests/run-macros/i8007.check | 6 +++ tests/run-macros/i8007/Macro_1.scala | 7 ++- tests/run-macros/i8007/Macro_3.scala | 68 ++++++++++++++++++++++++++++ tests/run-macros/i8007/Test_3.scala | 10 ---- tests/run-macros/i8007/Test_4.scala | 21 +++++++++ 5 files changed, 98 insertions(+), 14 deletions(-) create mode 100644 tests/run-macros/i8007/Macro_3.scala delete mode 100644 tests/run-macros/i8007/Test_3.scala create mode 100644 tests/run-macros/i8007/Test_4.scala diff --git a/tests/run-macros/i8007.check b/tests/run-macros/i8007.check index 2e2dbfdb3019..6eb80b792318 100644 --- a/tests/run-macros/i8007.check +++ b/tests/run-macros/i8007.check @@ -1,3 +1,9 @@ List("name", "age") Test 23 +() + +true + +false + diff --git a/tests/run-macros/i8007/Macro_1.scala b/tests/run-macros/i8007/Macro_1.scala index 44d0c3d5824c..2f8a6006c349 100644 --- a/tests/run-macros/i8007/Macro_1.scala +++ b/tests/run-macros/i8007/Macro_1.scala @@ -12,10 +12,9 @@ object Macro1 { case '[Unit] => Nil } - // Macro method 1 - // Demonstrates the use of quoted pattern matching - // over a refined type, extracting the tuple type - // for e.g., MirroredElemLabels + // Demonstrates the use of quoted pattern matching + // over a refined type extracting the tuple type + // for e.g., MirroredElemLabels inline def test1[T](value: =>T): List[String] = ${ test1Impl('value) } diff --git a/tests/run-macros/i8007/Macro_3.scala b/tests/run-macros/i8007/Macro_3.scala new file mode 100644 index 000000000000..cf03952967cc --- /dev/null +++ b/tests/run-macros/i8007/Macro_3.scala @@ -0,0 +1,68 @@ +import scala.deriving._ +import scala.quoted._ +import scala.quoted.matching._ +import scala.compiletime.{erasedValue, summonFrom, constValue} + +object Macro3 { + + trait Eq[T] { + def eqv(x: T, y: T): Boolean + } + + object Eq { + given Eq[String] { + def eqv(x: String, y: String) = x == y + } + + given Eq[Int] { + def eqv(x: Int, y: Int) = x == y + } + + def eqProduct[T](body: (T, T) => Boolean): Eq[T] = + new Eq[T] { + def eqv(x: T, y: T): Boolean = body(x, y) + } + + def summonAll[T](t: Type[T])(given qctx: QuoteContext): List[Expr[Eq[_]]] = t match { + case '[$tpe *: $tpes] => summonExpr(given '[Eq[$tpe]]).get :: summonAll(tpes) + case '[Unit] => Nil + } + + def derived[T: Type](ev: Expr[Mirror.Of[T]])(given qctx: QuoteContext): Expr[Eq[T]] = { + import qctx.tasty.{_, given} + + val elementTypes = ev match { + case '{ $m: Mirror.ProductOf[T] { type MirroredElemTypes = $elem } } => elem + } + + val elemInstances = summonAll(elementTypes) + + val eqProductBody: (Expr[T], Expr[T]) => Expr[Boolean] = (x, y) => { + elemInstances.zipWithIndex.foldLeft(Expr(true: Boolean)) { + case (acc, (elem, index)) => + val e1 = '{$x.asInstanceOf[Product].productElement(${Expr(index)})} + val e2 = '{$y.asInstanceOf[Product].productElement(${Expr(index)})} + '{ $acc && $elem.asInstanceOf[Eq[Any]].eqv($e1, $e2) } + } + } + + '{ + eqProduct((x: T, y: T) => ${eqProductBody('x, 'y)}) + } + } + } + + inline def test3[T](value: =>T, value2: =>T): Boolean = ${ test3Impl('value, 'value2) } + + def test3Impl[T: Type](value: Expr[T], value2: Expr[T])(given qctx: QuoteContext): Expr[Boolean] = { + import qctx.tasty.{_, given} + + val mirrorTpe = '[Mirror.Of[T]] + val mirrorExpr = summonExpr(given mirrorTpe).get + val derivedInstance = Eq.derived(mirrorExpr) + + '{ + $derivedInstance.eqv($value, $value2) + } + } +} \ No newline at end of file diff --git a/tests/run-macros/i8007/Test_3.scala b/tests/run-macros/i8007/Test_3.scala deleted file mode 100644 index 3569ebb57ac2..000000000000 --- a/tests/run-macros/i8007/Test_3.scala +++ /dev/null @@ -1,10 +0,0 @@ -import Macro1._ -import Macro2._ - -@main def Test() = { - val list = test1(Person("Test", 23)) - println(list) - println - - test2(Person("Test", 23)) -} \ No newline at end of file diff --git a/tests/run-macros/i8007/Test_4.scala b/tests/run-macros/i8007/Test_4.scala new file mode 100644 index 000000000000..9ad42a10687c --- /dev/null +++ b/tests/run-macros/i8007/Test_4.scala @@ -0,0 +1,21 @@ +import Macro1._ +import Macro2._ +import Macro3._ + +@main def Test() = { + val t1 = test1(Person("Test", 23)) + println(t1) + println + + val t2 = test2(Person("Test", 23)) + println(t2) + println + + val t3 = test3(Person("Test", 23), Person("Test", 23)) + println(t3) // true + println + + val t4 = test3(Person("Test", 23), Person("Test", 24)) + println(t4) // false + println +} \ No newline at end of file From e419209efb47c29f241283ecf68440e0522cab81 Mon Sep 17 00:00:00 2001 From: Aggelos Biboudis Date: Wed, 22 Jan 2020 17:03:13 +0100 Subject: [PATCH 5/8] Add documentation on derivation with macros --- .../reference/contextual/derivation-macro.md | 233 ++++++++++++++++++ docs/docs/reference/contextual/derivation.md | 4 + docs/docs/reference/metaprogramming/macros.md | 2 +- tests/run-macros/i8007/Macro_1.scala | 1 - tests/run-macros/i8007/Macro_2.scala | 1 - tests/run-macros/i8007/Macro_3.scala | 1 - 6 files changed, 238 insertions(+), 4 deletions(-) create mode 100644 docs/docs/reference/contextual/derivation-macro.md diff --git a/docs/docs/reference/contextual/derivation-macro.md b/docs/docs/reference/contextual/derivation-macro.md new file mode 100644 index 000000000000..76da364a9dd3 --- /dev/null +++ b/docs/docs/reference/contextual/derivation-macro.md @@ -0,0 +1,233 @@ +--- +layout: doc-page +title: How to write a type class `derived` method using macros +--- + +In the main [derivation](./derivation.md) documentation page we explaind the +details behind `Mirror`s and type class derivation. Here we demonstrate how to +implement a type class `derived` method using macros only. We follow the same +example of deriving `Eq` instances and for simplicity we support a `Product` +type e.g., a case class `Person`. The low-level method we will use to implement +the `derived` method exploits quotes, splices of both expressions and types and +the `scala.quoted.matching.summonExpr` method which is the equivalent of +`summonFrom`. The former is suitable for use in a quote context, used within +macros. + +As in the original code, the type class definition is the same: + +```scala +trait Eq[T] { + def eqv(x: T, y: T): Boolean +} +``` + +we need to implement a method `Eq.derived` on the companion object of `Eq` that +produces an instance for `Eq[T]` given a `Mirror[T]`. Here is a possible +signature, + +```scala +def derived[T: Type](ev: Expr[Mirror.Of[T]])(given qctx: QuoteContext): Expr[Eq[T]] = ??? +``` + +and for comparison reasons we give the same signature with had with `inline`: + +```scala +inline given derived[T]: (m: Mirror.Of[T]) => Eq[T] = ??? +``` + +Note, that since a type is used in a subsequent stage it will need to be lifted +to a `Type` by using the corresponding context bound. The body of this method is +shown below: + + +```scala +def derived[T: Type](m: Expr[Mirror.Of[T]])(given qctx: QuoteContext): Expr[Eq[T]] = { + import qctx.tasty.{_, given} + + val elementTypes = m match { + case '{ $m: Mirror.ProductOf[T] { type MirroredElemTypes = $elem } } => elem + } + + val elemInstances = summonAll(elementTypes) + + val eqProductBody: (Expr[T], Expr[T]) => Expr[Boolean] = (x, y) => { + elemInstances.zipWithIndex.foldLeft(Expr(true: Boolean)) { + case (acc, (elem, index)) => + val e1 = '{$x.asInstanceOf[Product].productElement(${Expr(index)})} + val e2 = '{$y.asInstanceOf[Product].productElement(${Expr(index)})} + '{ $acc && $elem.asInstanceOf[Eq[Any]].eqv($e1, $e2) } + } + } + + '{ + eqProduct((x: T, y: T) => ${eqProductBody('x, 'y)}) + } +} +``` + +Note, that in the `inline` case we can merely write +`summonAll[m.MirroredElemTypes]` inside the inline method but here, since +`summonExpr` is required if we need to query the context we need to extract the +element types in a macro fashion. Being inside a macro, our first reaction would +be to write the code below. Since the path inside the type argument is not +stable this cannot be used: + +```scala +'{ + summonAll[$m.MirroredElemTypes] +} +``` + +Instead we extract the tuple-type for element types using pattern matching over +quotes and more specifically of the refined type: + +```scala + case '{ $m: Mirror.ProductOf[T] { type MirroredElemTypes = $elem } } => elem +``` + +The implementation of `summonAll` as a macro can be show below: + +```scala +def summonAll[T](t: Type[T])(given qctx: QuoteContext): List[Expr[Eq[_]]] = t match { + case '[$tpe *: $tpes] => summonExpr(given '[Eq[$tpe]]).get :: summonAll(tpes) + case '[Unit] => Nil +} +``` + +Note, that in a realistic implementation the `summonExpr(given '[Eq[$tpe]]).get` +is going to fail if the necessary given instances for some type are not present. + +One additional difference with the body of `derived` here as opposed to the one +with `inline` is that with macros we need to synthesize the body of the code during the +macro-expansion time. That is the rationale behind the `eqProductBody` function. +Assuming that we calculate the equality of two `Person`s defined with a case +class that holds a name of type `String` and an age of type `Int`, the equality +check we want to generate is the following: + +```scala +true + && Eq[String].eqv(x.productElement(0),y.productElement(0)) + && Eq[Int].eqv(x.productElement(1), y.productElement(1)) +``` + +### Calling the derived method inside the macro + +Following the rules in [Macros](../metaprogramming.md) we create two methods. +One that hosts the top-level splice `eqv` and one that is the implementation. + +```scala +inline def eqv[T](value: =>T, value2: =>T): Boolean = ${ eqvImpl('value, 'value2) } + +def eqvImpl[T: Type](value: Expr[T], value2: Expr[T])(given qctx: QuoteContext): Expr[Boolean] = { + import qctx.tasty.{_, given} + + val mirrorTpe = '[Mirror.Of[T]] + val mirrorExpr = summonExpr(given mirrorTpe).get + val derivedInstance = Eq.derived(mirrorExpr) + + '{ + $derivedInstance.eqv($value, $value2) + } +} +``` + +Note, that we need to quote the type we need `Mirror.Of[T]` with the quoted +syntax for types and then trigger its synthesis with `summonExpr`. `mirrorExpr` +now holds the refined type for e.g., a `Person`: + +```scala +scala.deriving.Mirror { + type MirroredType >: Person <: Person + type MirroredMonoType >: Person <: Person + type MirroredElemTypes >: scala.Nothing <: scala.Tuple +} & scala.deriving.Mirror.Product { + type MirroredMonoType >: Person <: Person + type MirroredType >: Person <: Person + type MirroredLabel >: "Person" <: "Person" +} { + type MirroredElemTypes >: scala.*:[scala.Predef.String, scala.*:[scala.Int, scala.Unit]] <: scala.*:[scala.Predef.String, scala.*:[scala.Int, scala.Unit]] + type MirroredElemLabels >: scala.*:["name", scala.*:["age", scala.Unit]] <: scala.*:["name", scala.*:["age", scala.Unit]] +} +``` + +The derived instance then is finally generated with: + +```scala + val derivedInstance = Eq.derived(mirrorExpr) + + '{ + $derivedInstance.eqv($value, $value2) + } +``` + +The full code is shown below: + +```scala +import scala.deriving._ +import scala.quoted._ +import scala.quoted.matching._ + +object Macro { + + trait Eq[T] { + def eqv(x: T, y: T): Boolean + } + + object Eq { + given Eq[String] { + def eqv(x: String, y: String) = x == y + } + + given Eq[Int] { + def eqv(x: Int, y: Int) = x == y + } + + def eqProduct[T](body: (T, T) => Boolean): Eq[T] = + new Eq[T] { + def eqv(x: T, y: T): Boolean = body(x, y) + } + + def summonAll[T](t: Type[T])(given qctx: QuoteContext): List[Expr[Eq[_]]] = t match { + case '[$tpe *: $tpes] => summonExpr(given '[Eq[$tpe]]).get :: summonAll(tpes) + case '[Unit] => Nil + } + + def derived[T: Type](ev: Expr[Mirror.Of[T]])(given qctx: QuoteContext): Expr[Eq[T]] = { + import qctx.tasty.{_, given} + + val elementTypes = ev match { + case '{ $m: Mirror.ProductOf[T] { type MirroredElemTypes = $elem } } => elem + } + + val elemInstances = summonAll(elementTypes) + + val eqProductBody: (Expr[T], Expr[T]) => Expr[Boolean] = (x, y) => { + elemInstances.zipWithIndex.foldLeft(Expr(true: Boolean)) { + case (acc, (elem, index)) => + val e1 = '{$x.asInstanceOf[Product].productElement(${Expr(index)})} + val e2 = '{$y.asInstanceOf[Product].productElement(${Expr(index)})} + '{ $acc && $elem.asInstanceOf[Eq[Any]].eqv($e1, $e2) } + } + } + + '{ + eqProduct((x: T, y: T) => ${eqProductBody('x, 'y)}) + } + } + } + + inline def eqv[T](value: =>T, value2: =>T): Boolean = ${ eqvImpl('value, 'value2) } + + def eqvImpl[T: Type](value: Expr[T], value2: Expr[T])(given qctx: QuoteContext): Expr[Boolean] = { + import qctx.tasty.{_, given} + + val mirrorTpe = '[Mirror.Of[T]] + val mirrorExpr = summonExpr(given mirrorTpe).get + val derivedInstance = Eq.derived(mirrorExpr) + + '{ + $derivedInstance.eqv($value, $value2) + } + } +} +``` \ No newline at end of file diff --git a/docs/docs/reference/contextual/derivation.md b/docs/docs/reference/contextual/derivation.md index 96883ce983e6..795b4297ebd1 100644 --- a/docs/docs/reference/contextual/derivation.md +++ b/docs/docs/reference/contextual/derivation.md @@ -347,6 +347,10 @@ inline def derived[A](given gen: K0.Generic[A]): Eq[A] = gen.derive(eqSum, eqPro The framework described here enables all three of these approaches without mandating any of them. +For a brief discussion on how to use macros to write a type class `derived` +method please read more at [How to write a type class `derived` method using +macros](./derivation-macro.md). + ### Deriving instances elsewhere Sometimes one would like to derive a type class instance for an ADT after the ADT is defined, without being able to diff --git a/docs/docs/reference/metaprogramming/macros.md b/docs/docs/reference/metaprogramming/macros.md index 50286136db65..9e0214304c64 100644 --- a/docs/docs/reference/metaprogramming/macros.md +++ b/docs/docs/reference/metaprogramming/macros.md @@ -569,7 +569,7 @@ sum ### Find implicits within a macro Similarly to the `summonFrom` construct, it is possible to make implicit search available -in a quote context. For this we simply provide `scala.quoted.matching.summonExpr: +in a quote context. For this we simply provide `scala.quoted.matching.summonExpr`: ```scala inline def setFor[T]: Set[T] = ${ setForExpr[T] } diff --git a/tests/run-macros/i8007/Macro_1.scala b/tests/run-macros/i8007/Macro_1.scala index 2f8a6006c349..4341514dd0e0 100644 --- a/tests/run-macros/i8007/Macro_1.scala +++ b/tests/run-macros/i8007/Macro_1.scala @@ -1,7 +1,6 @@ import scala.deriving._ import scala.quoted._ import scala.quoted.matching._ -import scala.compiletime.{erasedValue, summonFrom, constValue} object Macro1 { case class Person(name: String, age: Int) diff --git a/tests/run-macros/i8007/Macro_2.scala b/tests/run-macros/i8007/Macro_2.scala index 97d6f5abb7c9..ed65f04bf4a8 100644 --- a/tests/run-macros/i8007/Macro_2.scala +++ b/tests/run-macros/i8007/Macro_2.scala @@ -1,7 +1,6 @@ import scala.deriving._ import scala.quoted._ import scala.quoted.matching._ -import scala.compiletime.{erasedValue, summonFrom, constValue} object Macro2 { diff --git a/tests/run-macros/i8007/Macro_3.scala b/tests/run-macros/i8007/Macro_3.scala index cf03952967cc..fb3b5ede8eb2 100644 --- a/tests/run-macros/i8007/Macro_3.scala +++ b/tests/run-macros/i8007/Macro_3.scala @@ -1,7 +1,6 @@ import scala.deriving._ import scala.quoted._ import scala.quoted.matching._ -import scala.compiletime.{erasedValue, summonFrom, constValue} object Macro3 { From a69c3ca174f975ed4d7bea3fc2af6a9ebb18f765 Mon Sep 17 00:00:00 2001 From: Aggelos Biboudis Date: Thu, 23 Jan 2020 14:59:46 +0100 Subject: [PATCH 6/8] WIP --- tests/run-macros/i8007/Macro_1.scala | 1 - tests/run-macros/i8007/Macro_3.scala | 87 ++++++++++++++++++---------- tests/run-macros/i8007/Test_4.scala | 14 +++++ 3 files changed, 69 insertions(+), 33 deletions(-) diff --git a/tests/run-macros/i8007/Macro_1.scala b/tests/run-macros/i8007/Macro_1.scala index 4341514dd0e0..20e6f5997dbf 100644 --- a/tests/run-macros/i8007/Macro_1.scala +++ b/tests/run-macros/i8007/Macro_1.scala @@ -3,7 +3,6 @@ import scala.quoted._ import scala.quoted.matching._ object Macro1 { - case class Person(name: String, age: Int) def mirrorFields[T](t: Type[T])(given qctx: QuoteContext): List[String] = t match { diff --git a/tests/run-macros/i8007/Macro_3.scala b/tests/run-macros/i8007/Macro_3.scala index fb3b5ede8eb2..4729d87397d3 100644 --- a/tests/run-macros/i8007/Macro_3.scala +++ b/tests/run-macros/i8007/Macro_3.scala @@ -2,54 +2,77 @@ import scala.deriving._ import scala.quoted._ import scala.quoted.matching._ -object Macro3 { +trait Eq[T] { + def eqv(x: T, y: T): Boolean +} + +object Eq { + given Eq[String] { + def eqv(x: String, y: String) = x == y + } - trait Eq[T] { - def eqv(x: T, y: T): Boolean + given Eq[Int] { + def eqv(x: Int, y: Int) = x == y } - object Eq { - given Eq[String] { - def eqv(x: String, y: String) = x == y + def eqProduct[T](body: (T, T) => Boolean): Eq[T] = + new Eq[T] { + def eqv(x: T, y: T): Boolean = body(x, y) } - given Eq[Int] { - def eqv(x: Int, y: Int) = x == y + def eqSum[T](body: (T, T) => Boolean): Eq[T] = + new Eq[T] { + def eqv(x: T, y: T): Boolean = body(x, y) } - def eqProduct[T](body: (T, T) => Boolean): Eq[T] = - new Eq[T] { - def eqv(x: T, y: T): Boolean = body(x, y) - } + def summonAll[T](t: Type[T])(given qctx: QuoteContext): List[Expr[Eq[_]]] = t match { + case '[$tpe *: $tpes] => + println(tpe.show) + summonExpr(given '[Eq[$tpe]]).get :: summonAll(tpes) + case '[Unit] => Nil + } - def summonAll[T](t: Type[T])(given qctx: QuoteContext): List[Expr[Eq[_]]] = t match { - case '[$tpe *: $tpes] => summonExpr(given '[Eq[$tpe]]).get :: summonAll(tpes) - case '[Unit] => Nil - } + def derived[T: Type](ev: Expr[Mirror.Of[T]])(given qctx: QuoteContext): Expr[Eq[T]] = { + import qctx.tasty.{_, given} - def derived[T: Type](ev: Expr[Mirror.Of[T]])(given qctx: QuoteContext): Expr[Eq[T]] = { - import qctx.tasty.{_, given} + ev match { + case '{ $m: Mirror.ProductOf[T] { type MirroredElemTypes = $elementTypes }} => + val elemInstances = summonAll(elementTypes) + val eqProductBody: (Expr[T], Expr[T]) => Expr[Boolean] = (x, y) => { + elemInstances.zipWithIndex.foldLeft(Expr(true: Boolean)) { + case (acc, (elem, index)) => + val e1 = '{$x.asInstanceOf[Product].productElement(${Expr(index)})} + val e2 = '{$y.asInstanceOf[Product].productElement(${Expr(index)})} - val elementTypes = ev match { - case '{ $m: Mirror.ProductOf[T] { type MirroredElemTypes = $elem } } => elem - } + '{ $acc && $elem.asInstanceOf[Eq[Any]].eqv($e1, $e2) } + } + } + '{ + eqProduct((x: T, y: T) => ${eqProductBody('x, 'y)}) + } - val elemInstances = summonAll(elementTypes) + case '{ $m: Mirror.SumOf[T] { type MirroredElemTypes = $elementTypes }} => + val elemInstances = summonAll(elementTypes) + val eqSumBody: (Expr[T], Expr[T]) => Expr[Boolean] = (x, y) => { + val ordx = '{ $m.ordinal($x) } + val ordy = '{ $m.ordinal($y) } - val eqProductBody: (Expr[T], Expr[T]) => Expr[Boolean] = (x, y) => { - elemInstances.zipWithIndex.foldLeft(Expr(true: Boolean)) { - case (acc, (elem, index)) => - val e1 = '{$x.asInstanceOf[Product].productElement(${Expr(index)})} - val e2 = '{$y.asInstanceOf[Product].productElement(${Expr(index)})} - '{ $acc && $elem.asInstanceOf[Eq[Any]].eqv($e1, $e2) } + val elements = Expr.ofList(elemInstances) + '{ + $ordx == $ordy && $elements($ordx).asInstanceOf[Eq[Any]].eqv($x, $y) + } } - } - '{ - eqProduct((x: T, y: T) => ${eqProductBody('x, 'y)}) - } + '{ + eqSum((x: T, y: T) => ${eqSumBody('x, 'y)}) + } } } +} + +object Macro3 { + + inline def test3[T](value: =>T, value2: =>T): Boolean = ${ test3Impl('value, 'value2) } diff --git a/tests/run-macros/i8007/Test_4.scala b/tests/run-macros/i8007/Test_4.scala index 9ad42a10687c..93793a72ae56 100644 --- a/tests/run-macros/i8007/Test_4.scala +++ b/tests/run-macros/i8007/Test_4.scala @@ -1,8 +1,18 @@ import Macro1._ import Macro2._ import Macro3._ +import scala.deriving._ + +case class Person(name: String, age: Int) + +enum Opt[+T] derives Eq { + case Sm(t: T) + case Nn +} @main def Test() = { + import Opt._ + val t1 = test1(Person("Test", 23)) println(t1) println @@ -18,4 +28,8 @@ import Macro3._ val t4 = test3(Person("Test", 23), Person("Test", 24)) println(t4) // false println + + val t5 = test3(Sm(Person("Test", 23)), Sm(Person("Test", 23))) + println(t5) // true + println } \ No newline at end of file From 06e61a94eabe5a230f6e38874306cb88763bcd37 Mon Sep 17 00:00:00 2001 From: Aggelos Biboudis Date: Thu, 23 Jan 2020 16:41:28 +0100 Subject: [PATCH 7/8] Implement full example with Mirrors of Sums and Products --- tests/run-macros/i8007.check | 6 ++++++ tests/run-macros/i8007/Macro_3.scala | 28 ++++++++++------------------ tests/run-macros/i8007/Test_4.scala | 19 ++++++++++++++----- 3 files changed, 30 insertions(+), 23 deletions(-) diff --git a/tests/run-macros/i8007.check b/tests/run-macros/i8007.check index 6eb80b792318..0ccbe496ef31 100644 --- a/tests/run-macros/i8007.check +++ b/tests/run-macros/i8007.check @@ -7,3 +7,9 @@ true false +true + +true + +false + diff --git a/tests/run-macros/i8007/Macro_3.scala b/tests/run-macros/i8007/Macro_3.scala index 4729d87397d3..69bea8009357 100644 --- a/tests/run-macros/i8007/Macro_3.scala +++ b/tests/run-macros/i8007/Macro_3.scala @@ -27,14 +27,19 @@ object Eq { def summonAll[T](t: Type[T])(given qctx: QuoteContext): List[Expr[Eq[_]]] = t match { case '[$tpe *: $tpes] => - println(tpe.show) - summonExpr(given '[Eq[$tpe]]).get :: summonAll(tpes) + val eqInstance = summonExpr(given '[Eq[$tpe]]) match { + case Some(ev) => ev + case None => derived(given tpe, qctx) + } + eqInstance :: summonAll(tpes) case '[Unit] => Nil } - def derived[T: Type](ev: Expr[Mirror.Of[T]])(given qctx: QuoteContext): Expr[Eq[T]] = { + given derived[T: Type](given qctx: QuoteContext): Expr[Eq[T]] = { import qctx.tasty.{_, given} + val ev: Expr[Mirror.Of[T]] = summonExpr(given '[Mirror.Of[T]]).get + ev match { case '{ $m: Mirror.ProductOf[T] { type MirroredElemTypes = $elementTypes }} => val elemInstances = summonAll(elementTypes) @@ -71,20 +76,7 @@ object Eq { } object Macro3 { + inline def [T](x: =>T) === (y: =>T)(given eq: Eq[T]): Boolean = eq.eqv(x, y) - - - inline def test3[T](value: =>T, value2: =>T): Boolean = ${ test3Impl('value, 'value2) } - - def test3Impl[T: Type](value: Expr[T], value2: Expr[T])(given qctx: QuoteContext): Expr[Boolean] = { - import qctx.tasty.{_, given} - - val mirrorTpe = '[Mirror.Of[T]] - val mirrorExpr = summonExpr(given mirrorTpe).get - val derivedInstance = Eq.derived(mirrorExpr) - - '{ - $derivedInstance.eqv($value, $value2) - } - } + implicit inline def eqGen[T]: Eq[T] = ${ Eq.derived[T] } } \ No newline at end of file diff --git a/tests/run-macros/i8007/Test_4.scala b/tests/run-macros/i8007/Test_4.scala index 93793a72ae56..1809d2e023eb 100644 --- a/tests/run-macros/i8007/Test_4.scala +++ b/tests/run-macros/i8007/Test_4.scala @@ -1,17 +1,18 @@ import Macro1._ import Macro2._ import Macro3._ -import scala.deriving._ +import Macro3.eqGen case class Person(name: String, age: Int) -enum Opt[+T] derives Eq { +enum Opt[+T] { case Sm(t: T) case Nn } @main def Test() = { import Opt._ + import Eq.{given, _} val t1 = test1(Person("Test", 23)) println(t1) @@ -21,15 +22,23 @@ enum Opt[+T] derives Eq { println(t2) println - val t3 = test3(Person("Test", 23), Person("Test", 23)) + val t3 = Person("Test", 23) === Person("Test", 23) println(t3) // true println - val t4 = test3(Person("Test", 23), Person("Test", 24)) + val t4 = Person("Test", 23) === Person("Test", 24) println(t4) // false println - val t5 = test3(Sm(Person("Test", 23)), Sm(Person("Test", 23))) + val t5 = Sm(23) === Sm(23) println(t5) // true println + + val t6 = Sm(Person("Test", 23)) === Sm(Person("Test", 23)) + println(t6) // true + println + + val t7 = Sm(Person("Test", 23)) === Sm(Person("Test", 24)) + println(t7) // false + println } \ No newline at end of file From 4033d71ae648c1990cf0bd5e2709a9067a69c3be Mon Sep 17 00:00:00 2001 From: Aggelos Biboudis Date: Thu, 23 Jan 2020 17:11:35 +0100 Subject: [PATCH 8/8] Update documentation --- .../reference/contextual/derivation-macro.md | 220 +++++++++--------- tests/run-macros/i8007/Macro_3.scala | 9 +- 2 files changed, 108 insertions(+), 121 deletions(-) diff --git a/docs/docs/reference/contextual/derivation-macro.md b/docs/docs/reference/contextual/derivation-macro.md index 76da364a9dd3..89e794037d97 100644 --- a/docs/docs/reference/contextual/derivation-macro.md +++ b/docs/docs/reference/contextual/derivation-macro.md @@ -3,7 +3,7 @@ layout: doc-page title: How to write a type class `derived` method using macros --- -In the main [derivation](./derivation.md) documentation page we explaind the +In the main [derivation](./derivation.md) documentation page, we explained the details behind `Mirror`s and type class derivation. Here we demonstrate how to implement a type class `derived` method using macros only. We follow the same example of deriving `Eq` instances and for simplicity we support a `Product` @@ -22,55 +22,57 @@ trait Eq[T] { ``` we need to implement a method `Eq.derived` on the companion object of `Eq` that -produces an instance for `Eq[T]` given a `Mirror[T]`. Here is a possible -signature, +produces a quoted instance for `Eq[T]`. Here is a possible signature, ```scala -def derived[T: Type](ev: Expr[Mirror.Of[T]])(given qctx: QuoteContext): Expr[Eq[T]] = ??? +given derived[T: Type](given qctx: QuoteContext): Expr[Eq[T]] ``` -and for comparison reasons we give the same signature with had with `inline`: +and for comparison reasons we give the same signature we had with `inline`: ```scala inline given derived[T]: (m: Mirror.Of[T]) => Eq[T] = ??? ``` Note, that since a type is used in a subsequent stage it will need to be lifted -to a `Type` by using the corresponding context bound. The body of this method is -shown below: +to a `Type` by using the corresponding context bound. Also, not that we can +summon the quoted `Mirror` inside the body of the `derived` this we can omit it +from the signature. The body of the `derived` method is shown below: ```scala -def derived[T: Type](m: Expr[Mirror.Of[T]])(given qctx: QuoteContext): Expr[Eq[T]] = { +given derived[T: Type](given qctx: QuoteContext): Expr[Eq[T]] = { import qctx.tasty.{_, given} - val elementTypes = m match { - case '{ $m: Mirror.ProductOf[T] { type MirroredElemTypes = $elem } } => elem - } + val ev: Expr[Mirror.Of[T]] = summonExpr(given '[Mirror.Of[T]]).get - val elemInstances = summonAll(elementTypes) + ev match { + case '{ $m: Mirror.ProductOf[T] { type MirroredElemTypes = $elementTypes }} => + val elemInstances = summonAll(elementTypes) + val eqProductBody: (Expr[T], Expr[T]) => Expr[Boolean] = (x, y) => { + elemInstances.zipWithIndex.foldLeft(Expr(true: Boolean)) { + case (acc, (elem, index)) => + val e1 = '{$x.asInstanceOf[Product].productElement(${Expr(index)})} + val e2 = '{$y.asInstanceOf[Product].productElement(${Expr(index)})} - val eqProductBody: (Expr[T], Expr[T]) => Expr[Boolean] = (x, y) => { - elemInstances.zipWithIndex.foldLeft(Expr(true: Boolean)) { - case (acc, (elem, index)) => - val e1 = '{$x.asInstanceOf[Product].productElement(${Expr(index)})} - val e2 = '{$y.asInstanceOf[Product].productElement(${Expr(index)})} - '{ $acc && $elem.asInstanceOf[Eq[Any]].eqv($e1, $e2) } - } - } + '{ $acc && $elem.asInstanceOf[Eq[Any]].eqv($e1, $e2) } + } + } + '{ + eqProduct((x: T, y: T) => ${eqProductBody('x, 'y)}) + } - '{ - eqProduct((x: T, y: T) => ${eqProductBody('x, 'y)}) + // case for Mirror.ProductOf[T] + // ... } } ``` Note, that in the `inline` case we can merely write `summonAll[m.MirroredElemTypes]` inside the inline method but here, since -`summonExpr` is required if we need to query the context we need to extract the -element types in a macro fashion. Being inside a macro, our first reaction would -be to write the code below. Since the path inside the type argument is not -stable this cannot be used: +`summonExpr` is required, we can extract the element types in a macro fashion. +Being inside a macro, our first reaction would be to write the code below. Since +the path inside the type argument is not stable this cannot be used: ```scala '{ @@ -82,21 +84,21 @@ Instead we extract the tuple-type for element types using pattern matching over quotes and more specifically of the refined type: ```scala - case '{ $m: Mirror.ProductOf[T] { type MirroredElemTypes = $elem } } => elem + case '{ $m: Mirror.ProductOf[T] { type MirroredElemTypes = $elementTypes } } => ... ``` -The implementation of `summonAll` as a macro can be show below: +The implementation of `summonAll` as a macro can be show below assuming that we +have the given instances for our primitive types: ```scala -def summonAll[T](t: Type[T])(given qctx: QuoteContext): List[Expr[Eq[_]]] = t match { - case '[$tpe *: $tpes] => summonExpr(given '[Eq[$tpe]]).get :: summonAll(tpes) - case '[Unit] => Nil -} + def summonAll[T](t: Type[T])(given qctx: QuoteContext): List[Expr[Eq[_]]] = t match { + case '[String *: $tpes] => '{ summon[Eq[String]] } :: summonAll(tpes) + case '[Int *: $tpes] => '{ summon[Eq[Int]] } :: summonAll(tpes) + case '[$tpe *: $tpes] => derived(given tpe, qctx) :: summonAll(tpes) + case '[Unit] => Nil + } ``` -Note, that in a realistic implementation the `summonExpr(given '[Eq[$tpe]]).get` -is going to fail if the necessary given instances for some type are not present. - One additional difference with the body of `derived` here as opposed to the one with `inline` is that with macros we need to synthesize the body of the code during the macro-expansion time. That is the rationale behind the `eqProductBody` function. @@ -114,50 +116,26 @@ true Following the rules in [Macros](../metaprogramming.md) we create two methods. One that hosts the top-level splice `eqv` and one that is the implementation. +Alternatively and what is shown below is that we can call the `eqv` method +directly. The `eqGen` can trigger the derivation. ```scala -inline def eqv[T](value: =>T, value2: =>T): Boolean = ${ eqvImpl('value, 'value2) } - -def eqvImpl[T: Type](value: Expr[T], value2: Expr[T])(given qctx: QuoteContext): Expr[Boolean] = { - import qctx.tasty.{_, given} +inline def [T](x: =>T) === (y: =>T)(given eq: Eq[T]): Boolean = eq.eqv(x, y) - val mirrorTpe = '[Mirror.Of[T]] - val mirrorExpr = summonExpr(given mirrorTpe).get - val derivedInstance = Eq.derived(mirrorExpr) - - '{ - $derivedInstance.eqv($value, $value2) - } -} +implicit inline def eqGen[T]: Eq[T] = ${ Eq.derived[T] } ``` -Note, that we need to quote the type we need `Mirror.Of[T]` with the quoted -syntax for types and then trigger its synthesis with `summonExpr`. `mirrorExpr` -now holds the refined type for e.g., a `Person`: +Note, that we use inline method syntax and we can compare instance such as +`Sm(Person("Test", 23)) === Sm(Person("Test", 24))` for e.g., the following two +types: ```scala -scala.deriving.Mirror { - type MirroredType >: Person <: Person - type MirroredMonoType >: Person <: Person - type MirroredElemTypes >: scala.Nothing <: scala.Tuple -} & scala.deriving.Mirror.Product { - type MirroredMonoType >: Person <: Person - type MirroredType >: Person <: Person - type MirroredLabel >: "Person" <: "Person" -} { - type MirroredElemTypes >: scala.*:[scala.Predef.String, scala.*:[scala.Int, scala.Unit]] <: scala.*:[scala.Predef.String, scala.*:[scala.Int, scala.Unit]] - type MirroredElemLabels >: scala.*:["name", scala.*:["age", scala.Unit]] <: scala.*:["name", scala.*:["age", scala.Unit]] -} -``` - -The derived instance then is finally generated with: - -```scala - val derivedInstance = Eq.derived(mirrorExpr) +case class Person(name: String, age: Int) - '{ - $derivedInstance.eqv($value, $value2) - } +enum Opt[+T] { + case Sm(t: T) + case Nn +} ``` The full code is shown below: @@ -167,67 +145,79 @@ import scala.deriving._ import scala.quoted._ import scala.quoted.matching._ -object Macro { +trait Eq[T] { + def eqv(x: T, y: T): Boolean +} + +object Eq { + given Eq[String] { + def eqv(x: String, y: String) = x == y + } - trait Eq[T] { - def eqv(x: T, y: T): Boolean + given Eq[Int] { + def eqv(x: Int, y: Int) = x == y } - object Eq { - given Eq[String] { - def eqv(x: String, y: String) = x == y + def eqProduct[T](body: (T, T) => Boolean): Eq[T] = + new Eq[T] { + def eqv(x: T, y: T): Boolean = body(x, y) } - given Eq[Int] { - def eqv(x: Int, y: Int) = x == y + def eqSum[T](body: (T, T) => Boolean): Eq[T] = + new Eq[T] { + def eqv(x: T, y: T): Boolean = body(x, y) } - def eqProduct[T](body: (T, T) => Boolean): Eq[T] = - new Eq[T] { - def eqv(x: T, y: T): Boolean = body(x, y) - } + def summonAll[T](t: Type[T])(given qctx: QuoteContext): List[Expr[Eq[_]]] = t match { + case '[String *: $tpes] => '{ summon[Eq[String]] } :: summonAll(tpes) + case '[Int *: $tpes] => '{ summon[Eq[Int]] } :: summonAll(tpes) + case '[$tpe *: $tpes] => derived(given tpe, qctx) :: summonAll(tpes) + case '[Unit] => Nil + } - def summonAll[T](t: Type[T])(given qctx: QuoteContext): List[Expr[Eq[_]]] = t match { - case '[$tpe *: $tpes] => summonExpr(given '[Eq[$tpe]]).get :: summonAll(tpes) - case '[Unit] => Nil - } + given derived[T: Type](given qctx: QuoteContext): Expr[Eq[T]] = { + import qctx.tasty.{_, given} - def derived[T: Type](ev: Expr[Mirror.Of[T]])(given qctx: QuoteContext): Expr[Eq[T]] = { - import qctx.tasty.{_, given} + val ev: Expr[Mirror.Of[T]] = summonExpr(given '[Mirror.Of[T]]).get - val elementTypes = ev match { - case '{ $m: Mirror.ProductOf[T] { type MirroredElemTypes = $elem } } => elem - } + ev match { + case '{ $m: Mirror.ProductOf[T] { type MirroredElemTypes = $elementTypes }} => + val elemInstances = summonAll(elementTypes) + val eqProductBody: (Expr[T], Expr[T]) => Expr[Boolean] = (x, y) => { + elemInstances.zipWithIndex.foldLeft(Expr(true: Boolean)) { + case (acc, (elem, index)) => + val e1 = '{$x.asInstanceOf[Product].productElement(${Expr(index)})} + val e2 = '{$y.asInstanceOf[Product].productElement(${Expr(index)})} - val elemInstances = summonAll(elementTypes) + '{ $acc && $elem.asInstanceOf[Eq[Any]].eqv($e1, $e2) } + } + } + '{ + eqProduct((x: T, y: T) => ${eqProductBody('x, 'y)}) + } - val eqProductBody: (Expr[T], Expr[T]) => Expr[Boolean] = (x, y) => { - elemInstances.zipWithIndex.foldLeft(Expr(true: Boolean)) { - case (acc, (elem, index)) => - val e1 = '{$x.asInstanceOf[Product].productElement(${Expr(index)})} - val e2 = '{$y.asInstanceOf[Product].productElement(${Expr(index)})} - '{ $acc && $elem.asInstanceOf[Eq[Any]].eqv($e1, $e2) } + case '{ $m: Mirror.SumOf[T] { type MirroredElemTypes = $elementTypes }} => + val elemInstances = summonAll(elementTypes) + val eqSumBody: (Expr[T], Expr[T]) => Expr[Boolean] = (x, y) => { + val ordx = '{ $m.ordinal($x) } + val ordy = '{ $m.ordinal($y) } + + val elements = Expr.ofList(elemInstances) + '{ + $ordx == $ordy && $elements($ordx).asInstanceOf[Eq[Any]].eqv($x, $y) + } } - } - '{ - eqProduct((x: T, y: T) => ${eqProductBody('x, 'y)}) - } + '{ + eqSum((x: T, y: T) => ${eqSumBody('x, 'y)}) + } } } +} - inline def eqv[T](value: =>T, value2: =>T): Boolean = ${ eqvImpl('value, 'value2) } - - def eqvImpl[T: Type](value: Expr[T], value2: Expr[T])(given qctx: QuoteContext): Expr[Boolean] = { - import qctx.tasty.{_, given} - - val mirrorTpe = '[Mirror.Of[T]] - val mirrorExpr = summonExpr(given mirrorTpe).get - val derivedInstance = Eq.derived(mirrorExpr) +object Macro3 { + inline def [T](x: =>T) === (y: =>T)(given eq: Eq[T]): Boolean = eq.eqv(x, y) - '{ - $derivedInstance.eqv($value, $value2) - } - } + implicit inline def eqGen[T]: Eq[T] = ${ Eq.derived[T] } } ``` \ No newline at end of file diff --git a/tests/run-macros/i8007/Macro_3.scala b/tests/run-macros/i8007/Macro_3.scala index 69bea8009357..d530f86427b6 100644 --- a/tests/run-macros/i8007/Macro_3.scala +++ b/tests/run-macros/i8007/Macro_3.scala @@ -26,12 +26,9 @@ object Eq { } def summonAll[T](t: Type[T])(given qctx: QuoteContext): List[Expr[Eq[_]]] = t match { - case '[$tpe *: $tpes] => - val eqInstance = summonExpr(given '[Eq[$tpe]]) match { - case Some(ev) => ev - case None => derived(given tpe, qctx) - } - eqInstance :: summonAll(tpes) + case '[String *: $tpes] => '{ summon[Eq[String]] } :: summonAll(tpes) + case '[Int *: $tpes] => '{ summon[Eq[Int]] } :: summonAll(tpes) + case '[$tpe *: $tpes] => derived(given tpe, qctx) :: summonAll(tpes) case '[Unit] => Nil }