From 833ea4f51bb6bf311c932f6c9994ae017856d0f0 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 12 Apr 2021 14:41:24 +0200 Subject: [PATCH 1/3] Enter Mirror elements after current phase --- .../src/dotty/tools/dotc/transform/SyntheticMembers.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala index d964e5a6c585..817be9cba633 100644 --- a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala +++ b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala @@ -552,7 +552,7 @@ class SyntheticMembers(thisPhase: DenotTransformer) { def addMethod(name: TermName, info: Type, cls: Symbol, body: (Symbol, Tree) => Context ?=> Tree): Unit = { val meth = newSymbol(clazz, name, Synthetic | Method, info, coord = clazz.coord) if (!existingDef(meth, clazz).exists) { - meth.entered + meth.enteredAfter(thisPhase) newBody = newBody :+ synthesizeDef(meth, vrefss => body(cls, vrefss.head.head)) } @@ -565,7 +565,7 @@ class SyntheticMembers(thisPhase: DenotTransformer) { val monoType = newSymbol(clazz, tpnme.MirroredMonoType, Synthetic, TypeAlias(linked.reachableRawTypeRef), coord = clazz.coord) newBody = newBody :+ TypeDef(monoType).withSpan(ctx.owner.span.focus) - monoType.entered + monoType.enteredAfter(thisPhase) } } def makeSingletonMirror() = From 3f9cb775b2a8acd1de35c9454ef756ccd1b330c8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 12 Apr 2021 14:58:25 +0200 Subject: [PATCH 2/3] Provide mirror support after inlining Mirror support runs in PostTyper to add new members to mirrors generated during Typer. But some anonymous mirrors are generated during inlining. We need to add the missing methods for them as well. Fixes #11542 Fixes #11961 Fixes #12052 --- .../dotty/tools/dotc/CompilationUnit.scala | 2 + compiler/src/dotty/tools/dotc/Compiler.scala | 1 + .../tools/dotc/transform/PostInlining.scala | 29 +++++++++++++ .../dotty/tools/dotc/typer/Synthesizer.scala | 1 + tests/run/i11542.scala | 31 ++++++++++++++ tests/run/i11542a.scala | 6 +++ tests/run/i11961.check | 4 ++ tests/run/i11961.scala | 41 +++++++++++++++++++ tests/run/i12052/MirrorType.scala | 34 +++++++++++++++ tests/run/i12052/Test.scala | 9 ++++ 10 files changed, 158 insertions(+) create mode 100644 compiler/src/dotty/tools/dotc/transform/PostInlining.scala create mode 100644 tests/run/i11542.scala create mode 100644 tests/run/i11542a.scala create mode 100644 tests/run/i11961.check create mode 100644 tests/run/i11961.scala create mode 100644 tests/run/i12052/MirrorType.scala create mode 100644 tests/run/i12052/Test.scala diff --git a/compiler/src/dotty/tools/dotc/CompilationUnit.scala b/compiler/src/dotty/tools/dotc/CompilationUnit.scala index a4f219c88510..ffb0a98875e4 100644 --- a/compiler/src/dotty/tools/dotc/CompilationUnit.scala +++ b/compiler/src/dotty/tools/dotc/CompilationUnit.scala @@ -43,6 +43,8 @@ class CompilationUnit protected (val source: SourceFile) { */ var needsInlining: Boolean = false + var needsMirrorSupport: Boolean = false + /** Will be set to `true` if contains `Quote`. * The information is used in phase `Staging` in order to avoid traversing trees that need no transformations. */ diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 4818fca07d0b..ef0acb58492b 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -50,6 +50,7 @@ class Compiler { protected def picklerPhases: List[List[Phase]] = List(new Pickler) :: // Generate TASTY info List(new Inlining) :: // Inline and execute macros + List(new PostInlining) :: // Add mirror support for inlined code List(new Staging) :: // Check staging levels and heal staged types List(new PickleQuotes) :: // Turn quoted trees into explicit run-time data structures Nil diff --git a/compiler/src/dotty/tools/dotc/transform/PostInlining.scala b/compiler/src/dotty/tools/dotc/transform/PostInlining.scala new file mode 100644 index 000000000000..bd43cadc1397 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/PostInlining.scala @@ -0,0 +1,29 @@ +package dotty.tools.dotc +package transform + +import core._ +import Contexts.* +import DenotTransformers.IdentityDenotTransformer +import Decorators.* +import ast.tpd.* + +/** A phase that adds mirror support for anonymous mirrors created at inlining. */ +class PostInlining extends MacroTransform, IdentityDenotTransformer: + thisPhase => + + override def phaseName: String = PostInlining.name + override def changesMembers = true + + override def run(using Context): Unit = + if ctx.compilationUnit.needsMirrorSupport then super.run + + lazy val synthMbr: SyntheticMembers = new SyntheticMembers(thisPhase) + + def newTransformer(using Context): Transformer = new Transformer: + override def transform(tree: Tree)(using Context): Tree = + super.transform(tree) match + case tree1: Template => synthMbr.addMirrorSupport(tree1) + case tree1 => tree1 + +object PostInlining: + val name: String = "postInlining" \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala index 76c8523adb14..0406c98d790b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala @@ -180,6 +180,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): * and mark it with given attachment so that it is made into a mirror at PostTyper. */ private def anonymousMirror(monoType: Type, attachment: Property.StickyKey[Unit], span: Span)(using Context) = + if ctx.isAfterTyper then ctx.compilationUnit.needsMirrorSupport = true val monoTypeDef = untpd.TypeDef(tpnme.MirroredMonoType, untpd.TypeTree(monoType)) val newImpl = untpd.Template( constr = untpd.emptyConstructor, diff --git a/tests/run/i11542.scala b/tests/run/i11542.scala new file mode 100644 index 000000000000..90c8471bdf03 --- /dev/null +++ b/tests/run/i11542.scala @@ -0,0 +1,31 @@ +object demo { + + trait Reader[A] + + given Reader[Int] with {} + + inline def summonReader[T <: Tuple]: List[Reader[_]] = inline compiletime.erasedValue[T] match { + case _: EmptyTuple => Nil + case _: (t *: ts) => compiletime.summonInline[Reader[t]] :: summonReader[ts] + } + + class CombinedReader[A]( + m: deriving.Mirror.ProductOf[A], + childReaders: List[Reader[_]] + ) extends Reader[A] + + inline given rdr[A <: Tuple](using m: deriving.Mirror.ProductOf[A]): Reader[A] = { + new CombinedReader(m, summonReader[m.MirroredElemTypes]) + } + +} + +@main def Test() = { + // OK + //summon[demo.Reader[(Int, Int, Int)]] + + // Exception in thread "main" java.lang.ClassCastException: class main$package$$anon$2 cannot be cast to class scala.deriving.Mirror$Product (main$package$$anon$2 and scala.deriving.Mirror$Product are in unnamed module of loader 'app') + // at main$package$.run(main.scala:25) + // at run.main(main.scala:23) + summon[demo.Reader[(Int, (Int, Int))]] +} \ No newline at end of file diff --git a/tests/run/i11542a.scala b/tests/run/i11542a.scala new file mode 100644 index 000000000000..db4142fb1a86 --- /dev/null +++ b/tests/run/i11542a.scala @@ -0,0 +1,6 @@ +type Foo = Tuple2[Int, Int] +// case class Foo(x: Int, y: Int) // works +class Reader(m: deriving.Mirror.ProductOf[Foo]) +given reader1(using m: deriving.Mirror.ProductOf[Foo]): Reader = new Reader(m) +inline def summonReader(): Reader = compiletime.summonInline[Reader] +@main def Test() = summonReader() diff --git a/tests/run/i11961.check b/tests/run/i11961.check new file mode 100644 index 000000000000..a9eee48cacc5 --- /dev/null +++ b/tests/run/i11961.check @@ -0,0 +1,4 @@ +STRING +BOOLEAN +STRING +BOOLEAN diff --git a/tests/run/i11961.scala b/tests/run/i11961.scala new file mode 100644 index 000000000000..f289f6b415b6 --- /dev/null +++ b/tests/run/i11961.scala @@ -0,0 +1,41 @@ +import scala.deriving.* +import scala.compiletime.{erasedValue, summonInline} + +case class Simple(a: String, b: Boolean) derives Printable +case class SimpleT(a: (String, Boolean)) derives Printable + +@main def Test: Unit = { + + summon[Printable[Simple]].print // Prints STRING BOOLEAN as expected + + summon[Printable[SimpleT]].print // java.lang.ClassCastException: SimpleT$$anon$1 cannot be cast to scala.deriving.Mirror$Product + +} + +trait Printable[T]: + def print: Unit + +object Printable: + + given Printable[String] with + def print: Unit = println("STRING") + + given Printable[Boolean] with + def print: Unit = println("BOOLEAN") + + def printProduct[T](p: Mirror.ProductOf[T], elems: => List[Printable[_]]): Printable[T] = + new Printable[T]: + def print: Unit = + elems.foreach(_.print) + + inline given derived[T](using m: Mirror.Of[T]): Printable[T] = + val elemInstances = summonAllPrintable[m.MirroredElemTypes] + inline m match + case p: Mirror.ProductOf[T] => printProduct(p, elemInstances) + +end Printable + +inline def summonAllPrintable[T <: Tuple]: List[Printable[_]] = + inline erasedValue[T] match + case _: EmptyTuple => Nil + case _: (t *: ts) => summonInline[Printable[t]] :: summonAllPrintable[ts] diff --git a/tests/run/i12052/MirrorType.scala b/tests/run/i12052/MirrorType.scala new file mode 100644 index 000000000000..b8b0b442690e --- /dev/null +++ b/tests/run/i12052/MirrorType.scala @@ -0,0 +1,34 @@ +import scala.quoted._ +import scala.deriving._ +import scala.compiletime.{erasedValue, constValue, summonFrom, summonInline} + +class MyContext { + implicit inline def autoMirrorType[T]: MirrorType[T] = MirrorType.generic +} + +trait MirrorType[T] { + def mirrorType: String +} + +object MirrorType { + class Container[T] + + inline def decode[T]: String = + summonFrom { + case ev: Mirror.ProductOf[T] => + s"Product-${new Container[ev.MirroredElemLabels]}" // This is the part that splices in the cast + case m: Mirror.SumOf[T] => + "Sum" + } + + inline def generic[T]: MirrorType[T] = + new MirrorType[T] { + def mirrorType: String = decode[T] + } + + extension[T](inline value: T) + inline def mirrorType = summonFrom { + case mt: MirrorType[T] => mt.mirrorType + case _ => "mirror not found" + } +} \ No newline at end of file diff --git a/tests/run/i12052/Test.scala b/tests/run/i12052/Test.scala new file mode 100644 index 000000000000..07ffb2a98f3e --- /dev/null +++ b/tests/run/i12052/Test.scala @@ -0,0 +1,9 @@ +import MirrorType._ +object Test { + def main(args: Array[String]): Unit = { + val ctx = new MyContext(); + import ctx._ + val tup = ("foo", 1) + assert(tup.mirrorType.isInstanceOf[String]) + } +} \ No newline at end of file From 8b8f42f1ab658f68da3c05b4b1803b6fb4b91b0a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 13 Apr 2021 10:15:43 +0200 Subject: [PATCH 3/3] Fix double generation for mirror support --- compiler/src/dotty/tools/dotc/transform/PostInlining.scala | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/PostInlining.scala b/compiler/src/dotty/tools/dotc/transform/PostInlining.scala index bd43cadc1397..54e654781aed 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostInlining.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostInlining.scala @@ -5,6 +5,7 @@ import core._ import Contexts.* import DenotTransformers.IdentityDenotTransformer import Decorators.* +import SyntheticMembers.* import ast.tpd.* /** A phase that adds mirror support for anonymous mirrors created at inlining. */ @@ -22,7 +23,11 @@ class PostInlining extends MacroTransform, IdentityDenotTransformer: def newTransformer(using Context): Transformer = new Transformer: override def transform(tree: Tree)(using Context): Tree = super.transform(tree) match - case tree1: Template => synthMbr.addMirrorSupport(tree1) + case tree1: Template + if tree1.hasAttachment(ExtendsSingletonMirror) + || tree1.hasAttachment(ExtendsProductMirror) + || tree1.hasAttachment(ExtendsSumMirror) => + synthMbr.addMirrorSupport(tree1) case tree1 => tree1 object PostInlining: