From 7f25db90f81f713ed1977395f3a735737b450707 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 12 Apr 2021 14:41:24 +0200 Subject: [PATCH 1/4] 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 4b56143afad4eb6a94d771706b8c5012475571cb Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 12 Apr 2021 14:58:25 +0200 Subject: [PATCH 2/4] 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 e6873c40c406a3ed192a64e86d5ad7cc9af12b6b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 13 Apr 2021 10:15:43 +0200 Subject: [PATCH 3/4] 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: From cfff8c4bb84320ab2012d54d81ead5f61ff35007 Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 13 Apr 2021 11:07:13 +0200 Subject: [PATCH 4/4] Address review comment --- compiler/src/dotty/tools/dotc/CompilationUnit.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/src/dotty/tools/dotc/CompilationUnit.scala b/compiler/src/dotty/tools/dotc/CompilationUnit.scala index ffb0a98875e4..e858e01efc48 100644 --- a/compiler/src/dotty/tools/dotc/CompilationUnit.scala +++ b/compiler/src/dotty/tools/dotc/CompilationUnit.scala @@ -43,6 +43,7 @@ class CompilationUnit protected (val source: SourceFile) { */ var needsInlining: Boolean = false + /** Set to `true` if inliner added anonymous mirrors that need to be completed */ var needsMirrorSupport: Boolean = false /** Will be set to `true` if contains `Quote`.