From 3f71bd5e4f355762b156f55018bc8e0e9cb69b70 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Tue, 12 Dec 2023 17:55:07 +0100 Subject: [PATCH 01/11] compare reading java from tasty with from classfile --- .../pipelining/Yjava-tasty-annotation/b-alt/.keep | 0 .../pipelining/Yjava-tasty-annotation/build.sbt | 8 ++++++++ sbt-test/pipelining/Yjava-tasty-annotation/test | 2 ++ sbt-test/pipelining/Yjava-tasty-enum/b-alt/.keep | 0 sbt-test/pipelining/Yjava-tasty-enum/build.sbt | 12 ++++++++++++ sbt-test/pipelining/Yjava-tasty-enum/test | 2 ++ .../pipelining/Yjava-tasty-from-tasty/b-alt/.keep | 0 .../pipelining/Yjava-tasty-from-tasty/build.sbt | 14 ++++++++++++++ sbt-test/pipelining/Yjava-tasty-from-tasty/test | 2 ++ .../pipelining/Yjava-tasty-generic/b-alt/.keep | 0 sbt-test/pipelining/Yjava-tasty-generic/build.sbt | 12 ++++++++++++ sbt-test/pipelining/Yjava-tasty-generic/test | 2 ++ .../Yjava-tasty-result-types/b-alt/.keep | 0 .../pipelining/Yjava-tasty-result-types/build.sbt | 12 ++++++++++++ sbt-test/pipelining/Yjava-tasty-result-types/test | 2 ++ 15 files changed, 68 insertions(+) create mode 100644 sbt-test/pipelining/Yjava-tasty-annotation/b-alt/.keep create mode 100644 sbt-test/pipelining/Yjava-tasty-enum/b-alt/.keep create mode 100644 sbt-test/pipelining/Yjava-tasty-from-tasty/b-alt/.keep create mode 100644 sbt-test/pipelining/Yjava-tasty-generic/b-alt/.keep create mode 100644 sbt-test/pipelining/Yjava-tasty-result-types/b-alt/.keep diff --git a/sbt-test/pipelining/Yjava-tasty-annotation/b-alt/.keep b/sbt-test/pipelining/Yjava-tasty-annotation/b-alt/.keep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/sbt-test/pipelining/Yjava-tasty-annotation/build.sbt b/sbt-test/pipelining/Yjava-tasty-annotation/build.sbt index 9299d94c060d..18f6b8224968 100644 --- a/sbt-test/pipelining/Yjava-tasty-annotation/build.sbt +++ b/sbt-test/pipelining/Yjava-tasty-annotation/build.sbt @@ -11,3 +11,11 @@ lazy val b = project.in(file("b")) Compile / unmanagedClasspath := Seq(Attributed.blank((ThisBuild / baseDirectory).value / "a-annotation-java-tasty.jar")), scalacOptions += "-Ycheck:all", ) + +// same as b, but adds the real classes to the classpath instead of the tasty jar +lazy val bAlt = project.in(file("b-alt")) + .settings( + Compile / sources := (b / Compile / sources).value, + Compile / unmanagedClasspath := Seq(Attributed.blank((ThisBuild / baseDirectory).value / "a-annotation-classes")), + scalacOptions += "-Ycheck:all", + ) diff --git a/sbt-test/pipelining/Yjava-tasty-annotation/test b/sbt-test/pipelining/Yjava-tasty-annotation/test index 6105296d455b..6f7f57e91ab1 100644 --- a/sbt-test/pipelining/Yjava-tasty-annotation/test +++ b/sbt-test/pipelining/Yjava-tasty-annotation/test @@ -1,3 +1,5 @@ > a/compile # Test depending on a java compiled annotation through TASTy > b/compile +# double check against the real java classes +> bAlt/compile diff --git a/sbt-test/pipelining/Yjava-tasty-enum/b-alt/.keep b/sbt-test/pipelining/Yjava-tasty-enum/b-alt/.keep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/sbt-test/pipelining/Yjava-tasty-enum/build.sbt b/sbt-test/pipelining/Yjava-tasty-enum/build.sbt index 0c95d8318913..aca2391987e9 100644 --- a/sbt-test/pipelining/Yjava-tasty-enum/build.sbt +++ b/sbt-test/pipelining/Yjava-tasty-enum/build.sbt @@ -17,3 +17,15 @@ lazy val b = project.in(file("b")) fork := true, // we have to fork the JVM if we actually want to run the code with correct failure semantics Runtime / unmanagedClasspath += Attributed.blank((ThisBuild / baseDirectory).value / "a-enum-classes"), // make sure the java classes are visible at runtime ) + +// same as b, but adds the real classes to the classpath instead of the tasty jar +lazy val bAlt = project.in(file("b-alt")) + .settings( + Compile / sources := (b / Compile / sources).value, + Compile / unmanagedClasspath := Seq(Attributed.blank((ThisBuild / baseDirectory).value / "a-enum-classes")), + scalacOptions += "-Ycheck:all", + ) + .settings( + fork := true, // we have to fork the JVM if we actually want to run the code with correct failure semantics + Runtime / unmanagedClasspath += Attributed.blank((ThisBuild / baseDirectory).value / "a-enum-classes"), // make sure the java classes are visible at runtime + ) diff --git a/sbt-test/pipelining/Yjava-tasty-enum/test b/sbt-test/pipelining/Yjava-tasty-enum/test index fe04b1a7c7ea..fa53c47aea59 100644 --- a/sbt-test/pipelining/Yjava-tasty-enum/test +++ b/sbt-test/pipelining/Yjava-tasty-enum/test @@ -1,3 +1,5 @@ > a/compile # test depending on a java compiled enum through TASTy > b/run +# double check against the real java classes +> bAlt/run diff --git a/sbt-test/pipelining/Yjava-tasty-from-tasty/b-alt/.keep b/sbt-test/pipelining/Yjava-tasty-from-tasty/b-alt/.keep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/sbt-test/pipelining/Yjava-tasty-from-tasty/build.sbt b/sbt-test/pipelining/Yjava-tasty-from-tasty/build.sbt index 570e72a40c1b..e4b15d3d9c7e 100644 --- a/sbt-test/pipelining/Yjava-tasty-from-tasty/build.sbt +++ b/sbt-test/pipelining/Yjava-tasty-from-tasty/build.sbt @@ -33,3 +33,17 @@ lazy val b = project.in(file("b")) // make sure the java classes are visible at runtime Runtime / unmanagedClasspath += Attributed.blank((ThisBuild / baseDirectory).value / "a-pre-classes"), ) + +// same as b, but adds the real classes to the classpath instead of the tasty jar +lazy val bAlt = project.in(file("b-alt")) + .settings( + scalacOptions += "-Ycheck:all", + Compile / sources := (b / Compile / sources).value, + Compile / unmanagedClasspath := Seq(Attributed.blank((ThisBuild / baseDirectory).value / "a-pre-classes")), + ) + .settings( + // we have to fork the JVM if we actually want to run the code with correct failure semantics + fork := true, + // make sure the java classes are visible at runtime + Runtime / unmanagedClasspath += Attributed.blank((ThisBuild / baseDirectory).value / "a-pre-classes"), + ) diff --git a/sbt-test/pipelining/Yjava-tasty-from-tasty/test b/sbt-test/pipelining/Yjava-tasty-from-tasty/test index 5c08ed4c4458..b4ce2965b995 100644 --- a/sbt-test/pipelining/Yjava-tasty-from-tasty/test +++ b/sbt-test/pipelining/Yjava-tasty-from-tasty/test @@ -3,3 +3,5 @@ > a_from_tasty/compile # test java tasty is still written even with -from-tasty > b/run +# double check against the real java classes +> bAlt/run diff --git a/sbt-test/pipelining/Yjava-tasty-generic/b-alt/.keep b/sbt-test/pipelining/Yjava-tasty-generic/b-alt/.keep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/sbt-test/pipelining/Yjava-tasty-generic/build.sbt b/sbt-test/pipelining/Yjava-tasty-generic/build.sbt index 0c8a5c55fe7e..07e2ea56fbaa 100644 --- a/sbt-test/pipelining/Yjava-tasty-generic/build.sbt +++ b/sbt-test/pipelining/Yjava-tasty-generic/build.sbt @@ -15,3 +15,15 @@ lazy val b = project.in(file("b")) fork := true, // we have to fork the JVM if we actually want to run the code with correct failure semantics Runtime / unmanagedClasspath += Attributed.blank((ThisBuild / baseDirectory).value / "a-generic-classes"), // make sure the java classes are visible at runtime ) + +// same as b, but adds the real classes to the classpath instead of the tasty jar +lazy val bAlt = project.in(file("b-alt")) + .settings( + Compile / sources := (b / Compile / sources).value, + Compile / unmanagedClasspath := Seq(Attributed.blank((ThisBuild / baseDirectory).value / "a-generic-classes")), + scalacOptions += "-Ycheck:all", + ) + .settings( + fork := true, // we have to fork the JVM if we actually want to run the code with correct failure semantics + Runtime / unmanagedClasspath += Attributed.blank((ThisBuild / baseDirectory).value / "a-generic-classes"), // make sure the java classes are visible at runtime + ) diff --git a/sbt-test/pipelining/Yjava-tasty-generic/test b/sbt-test/pipelining/Yjava-tasty-generic/test index cbe3e14572a8..2265d58a8262 100644 --- a/sbt-test/pipelining/Yjava-tasty-generic/test +++ b/sbt-test/pipelining/Yjava-tasty-generic/test @@ -1,3 +1,5 @@ > a/compile # Test depending on a java generic class through TASTy > b/run +# double check against the real java classes +> bAlt/run diff --git a/sbt-test/pipelining/Yjava-tasty-result-types/b-alt/.keep b/sbt-test/pipelining/Yjava-tasty-result-types/b-alt/.keep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/sbt-test/pipelining/Yjava-tasty-result-types/build.sbt b/sbt-test/pipelining/Yjava-tasty-result-types/build.sbt index 8f9e782f8810..512344f0635b 100644 --- a/sbt-test/pipelining/Yjava-tasty-result-types/build.sbt +++ b/sbt-test/pipelining/Yjava-tasty-result-types/build.sbt @@ -15,3 +15,15 @@ lazy val b = project.in(file("b")) fork := true, // we have to fork the JVM if we actually want to run the code with correct failure semantics Runtime / unmanagedClasspath += Attributed.blank((ThisBuild / baseDirectory).value / "a-result-types-classes"), // make sure the java classes are visible at runtime ) + +// same as b, but adds the real classes to the classpath instead of the tasty jar +lazy val bAlt = project.in(file("b-alt")) + .settings( + Compile / sources := (b / Compile / sources).value, + Compile / unmanagedClasspath := Seq(Attributed.blank((ThisBuild / baseDirectory).value / "a-result-types-classes")), + scalacOptions += "-Ycheck:all", + ) + .settings( + fork := true, // we have to fork the JVM if we actually want to run the code with correct failure semantics + Runtime / unmanagedClasspath += Attributed.blank((ThisBuild / baseDirectory).value / "a-result-types-classes"), // make sure the java classes are visible at runtime + ) diff --git a/sbt-test/pipelining/Yjava-tasty-result-types/test b/sbt-test/pipelining/Yjava-tasty-result-types/test index c1cbbb1f2fe5..4a758ea9991d 100644 --- a/sbt-test/pipelining/Yjava-tasty-result-types/test +++ b/sbt-test/pipelining/Yjava-tasty-result-types/test @@ -1,3 +1,5 @@ > a/compile # Test depending on a java static final result, and method result through TASTy > b/run +# double check against the real java classes +> bAlt/run From ec2a0c1d66e483a33c36c936a41202606066d4fb Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Tue, 12 Dec 2023 19:03:47 +0100 Subject: [PATCH 02/11] test all combinations of java.lang.Object from java --- .../a/src/main/scala/a/A.java | 40 ++++++++++++++++ .../a/src/main/scala/a/AImport.java | 27 +++++++++++ .../a/src/main/scala/a/package.scala | 2 + .../Yjava-tasty-fromjavaobject/b-alt/.keep | 0 .../b/src/main/scala/b/B.scala | 42 +++++++++++++++++ .../b/src/main/scala/b/BImport.scala | 23 ++++++++++ .../Yjava-tasty-fromjavaobject/build.sbt | 46 +++++++++++++++++++ .../Yjava-tasty-fromjavaobject/c-alt/.keep | 0 .../c/src/main/scala/c/C.scala | 16 +++++++ .../c/src/main/scala/c/CImport.scala | 14 ++++++ .../project/DottyInjectedPlugin.scala | 12 +++++ .../Yjava-tasty-fromjavaobject/test | 9 ++++ 12 files changed, 231 insertions(+) create mode 100644 sbt-test/pipelining/Yjava-tasty-fromjavaobject/a/src/main/scala/a/A.java create mode 100644 sbt-test/pipelining/Yjava-tasty-fromjavaobject/a/src/main/scala/a/AImport.java create mode 100644 sbt-test/pipelining/Yjava-tasty-fromjavaobject/a/src/main/scala/a/package.scala create mode 100644 sbt-test/pipelining/Yjava-tasty-fromjavaobject/b-alt/.keep create mode 100644 sbt-test/pipelining/Yjava-tasty-fromjavaobject/b/src/main/scala/b/B.scala create mode 100644 sbt-test/pipelining/Yjava-tasty-fromjavaobject/b/src/main/scala/b/BImport.scala create mode 100644 sbt-test/pipelining/Yjava-tasty-fromjavaobject/build.sbt create mode 100644 sbt-test/pipelining/Yjava-tasty-fromjavaobject/c-alt/.keep create mode 100644 sbt-test/pipelining/Yjava-tasty-fromjavaobject/c/src/main/scala/c/C.scala create mode 100644 sbt-test/pipelining/Yjava-tasty-fromjavaobject/c/src/main/scala/c/CImport.scala create mode 100644 sbt-test/pipelining/Yjava-tasty-fromjavaobject/project/DottyInjectedPlugin.scala create mode 100644 sbt-test/pipelining/Yjava-tasty-fromjavaobject/test diff --git a/sbt-test/pipelining/Yjava-tasty-fromjavaobject/a/src/main/scala/a/A.java b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/a/src/main/scala/a/A.java new file mode 100644 index 000000000000..5c034175c866 --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/a/src/main/scala/a/A.java @@ -0,0 +1,40 @@ +// this test ensures that Object can accept Any from Scala +// see Definitions.FromJavaObjectSymbol +package a; + +public class A { + + public static class Inner extends Object { + public Inner() {} + + public void meth1(T arg) {} + public void meth2(U arg) {} + } + + public static class Inner_sel extends java.lang.Object { + public Inner_sel() {} + + public void meth1(T arg) {} + public void meth2(U arg) {} + } + + // 1. At the top level: + public void meth1(Object arg) {} + public void meth1_sel(java.lang.Object arg) {} + public void meth2(T arg) {} // T implicitly extends Object + + // 2. In a class type parameter: + public void meth3(scala.collection.immutable.List arg) {} + public void meth3_sel(scala.collection.immutable.List arg) {} + public void meth4(scala.collection.immutable.List arg) {} + + // 3. As the type parameter of an array: + public void meth5(Object[] arg) {} + public void meth5_sel(java.lang.Object[] arg) {} + public void meth6(T[] arg) {} + + // 4. As the repeated argument of a varargs method: + public void meth7(Object... args) {} + public void meth7_sel(java.lang.Object... args) {} + public void meth8(T... args) {} +} diff --git a/sbt-test/pipelining/Yjava-tasty-fromjavaobject/a/src/main/scala/a/AImport.java b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/a/src/main/scala/a/AImport.java new file mode 100644 index 000000000000..44c64f39da05 --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/a/src/main/scala/a/AImport.java @@ -0,0 +1,27 @@ +// this test ensures that Object can accept Any from Scala +// see Definitions.FromJavaObjectSymbol +package a; + +import java.lang.Object; + +// same signatures that reference Object explicitly from A.java, but with the java.lang.Object import +public class AImport { + + public static class Inner extends Object { + public Inner() {} + + public void meth1(T arg) {} + } + + // 1. At the top level: + public void meth1(Object arg) {} + + // 2. In a class type parameter: + public void meth3(scala.collection.immutable.List arg) {} + + // 3. As the type parameter of an array: + public void meth5(Object[] arg) {} + + // 4. As the repeated argument of a varargs method: + public void meth7(Object... args) {} +} diff --git a/sbt-test/pipelining/Yjava-tasty-fromjavaobject/a/src/main/scala/a/package.scala b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/a/src/main/scala/a/package.scala new file mode 100644 index 000000000000..93f99e9892fe --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/a/src/main/scala/a/package.scala @@ -0,0 +1,2 @@ +// THIS FILE EXISTS SO THAT `A.java` WILL BE COMPILED BY SCALAC +package a diff --git a/sbt-test/pipelining/Yjava-tasty-fromjavaobject/b-alt/.keep b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/b-alt/.keep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/sbt-test/pipelining/Yjava-tasty-fromjavaobject/b/src/main/scala/b/B.scala b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/b/src/main/scala/b/B.scala new file mode 100644 index 000000000000..baeee9495b4c --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/b/src/main/scala/b/B.scala @@ -0,0 +1,42 @@ +package b + +import a.A + +// keep in sync with Bexplicit.scala +object B { + + val newA = new A + + val newAInner = new A.Inner[Int]() + val newAInner_sel = new A.Inner_sel[Int]() + + @main + def test = { + newA.meth1(1) // OK + newA.meth1_sel(1) // OK + newA.meth2(1) // OK + newA.meth3(List[Int](1)) // OK + newA.meth3_sel(List[Int](1)) // OK + newA.meth4(List[Int](1)) // OK + newA.meth5(Array[Object]("abc")) // OK + newA.meth5_sel(Array[Object]("abc")) // OK + newA.meth6(Array[String]("abc")) // Ok + // newA.meth5(Array[Int](1)) // error: Array[Int] is not a subtype of Array[Object] + // newA.meth6(Array[Int](1)) // error: Array[Int] is not a subtype of Array[T & Object] + newA.meth7(1) // OK (creates a reference array) + newA.meth7_sel(1) // OK (creates a reference array) + newA.meth8(1) // OK (creates a primitive array and copies it into a reference array at Erasure) + val ai = Array[Int](1) + newA.meth7(ai: _*) // OK (will copy the array at Erasure) + newA.meth7_sel(ai: _*) // OK (will copy the array at Erasure) + newA.meth8(ai: _*) // OK (will copy the array at Erasure) + + newAInner.meth1(1) // OK + newAInner.meth2(1) // OK + newAInner_sel.meth1(1) // OK + newAInner_sel.meth2(1) // OK + + BImport.testImport() // OK + } +} + diff --git a/sbt-test/pipelining/Yjava-tasty-fromjavaobject/b/src/main/scala/b/BImport.scala b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/b/src/main/scala/b/BImport.scala new file mode 100644 index 000000000000..06125d0d750a --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/b/src/main/scala/b/BImport.scala @@ -0,0 +1,23 @@ +package b + +import a.AImport + +object BImport { + + val newA = new AImport + + val newAInner = new AImport.Inner[Int]() + + def testImport() = { + newA.meth1(1) // OK + newA.meth3(List[Int](1)) // OK + newA.meth5(Array[Object]("abc")) // OK + // newA.meth5(Array[Int](1)) // error: Array[Int] is not a subtype of Array[Object] + newA.meth7(1) // OK (creates a reference array) + val ai = Array[Int](1) + newA.meth7(ai: _*) // OK (will copy the array at Erasure) + + newAInner.meth1(1) // OK + } +} + diff --git a/sbt-test/pipelining/Yjava-tasty-fromjavaobject/build.sbt b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/build.sbt new file mode 100644 index 000000000000..baeb25bad7f6 --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/build.sbt @@ -0,0 +1,46 @@ +lazy val a = project.in(file("a")) + .settings( + compileOrder := CompileOrder.Mixed, // ensure we send java sources to Scala compiler + scalacOptions += "-Yjava-tasty", // enable pickling of java signatures + scalacOptions ++= Seq("-Yjava-tasty-output", ((ThisBuild / baseDirectory).value / "a-enum-java-tasty.jar").toString), + scalacOptions += "-Ycheck:all", + Compile / classDirectory := ((ThisBuild / baseDirectory).value / "a-enum-classes"), // send classfiles to a different directory + ) + + +lazy val b = project.in(file("b")) + .settings( + Compile / unmanagedClasspath := Seq(Attributed.blank((ThisBuild / baseDirectory).value / "a-enum-java-tasty.jar")), + scalacOptions += "-Ycheck:all", + ) + .settings( + fork := true, // we have to fork the JVM if we actually want to run the code with correct failure semantics + Runtime / unmanagedClasspath += Attributed.blank((ThisBuild / baseDirectory).value / "a-enum-classes"), // make sure the java classes are visible at runtime + ) + +// same as b, but adds the real classes to the classpath instead of the tasty jar +lazy val bAlt = project.in(file("b-alt")) + .settings( + Compile / sources := (b / Compile / sources).value, + Compile / unmanagedClasspath := Seq(Attributed.blank((ThisBuild / baseDirectory).value / "a-enum-classes")), + scalacOptions += "-Ycheck:all", + ) + .settings( + fork := true, // we have to fork the JVM if we actually want to run the code with correct failure semantics + Runtime / unmanagedClasspath += Attributed.blank((ThisBuild / baseDirectory).value / "a-enum-classes"), // make sure the java classes are visible at runtime + ) + +// negative compilation tests +lazy val c = project.in(file("c")) + .settings( + Compile / unmanagedClasspath := Seq(Attributed.blank((ThisBuild / baseDirectory).value / "a-enum-java-tasty.jar")), + scalacOptions += "-Ycheck:all", + ) + +// same as c, but adds the real classes to the classpath instead of the tasty jar +lazy val cAlt = project.in(file("c-alt")) + .settings( + Compile / sources := (c / Compile / sources).value, + Compile / unmanagedClasspath := Seq(Attributed.blank((ThisBuild / baseDirectory).value / "a-enum-classes")), + scalacOptions += "-Ycheck:all", + ) diff --git a/sbt-test/pipelining/Yjava-tasty-fromjavaobject/c-alt/.keep b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/c-alt/.keep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/sbt-test/pipelining/Yjava-tasty-fromjavaobject/c/src/main/scala/c/C.scala b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/c/src/main/scala/c/C.scala new file mode 100644 index 000000000000..05dff30bd63e --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/c/src/main/scala/c/C.scala @@ -0,0 +1,16 @@ +package c + +import a.A + +object C { + + val newA = new A + + @main + def test = { + newA.meth5(Array[Int](1)) // error: Array[Int] is not a subtype of Array[Object] + newA.meth5_sel(Array[Int](1)) // error: Array[Int] is not a subtype of Array[Object] + newA.meth6(Array[Int](1)) // error: Array[Int] is not a subtype of Array[T & Object] + } +} + diff --git a/sbt-test/pipelining/Yjava-tasty-fromjavaobject/c/src/main/scala/c/CImport.scala b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/c/src/main/scala/c/CImport.scala new file mode 100644 index 000000000000..dc27126060b8 --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/c/src/main/scala/c/CImport.scala @@ -0,0 +1,14 @@ +package c + +import a.AImport + +object CImport { + + val newA = new AImport + + @main + def test = { + newA.meth5(Array[Int](1)) // error: Array[Int] is not a subtype of Array[Object] + } +} + diff --git a/sbt-test/pipelining/Yjava-tasty-fromjavaobject/project/DottyInjectedPlugin.scala b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/project/DottyInjectedPlugin.scala new file mode 100644 index 000000000000..69f15d168bfc --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/project/DottyInjectedPlugin.scala @@ -0,0 +1,12 @@ +import sbt._ +import Keys._ + +object DottyInjectedPlugin extends AutoPlugin { + override def requires = plugins.JvmPlugin + override def trigger = allRequirements + + override val projectSettings = Seq( + scalaVersion := sys.props("plugin.scalaVersion"), + scalacOptions += "-source:3.0-migration" + ) +} diff --git a/sbt-test/pipelining/Yjava-tasty-fromjavaobject/test b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/test new file mode 100644 index 000000000000..caf2a6e3c1e7 --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/test @@ -0,0 +1,9 @@ +> a/compile +# test depending on a java compiled enum through TASTy +> b/run +# double check against the real java classes +> bAlt/run +# check that java Array T is Array T & Object +-> c/compile +# double check against the real java classes +-> cAlt/compile From 28cb6a778476852fef62714ec63ad201300e809d Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Wed, 13 Dec 2023 11:37:08 +0100 Subject: [PATCH 03/11] escape FromJavaObject in TASTy, recover at unpickle --- .../tools/dotc/core/tasty/TreePickler.scala | 17 ++++++++++------- .../tools/dotc/core/tasty/TreeUnpickler.scala | 13 ++++++++----- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 01c5a44ac736..68b4fa770bf4 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -13,7 +13,7 @@ import ast.{untpd, tpd} import Contexts.*, Symbols.*, Types.*, Names.*, Constants.*, Decorators.*, Annotations.*, Flags.* import Comments.{Comment, docCtx} import NameKinds.* -import StdNames.nme +import StdNames.{nme, tpnme} import config.Config import collection.mutable import reporting.{Profile, NoProfile} @@ -49,6 +49,9 @@ class TreePickler(pickler: TastyPickler, attributes: Attributes) { private var profile: Profile = NoProfile + private val isOutlinePickle: Boolean = attributes.isOutline + private val isJavaPickle: Boolean = attributes.isJava + def treeAnnots(tree: untpd.MemberDef): List[Tree] = val ts = annotTrees.lookup(tree) if ts == null then Nil else ts.toList @@ -188,19 +191,19 @@ class TreePickler(pickler: TastyPickler, attributes: Attributes) { def pickleExternalRef(sym: Symbol) = { val isShadowedRef = sym.isClass && tpe.prefix.member(sym.name).symbol != sym - if (sym.is(Flags.Private) || isShadowedRef) { + if sym.is(Flags.Private) || isShadowedRef then writeByte(if (tpe.isType) TYPEREFin else TERMREFin) withLength { pickleNameAndSig(sym.name, sym.signature, sym.targetName) pickleType(tpe.prefix) pickleType(sym.owner.typeRef) } - } - else { + else if isJavaPickle && sym == defn.FromJavaObjectSymbol then + pickleType(defn.ObjectType) // when unpickling Java TASTy, replace by + else writeByte(if (tpe.isType) TYPEREF else TERMREF) pickleNameAndSig(sym.name, tpe.signature, sym.targetName) - pickleType(tpe.prefix) - } + pickleType(tpe.prefix) } if (sym.is(Flags.Package)) { writeByte(if (tpe.isType) TYPEREFpkg else TERMREFpkg) @@ -342,7 +345,7 @@ class TreePickler(pickler: TastyPickler, attributes: Attributes) { case _: Template | _: Hole => pickleTree(tpt) case _ if tpt.isType => pickleTpt(tpt) } - if attributes.isOutline && sym.isTerm && attributes.isJava then + if isOutlinePickle && sym.isTerm && isJavaPickle then // TODO: if we introduce outline typing for Scala definitions // then we will need to update the check here pickleElidedUnlessEmpty(rhs, tpt.tpe) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index fcd0a62f3a60..3fb6fd30fce0 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -441,7 +441,11 @@ class TreeUnpickler(reader: TastyReader, readPackageRef().termRef case TYPEREF => val name = readName().toTypeName - TypeRef(readType(), name) + val pre = readType() + if unpicklingJava && name == tpnme.Object && (pre.termSymbol eq defn.JavaLangPackageVal) then + defn.FromJavaObjectType + else + TypeRef(pre, name) case TERMREF => val sname = readName() val prefix = readType() @@ -1202,12 +1206,11 @@ class TreeUnpickler(reader: TastyReader, def completeSelect(name: Name, sig: Signature, target: Name): Select = val qual = readTree() - val denot0 = accessibleDenot(qual.tpe.widenIfUnstable, name, sig, target) val denot = - if unpicklingJava && name == tpnme.Object && denot0.symbol == defn.ObjectClass then - defn.FromJavaObjectType.denot + if unpicklingJava && name == tpnme.Object && qual.symbol == defn.JavaLangPackageVal then + defn.FromJavaObjectSymbol.denot else - denot0 + accessibleDenot(qual.tpe.widenIfUnstable, name, sig, target) makeSelect(qual, name, denot) def readQualId(): (untpd.Ident, TypeRef) = From 60dfd961ec00936645325b7ac5c6ab39f2c2ba86 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Wed, 13 Dec 2023 11:51:54 +0100 Subject: [PATCH 04/11] improve debug message for illegal elided tree --- compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 3fb6fd30fce0..756924580333 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -1231,8 +1231,9 @@ class TreeUnpickler(reader: TastyReader, untpd.Ident(readName()).withType(readType()) case ELIDED => if !isOutline then - report.error( - s"Illegal elided tree in unpickler without ${attributeTagToString(OUTLINEattr)}, ${ctx.source}") + val msg = + s"Illegal elided tree in unpickler at $start without ${attributeTagToString(OUTLINEattr)}, ${ctx.source}" + report.error(msg) untpd.Ident(nme.WILDCARD).withType(readType()) case IDENTtpt => untpd.Ident(readName().toTypeName).withType(readType()) From 25f855851f8f6aa108c7b87053e3cec0167ced11 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Wed, 13 Dec 2023 17:43:41 +0100 Subject: [PATCH 05/11] Check for FromJavaObject in -Ytest-pickler --- .../tools/dotc/printing/OutlinePrinter.scala | 39 +++++++++++++++++++ .../tools/dotc/printing/RefinedPrinter.scala | 15 ++++++- .../dotty/tools/dotc/transform/Pickler.scala | 20 ++++++++-- .../Yjava-tasty-fromjavaobject/a-check/.keep | 0 .../Yjava-tasty-fromjavaobject/build.sbt | 11 ++++++ .../Yjava-tasty-fromjavaobject/test | 3 ++ 6 files changed, 83 insertions(+), 5 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/printing/OutlinePrinter.scala create mode 100644 sbt-test/pipelining/Yjava-tasty-fromjavaobject/a-check/.keep diff --git a/compiler/src/dotty/tools/dotc/printing/OutlinePrinter.scala b/compiler/src/dotty/tools/dotc/printing/OutlinePrinter.scala new file mode 100644 index 000000000000..a1a1e8d82aa5 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/printing/OutlinePrinter.scala @@ -0,0 +1,39 @@ +package dotty.tools +package dotc +package printing + +import core.* +import Texts.* +import Flags.* +import NameOps.* +import StdNames.* +import Contexts.* +import Symbols.* +import ast.{Trees, untpd} +import Trees.* + +object OutlinePrinter: + def apply(_ctx: Context): Printer = new OutlinePrinter(_ctx) + +/** A printer that elides known standard tree forms from the rhs of def and val. + * Typically used for printing Java trees which elide the rhs. + */ +class OutlinePrinter private (_ctx: Context) extends RefinedPrinter(_ctx) { + + /* Typical patterns seen in output of typer for Java code, plus the output of unpickling an ELIDED tree */ + def isElidableExpr[T <: Untyped](tree: Tree[T]): Boolean = tree match { + case tree: Ident[T] if tree.name == nme.WILDCARD => true + case tree: Select[T] if tree.symbol == defn.Predef_undefined => true + case Apply(Select(tree: New[T], nme.CONSTRUCTOR), Nil) if tree.tpt.typeOpt.typeSymbol.is(Module) => true + case _ => false + } + + def elideExpr[T <: Untyped](tree: Tree[T], original: => Text): Text = + if isElidableExpr(tree) then Str("_") else original + + override protected def rhsValDef[T <: Untyped](rhs: Tree[T], original: => Text): Text = + elideExpr(rhs, original) + + override protected def rhsDefDef[T <: Untyped](rhs: Tree[T], original: => Text): Text = + elideExpr(rhs, original) +} diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 8ad1188a3e7e..bded9adcaadc 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -29,6 +29,8 @@ import dotty.tools.dotc.util.SourcePosition import dotty.tools.dotc.ast.untpd.{MemberDef, Modifiers, PackageDef, RefTree, Template, TypeDef, ValOrDefDef} import cc.{CaptureSet, CapturingType, toCaptureSet, IllegalCaptureRef} +import scala.annotation.unused + class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { /** A stack of enclosing DefDef, TypeDef, or ClassDef, or ModuleDefs nodes */ @@ -920,7 +922,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { dclTextOr(tree) { modText(tree.mods, tree.symbol, keywordStr(if (tree.mods.is(Mutable)) "var" else "val"), isType = false) ~~ valDefText(nameIdText(tree)) ~ optAscription(tree.tpt) ~ - withEnclosingDef(tree) { optText(tree.rhs)(" = " ~ _) } + withEnclosingDef(tree) { optText(tree.rhs)(rhs => " = " ~ rhsValDef(tree.rhs, rhs)) } } } @@ -977,11 +979,20 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { coreSig ~ optAscription(tree.tpt) - ~ optText(tree.rhs)(" = " ~ keywordText("macro ").provided(tree.symbol.isScala2Macro) ~ _) + ~ optText(tree.rhs)(rhs => + " = " ~ rhsDefDef(tree.rhs, keywordText("macro ").provided(tree.symbol.isScala2Macro) ~ rhs)) } } } + /** Inspect the rhs of a ValDef, overridden in OutlinePrinter */ + protected def rhsValDef[T <: Untyped](@unused("override may inspect rhs") rhs: Tree[T], original: => Text): Text = + original + + /** Inspect the rhs of a DefDef, overridden in OutlinePrinter */ + protected def rhsDefDef[T <: Untyped](@unused("override may inspect rhs") rhs: Tree[T], original: => Text): Text = + original + protected def toTextTemplate(impl: Template, ofNew: Boolean = false): Text = { val Template(constr @ DefDef(_, paramss, _, _), _, self, _) = impl val tparamsTxt = withEnclosingDef(constr) { diff --git a/compiler/src/dotty/tools/dotc/transform/Pickler.scala b/compiler/src/dotty/tools/dotc/transform/Pickler.scala index 1eaf0a58fda2..29789160df86 100644 --- a/compiler/src/dotty/tools/dotc/transform/Pickler.scala +++ b/compiler/src/dotty/tools/dotc/transform/Pickler.scala @@ -10,7 +10,7 @@ import config.Printers.{noPrinter, pickling} import config.Feature import java.io.PrintStream import io.ClassfileWriterOps -import StdNames.str +import StdNames.{str, nme} import Periods.* import Phases.* import Symbols.* @@ -20,6 +20,7 @@ import collection.mutable import util.concurrent.{Executor, Future} import compiletime.uninitialized import dotty.tools.io.JarArchive +import dotty.tools.dotc.printing.OutlinePrinter object Pickler { val name: String = "pickler" @@ -86,6 +87,15 @@ class Pickler extends Phase { Pickler.ParallelPickling && !ctx.settings.YtestPickler.value && !ctx.settings.YjavaTasty.value // disable parallel pickling when `-Yjava-tasty` is set (internal testing only) + private def adjustPrinter(ictx: Context): Context = + if ictx.compilationUnit.typedAsJava then + // use special printer because Java parser will use `Predef.???` as rhs, + // which conflicts with the unpickling of ELIDED as `Ident(nme.WILDCARD).withType(tpe)` + // In the future we could modify the typer/parser to elide the rhs in the same way. + ictx.fresh.setPrinterFn(OutlinePrinter(_)) + else + ictx + override def run(using Context): Unit = { val unit = ctx.compilationUnit pickling.println(i"unpickling in run ${ctx.runId}") @@ -94,7 +104,8 @@ class Pickler extends Phase { cls <- dropCompanionModuleClasses(topLevelClasses(unit.tpdTree)) tree <- sliceTopLevel(unit.tpdTree, cls) do - if ctx.settings.YtestPickler.value then beforePickling(cls) = tree.show + if ctx.settings.YtestPickler.value then + beforePickling(cls) = tree.show(using adjustPrinter(ctx)) val sourceRelativePath = val reference = ctx.settings.sourceroot.value @@ -242,11 +253,14 @@ class Pickler extends Phase { pickling.println("************* entered toplevel ***********") val rootCtx = ctx for ((cls, (unit, unpickler)) <- unpicklers) do + if unit.typedAsJava then + if unpickler.unpickler.nameAtRef.contents.exists(_ == nme.FromJavaObject) then + report.error(em"Pickled reference to FromJavaObject in Java defined $cls in ${cls.source}") val unpickled = unpickler.rootTrees val freshUnit = CompilationUnit(rootCtx.compilationUnit.source) freshUnit.needsCaptureChecking = unit.needsCaptureChecking freshUnit.knowsPureFuns = unit.knowsPureFuns - inContext(rootCtx.fresh.setCompilationUnit(freshUnit)): + inContext(adjustPrinter(rootCtx.fresh.setCompilationUnit(freshUnit))): testSame(i"$unpickled%\n%", beforePickling(cls), cls) private def testSame(unpickled: String, previous: String, cls: ClassSymbol)(using Context) = diff --git a/sbt-test/pipelining/Yjava-tasty-fromjavaobject/a-check/.keep b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/a-check/.keep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/sbt-test/pipelining/Yjava-tasty-fromjavaobject/build.sbt b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/build.sbt index baeb25bad7f6..6738db3016fa 100644 --- a/sbt-test/pipelining/Yjava-tasty-fromjavaobject/build.sbt +++ b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/build.sbt @@ -7,6 +7,17 @@ lazy val a = project.in(file("a")) Compile / classDirectory := ((ThisBuild / baseDirectory).value / "a-enum-classes"), // send classfiles to a different directory ) +// compiles the same sources as a, but with -Ytest-pickler +lazy val aCheck = project.in(file("a-check")) + .settings( + scalacOptions += "-Ytest-pickler", // check that the pickler is correct + Compile / sources := (a / Compile / sources).value, // use the same sources as a + compileOrder := CompileOrder.Mixed, // ensure we send java sources to Scala compiler + scalacOptions += "-Yjava-tasty", // enable pickling of java signatures + scalacOptions ++= Seq("-Yjava-tasty-output", ((ThisBuild / baseDirectory).value / "a-enum-java-tasty-2.jar").toString), + Compile / classDirectory := ((ThisBuild / baseDirectory).value / "a-enum-classes-2"), // send classfiles to a different directory + ) + lazy val b = project.in(file("b")) .settings( diff --git a/sbt-test/pipelining/Yjava-tasty-fromjavaobject/test b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/test index caf2a6e3c1e7..7a34a0cb5ec1 100644 --- a/sbt-test/pipelining/Yjava-tasty-fromjavaobject/test +++ b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/test @@ -1,4 +1,7 @@ +# compile Java sources, and send them to TASTy > a/compile +# compile Java sources, and check that they are pickled correctly +> aCheck/compile # test depending on a java compiled enum through TASTy > b/run # double check against the real java classes From f0d0ac8b8534a658a043c4dee6e64199ae9fabc9 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Wed, 13 Dec 2023 21:36:14 +0100 Subject: [PATCH 06/11] also test getter/setter and constructor params --- .../tools/dotc/printing/OutlinePrinter.scala | 8 +++++--- .../a/src/main/scala/a/A.java | 20 +++++++++++++++++-- .../a/src/main/scala/a/AImport.java | 10 +++++++++- .../b/src/main/scala/b/B.scala | 18 +++++++++++++++-- .../b/src/main/scala/b/BImport.scala | 10 +++++++++- 5 files changed, 57 insertions(+), 9 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/printing/OutlinePrinter.scala b/compiler/src/dotty/tools/dotc/printing/OutlinePrinter.scala index a1a1e8d82aa5..acc30e1e9463 100644 --- a/compiler/src/dotty/tools/dotc/printing/OutlinePrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/OutlinePrinter.scala @@ -22,9 +22,11 @@ class OutlinePrinter private (_ctx: Context) extends RefinedPrinter(_ctx) { /* Typical patterns seen in output of typer for Java code, plus the output of unpickling an ELIDED tree */ def isElidableExpr[T <: Untyped](tree: Tree[T]): Boolean = tree match { - case tree: Ident[T] if tree.name == nme.WILDCARD => true - case tree: Select[T] if tree.symbol == defn.Predef_undefined => true - case Apply(Select(tree: New[T], nme.CONSTRUCTOR), Nil) if tree.tpt.typeOpt.typeSymbol.is(Module) => true + case tree: Ident[T] if tree.name == nme.WILDCARD => true // `ELIDED exprType` + case tree: Literal[T] => true // e.g. `()` + case tree: Select[T] if tree.symbol == defn.Predef_undefined => true // e.g. `Predef.???` + case Apply(Select(tree: New[T], nme.CONSTRUCTOR), Nil) + if tree.tpt.typeOpt.typeSymbol.is(Module) => true // e.g. `new foo.Foo$()` (rhs of a module val) case _ => false } diff --git a/sbt-test/pipelining/Yjava-tasty-fromjavaobject/a/src/main/scala/a/A.java b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/a/src/main/scala/a/A.java index 5c034175c866..b798a9dedce9 100644 --- a/sbt-test/pipelining/Yjava-tasty-fromjavaobject/a/src/main/scala/a/A.java +++ b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/a/src/main/scala/a/A.java @@ -5,14 +5,30 @@ public class A { public static class Inner extends Object { - public Inner() {} + public T field1; + public T getter1() { return field1; } + public Object field2; + public Object getter2() { return field2; } + + public Inner(T param1, Object param2) { + this.field1 = param1; + this.field2 = param2; + } public void meth1(T arg) {} public void meth2(U arg) {} } public static class Inner_sel extends java.lang.Object { - public Inner_sel() {} + public T field1; + public T getter1() { return field1; } + public java.lang.Object field2; + public java.lang.Object getter2() { return field2; } + + public Inner_sel(T param1, java.lang.Object param2) { + this.field1 = param1; + this.field2 = param2; + } public void meth1(T arg) {} public void meth2(U arg) {} diff --git a/sbt-test/pipelining/Yjava-tasty-fromjavaobject/a/src/main/scala/a/AImport.java b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/a/src/main/scala/a/AImport.java index 44c64f39da05..6dd2608883d8 100644 --- a/sbt-test/pipelining/Yjava-tasty-fromjavaobject/a/src/main/scala/a/AImport.java +++ b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/a/src/main/scala/a/AImport.java @@ -8,7 +8,15 @@ public class AImport { public static class Inner extends Object { - public Inner() {} + public T field1; + public T getter1() { return field1; } + public Object field2; + public Object getter2() { return field2; } + + public Inner(T param1, Object param2) { + this.field1 = param1; + this.field2 = param2; + } public void meth1(T arg) {} } diff --git a/sbt-test/pipelining/Yjava-tasty-fromjavaobject/b/src/main/scala/b/B.scala b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/b/src/main/scala/b/B.scala index baeee9495b4c..b2a1c300bfd0 100644 --- a/sbt-test/pipelining/Yjava-tasty-fromjavaobject/b/src/main/scala/b/B.scala +++ b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/b/src/main/scala/b/B.scala @@ -7,8 +7,8 @@ object B { val newA = new A - val newAInner = new A.Inner[Int]() - val newAInner_sel = new A.Inner_sel[Int]() + val newAInner = new A.Inner[Int](23, true) + val newAInner_sel = new A.Inner_sel[Int](23, true) @main def test = { @@ -36,6 +36,20 @@ object B { newAInner_sel.meth1(1) // OK newAInner_sel.meth2(1) // OK + assert((newAInner.field1: Int) == 23) // OK + newAInner.field1 = 31 // OK + assert((newAInner.getter1: Int) == 31) // OK + assert(newAInner.field2 == true) // OK + newAInner.field2 = false // OK + assert(newAInner.getter2 == false) // OK + + assert((newAInner_sel.field1: Int) == 23) // OK + newAInner_sel.field1 = 31 // OK + assert((newAInner_sel.getter1: Int) == 31) // OK + assert(newAInner_sel.field2 == true) // OK + newAInner_sel.field2 = false // OK + assert(newAInner_sel.getter2 == false) // OK + BImport.testImport() // OK } } diff --git a/sbt-test/pipelining/Yjava-tasty-fromjavaobject/b/src/main/scala/b/BImport.scala b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/b/src/main/scala/b/BImport.scala index 06125d0d750a..17d3fbca1591 100644 --- a/sbt-test/pipelining/Yjava-tasty-fromjavaobject/b/src/main/scala/b/BImport.scala +++ b/sbt-test/pipelining/Yjava-tasty-fromjavaobject/b/src/main/scala/b/BImport.scala @@ -6,7 +6,7 @@ object BImport { val newA = new AImport - val newAInner = new AImport.Inner[Int]() + val newAInner = new AImport.Inner[Int](23, true) def testImport() = { newA.meth1(1) // OK @@ -18,6 +18,14 @@ object BImport { newA.meth7(ai: _*) // OK (will copy the array at Erasure) newAInner.meth1(1) // OK + + assert((newAInner.field1: Int) == 23) // OK + newAInner.field1 = 31 // OK + assert((newAInner.getter1: Int) == 31) // OK + + assert(newAInner.field2 == true) // OK + newAInner.field2 = false // OK + assert(newAInner.getter2 == false) // OK } } From fb2e2d537fb4c4e84c9f70824cbde784efb806b9 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Thu, 14 Dec 2023 11:52:05 +0100 Subject: [PATCH 07/11] refactor printers --- .../tools/dotc/printing/OutlinePrinter.scala | 18 ++++++++++-------- .../tools/dotc/printing/RefinedPrinter.scala | 15 ++++++--------- .../dotty/tools/dotc/transform/Pickler.scala | 8 ++++---- 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/printing/OutlinePrinter.scala b/compiler/src/dotty/tools/dotc/printing/OutlinePrinter.scala index acc30e1e9463..178335ee4b51 100644 --- a/compiler/src/dotty/tools/dotc/printing/OutlinePrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/OutlinePrinter.scala @@ -22,20 +22,22 @@ class OutlinePrinter private (_ctx: Context) extends RefinedPrinter(_ctx) { /* Typical patterns seen in output of typer for Java code, plus the output of unpickling an ELIDED tree */ def isElidableExpr[T <: Untyped](tree: Tree[T]): Boolean = tree match { + case tree if tree.isEmpty => false case tree: Ident[T] if tree.name == nme.WILDCARD => true // `ELIDED exprType` case tree: Literal[T] => true // e.g. `()` case tree: Select[T] if tree.symbol == defn.Predef_undefined => true // e.g. `Predef.???` case Apply(Select(tree: New[T], nme.CONSTRUCTOR), Nil) if tree.tpt.typeOpt.typeSymbol.is(Module) => true // e.g. `new foo.Foo$()` (rhs of a module val) - case _ => false + case _ => + sys.error(s"Unexpected tree in OutlinePrinter: ${tree.show}, $tree") + false } - def elideExpr[T <: Untyped](tree: Tree[T], original: => Text): Text = - if isElidableExpr(tree) then Str("_") else original + override protected def rhsValDef[T <: Untyped](tree: ValDef[T]): Text = + if isElidableExpr(tree.rhs) then " = " ~ "elided" ~ "[" ~ toText(tree.tpt) ~ "]" + else super.rhsValDef(tree) - override protected def rhsValDef[T <: Untyped](rhs: Tree[T], original: => Text): Text = - elideExpr(rhs, original) - - override protected def rhsDefDef[T <: Untyped](rhs: Tree[T], original: => Text): Text = - elideExpr(rhs, original) + override protected def rhsDefDef[T <: Untyped](tree: DefDef[T]): Text = + if isElidableExpr(tree.rhs) then " = " ~ "elided" ~ "[" ~ toText(tree.tpt) ~ "]" + else super.rhsDefDef(tree) } diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index bded9adcaadc..b73f29b4af5e 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -29,8 +29,6 @@ import dotty.tools.dotc.util.SourcePosition import dotty.tools.dotc.ast.untpd.{MemberDef, Modifiers, PackageDef, RefTree, Template, TypeDef, ValOrDefDef} import cc.{CaptureSet, CapturingType, toCaptureSet, IllegalCaptureRef} -import scala.annotation.unused - class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { /** A stack of enclosing DefDef, TypeDef, or ClassDef, or ModuleDefs nodes */ @@ -922,7 +920,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { dclTextOr(tree) { modText(tree.mods, tree.symbol, keywordStr(if (tree.mods.is(Mutable)) "var" else "val"), isType = false) ~~ valDefText(nameIdText(tree)) ~ optAscription(tree.tpt) ~ - withEnclosingDef(tree) { optText(tree.rhs)(rhs => " = " ~ rhsValDef(tree.rhs, rhs)) } + withEnclosingDef(tree) { rhsValDef(tree) } } } @@ -979,19 +977,18 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { coreSig ~ optAscription(tree.tpt) - ~ optText(tree.rhs)(rhs => - " = " ~ rhsDefDef(tree.rhs, keywordText("macro ").provided(tree.symbol.isScala2Macro) ~ rhs)) + ~ rhsDefDef(tree) } } } /** Inspect the rhs of a ValDef, overridden in OutlinePrinter */ - protected def rhsValDef[T <: Untyped](@unused("override may inspect rhs") rhs: Tree[T], original: => Text): Text = - original + protected def rhsValDef[T <: Untyped](tree: ValDef[T]): Text = + optText(tree.rhs)(" = " ~ _) /** Inspect the rhs of a DefDef, overridden in OutlinePrinter */ - protected def rhsDefDef[T <: Untyped](@unused("override may inspect rhs") rhs: Tree[T], original: => Text): Text = - original + protected def rhsDefDef[T <: Untyped](tree: DefDef[T]): Text = + optText(tree.rhs)(" = " ~ keywordText("macro ").provided(tree.symbol.isScala2Macro) ~ _) protected def toTextTemplate(impl: Template, ofNew: Boolean = false): Text = { val Template(constr @ DefDef(_, paramss, _, _), _, self, _) = impl diff --git a/compiler/src/dotty/tools/dotc/transform/Pickler.scala b/compiler/src/dotty/tools/dotc/transform/Pickler.scala index 29789160df86..d1248230d356 100644 --- a/compiler/src/dotty/tools/dotc/transform/Pickler.scala +++ b/compiler/src/dotty/tools/dotc/transform/Pickler.scala @@ -87,8 +87,8 @@ class Pickler extends Phase { Pickler.ParallelPickling && !ctx.settings.YtestPickler.value && !ctx.settings.YjavaTasty.value // disable parallel pickling when `-Yjava-tasty` is set (internal testing only) - private def adjustPrinter(ictx: Context): Context = - if ictx.compilationUnit.typedAsJava then + private def adjustPrinter(ictx: Context, isOutline: Boolean): Context = + if isOutline then // use special printer because Java parser will use `Predef.???` as rhs, // which conflicts with the unpickling of ELIDED as `Ident(nme.WILDCARD).withType(tpe)` // In the future we could modify the typer/parser to elide the rhs in the same way. @@ -105,7 +105,7 @@ class Pickler extends Phase { tree <- sliceTopLevel(unit.tpdTree, cls) do if ctx.settings.YtestPickler.value then - beforePickling(cls) = tree.show(using adjustPrinter(ctx)) + beforePickling(cls) = tree.show(using adjustPrinter(ctx, unit.typedAsJava)) val sourceRelativePath = val reference = ctx.settings.sourceroot.value @@ -260,7 +260,7 @@ class Pickler extends Phase { val freshUnit = CompilationUnit(rootCtx.compilationUnit.source) freshUnit.needsCaptureChecking = unit.needsCaptureChecking freshUnit.knowsPureFuns = unit.knowsPureFuns - inContext(adjustPrinter(rootCtx.fresh.setCompilationUnit(freshUnit))): + inContext(adjustPrinter(rootCtx.fresh.setCompilationUnit(freshUnit), unit.typedAsJava)): testSame(i"$unpickled%\n%", beforePickling(cls), cls) private def testSame(unpickled: String, previous: String, cls: ClassSymbol)(using Context) = From c12ed63d8aa6fd948028ea29aad6d44d635014c8 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Thu, 14 Dec 2023 11:53:21 +0100 Subject: [PATCH 08/11] ensure synthetic java default secondary constructor has body --- .../tools/dotc/parsing/JavaParsers.scala | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala index bdd29d9ec0ef..492a74c39633 100644 --- a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala @@ -120,12 +120,13 @@ object JavaParsers { // can call it. // This also avoids clashes between the constructor parameter names and member names. if (needsDummyConstr) { - if (constr1 == EmptyTree) constr1 = makeConstructor(List(), Nil) + if (constr1 == EmptyTree) constr1 = makeConstructor(List(), Nil, Parsers.unimplementedExpr) stats1 = constr1 :: stats1 - constr1 = makeConstructor(List(scalaDot(tpnme.Unit)), tparams, Flags.JavaDefined | Flags.PrivateLocal) + constr1 = + makeConstructor(List(scalaDot(tpnme.Unit)), tparams, EmptyTree, Flags.JavaDefined | Flags.PrivateLocal) } else if (constr1 == EmptyTree) { - constr1 = makeConstructor(List(), tparams) + constr1 = makeConstructor(List(), tparams, EmptyTree) } Template(constr1.asInstanceOf[DefDef], parents, Nil, EmptyValDef, stats1) } @@ -135,9 +136,9 @@ object JavaParsers { def makeParam(name: TermName, tpt: Tree): ValDef = ValDef(name, tpt, EmptyTree).withMods(Modifiers(Flags.JavaDefined | Flags.Param)) - def makeConstructor(formals: List[Tree], tparams: List[TypeDef], flags: FlagSet = Flags.JavaDefined): DefDef = { + def makeConstructor(formals: List[Tree], tparams: List[TypeDef], body: Tree, flags: FlagSet = Flags.JavaDefined): DefDef = { val vparams = formals.zipWithIndex.map { case (p, i) => makeSyntheticParam(i + 1, p).withMods(Modifiers(flags)) } - DefDef(nme.CONSTRUCTOR, joinParams(tparams, List(vparams)), TypeTree(), EmptyTree).withMods(Modifiers(flags)) + DefDef(nme.CONSTRUCTOR, joinParams(tparams, List(vparams)), TypeTree(), body).withMods(Modifiers(flags)) } // ------------- general parsing --------------------------- @@ -750,7 +751,7 @@ object JavaParsers { atSpan(cdef.span) { assert(cdef.span.exists) ModuleDef(cdef.name.toTermName, - makeTemplate(List(), statics, List(), false)).withMods((cdef.mods & Flags.RetainedModuleClassFlags).toTermFlags) + makeTemplate(List(), statics, List(), needsDummyConstr = false)).withMods((cdef.mods & Flags.RetainedModuleClassFlags).toTermFlags) } def addCompanionObject(statics: List[Tree], cdef: TypeDef): List[Tree] = @@ -821,7 +822,7 @@ object JavaParsers { val interfaces = interfacesOpt() val (statics, body) = typeBody(CLASS, name, tparams) val cls = atSpan(start, nameOffset) { - TypeDef(name, makeTemplate(superclass :: interfaces, body, tparams, true)).withMods(mods) + TypeDef(name, makeTemplate(superclass :: interfaces, body, tparams, needsDummyConstr = true)).withMods(mods) } addCompanionObject(statics, cls) } @@ -864,7 +865,7 @@ object JavaParsers { parents = superclass :: interfaces, stats = canonicalConstructor :: accessors ::: body, tparams = tparams, - true + needsDummyConstr = true ) ).withMods(mods) } @@ -887,7 +888,7 @@ object JavaParsers { val iface = atSpan(start, nameOffset) { TypeDef( name, - makeTemplate(parents, body, tparams, false)).withMods(mods | Flags.JavaInterface) + makeTemplate(parents, body, tparams, needsDummyConstr = false)).withMods(mods | Flags.JavaInterface) } addCompanionObject(statics, iface) } @@ -940,7 +941,7 @@ object JavaParsers { } val constr = DefDef(nme.CONSTRUCTOR, List(constructorParams), TypeTree(), EmptyTree).withMods(Modifiers(Flags.JavaDefined)) - val templ = makeTemplate(annotationParents, constr :: body, List(), true) + val templ = makeTemplate(annotationParents, constr :: body, List(), needsDummyConstr = true) val annot = atSpan(start, nameOffset) { TypeDef(name, templ).withMods(mods | Flags.JavaInterface | Flags.JavaAnnotation) } @@ -992,7 +993,7 @@ object JavaParsers { Select(New(javaLangDot(tpnme.Enum)), nme.CONSTRUCTOR), List(enumType)), Nil) val enumclazz = atSpan(start, nameOffset) { TypeDef(name, - makeTemplate(superclazz :: interfaces, body, List(), true)).withMods(mods | Flags.JavaEnum) + makeTemplate(superclazz :: interfaces, body, List(), needsDummyConstr = true)).withMods(mods | Flags.JavaEnum) } addCompanionObject(consts ::: statics ::: predefs, enumclazz) } From 0938fe5603a17c7c786ac8fc6118a9d5d8b257de Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Thu, 14 Dec 2023 15:57:38 +0100 Subject: [PATCH 09/11] elide fake java primary constructor introduce SPLITCLAUSE at the end of the template parents, if present, then there is no primary constructor. We assert that we have the JAVAattr and reconstruct the fake primary constructor to satisfy the compiler. --- .../tools/dotc/core/tasty/TreePickler.scala | 14 +++++- .../tools/dotc/core/tasty/TreeUnpickler.scala | 50 +++++++++++++++++-- .../tools/dotc/parsing/JavaParsers.scala | 4 +- .../tools/dotc/printing/OutlinePrinter.scala | 1 + .../dotty/tools/dotc/transform/Pickler.scala | 25 +++------- tasty/src/dotty/tools/tasty/TastyFormat.scala | 3 +- 6 files changed, 70 insertions(+), 27 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 68b4fa770bf4..e652d68f1f51 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -361,7 +361,7 @@ class TreePickler(pickler: TastyPickler, attributes: Attributes) { else throw ex if sym.is(Method) && sym.owner.isClass then - profile.recordMethodSize(sym, currentAddr.index - addr.index, mdef.span) + profile.recordMethodSize(sym, (currentAddr.index - addr.index) max 1, mdef.span) for docCtx <- ctx.docCtx do val comment = docCtx.docstrings.lookup(sym) if comment != null then @@ -617,7 +617,17 @@ class TreePickler(pickler: TastyPickler, attributes: Attributes) { } } } - pickleStats(tree.constr :: rest) + if isJavaPickle then + val rest0 = rest.dropWhile: + case stat: ValOrDefDef => stat.symbol.is(Flags.Invisible) + case _ => false + if tree.constr.symbol.is(Flags.Invisible) then + writeByte(SPLITCLAUSE) + pickleStats(rest0) + else + pickleStats(tree.constr :: rest0) + else + pickleStats(tree.constr :: rest) } case Import(expr, selectors) => writeByte(IMPORT) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 756924580333..4a21afa97054 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -163,6 +163,11 @@ class TreeUnpickler(reader: TastyReader, def forkAt(start: Addr): TreeReader = new TreeReader(subReader(start, endAddr)) def fork: TreeReader = forkAt(currentAddr) + def skipParentTree(tag: Int): Unit = { + if tag == SPLITCLAUSE then () + else skipTree(tag) + } + def skipParentTree(): Unit = skipParentTree(readByte()) def skipTree(tag: Int): Unit = { if (tag >= firstLengthTreeTag) goto(readEnd()) else if (tag >= firstNatASTTreeTag) { readNat(); skipTree() } @@ -1011,7 +1016,7 @@ class TreeUnpickler(reader: TastyReader, * parsed in this way as InferredTypeTrees. */ def readParents(withArgs: Boolean)(using Context): List[Tree] = - collectWhile(nextByte != SELFDEF && nextByte != DEFDEF) { + collectWhile({val tag = nextByte; tag != SELFDEF && tag != DEFDEF && tag != SPLITCLAUSE}) { nextUnsharedTag match case APPLY | TYPEAPPLY | BLOCK => if withArgs then readTree() @@ -1038,7 +1043,8 @@ class TreeUnpickler(reader: TastyReader, val bodyFlags = { val bodyIndexer = fork // The first DEFDEF corresponds to the primary constructor - while (bodyIndexer.reader.nextByte != DEFDEF) bodyIndexer.skipTree() + while ({val tag = bodyIndexer.reader.nextByte; tag != DEFDEF && tag != SPLITCLAUSE}) do + bodyIndexer.skipParentTree() bodyIndexer.indexStats(end) } val parentReader = fork @@ -1057,7 +1063,38 @@ class TreeUnpickler(reader: TastyReader, cls.owner.thisType, cls, parentTypes, cls.unforcedDecls, selfInfo = if (self.isEmpty) NoType else self.tpt.tpe ).integrateOpaqueMembers - val constr = readIndexedDef().asInstanceOf[DefDef] + + val (constr, stats0) = + if nextByte == SPLITCLAUSE then + assert(unpicklingJava, s"unexpected SPLITCLAUSE at $start") + val tag = readByte() + def ta = ctx.typeAssigner + val flags = Flags.JavaDefined | Flags.PrivateLocal | Flags.Invisible + val pflags = Flags.JavaDefined | Flags.Param + val tdefRefs = tparams.map(_.symbol.asType) + val ctorCompleter = new LazyType { + def complete(denot: SymDenotation)(using Context) = + val sym = denot.symbol + lazy val tparamSyms: List[TypeSymbol] = tparams.map: tdef => + val completer = new LazyType { + def complete(denot: SymDenotation)(using Context) = + denot.info = tdef.symbol.asType.info.subst(tdefRefs, tparamSyms.map(_.typeRef)) + } + newSymbol(sym, tdef.name, pflags, completer, coord = cls.coord) + val paramSym = + newSymbol(sym, nme.syntheticParamName(1), pflags, defn.UnitType, coord = cls.coord) + val paramSymss = tparamSyms :: List(paramSym) :: Nil + val res = effectiveResultType(sym, paramSymss) + denot.info = methodType(paramSymss, res) + denot.setParamss(paramSymss) + } + val ctorSym = newSymbol(ctx.owner, nme.CONSTRUCTOR, flags, ctorCompleter, coord = coordAt(start)) + val accSym = newSymbol(cls, nme.syntheticParamName(1), flags, defn.UnitType, coord = ctorSym.coord) + val ctorDef = tpd.DefDef(ctorSym, EmptyTree) + val accessor = tpd.ValDef(accSym, ElidedTree(accSym.info)) + (ctorDef.setDefTree, accessor.setDefTree :: Nil) + else + readIndexedDef().asInstanceOf[DefDef] -> Nil val mappedParents: LazyTreeList = if parents.exists(_.isInstanceOf[InferredTypeTree]) then // parents were not read fully, will need to be read again later on demand @@ -1068,7 +1105,7 @@ class TreeUnpickler(reader: TastyReader, val lazyStats = readLater(end, rdr => { val stats = rdr.readIndexedStats(localDummy, end) - tparams ++ vparams ++ stats + tparams ++ vparams ++ stats0 ++ stats }) defn.patchStdLibClass(cls) NamerOps.addConstructorProxies(cls) @@ -1178,6 +1215,9 @@ class TreeUnpickler(reader: TastyReader, // ------ Reading trees ----------------------------------------------------- + private def ElidedTree(tpe: Type)(using Context): Tree = + untpd.Ident(nme.WILDCARD).withType(tpe) + def readTree()(using Context): Tree = { val sctx = sourceChangeContext() if (sctx `ne` ctx) return readTree()(using sctx) @@ -1234,7 +1274,7 @@ class TreeUnpickler(reader: TastyReader, val msg = s"Illegal elided tree in unpickler at $start without ${attributeTagToString(OUTLINEattr)}, ${ctx.source}" report.error(msg) - untpd.Ident(nme.WILDCARD).withType(readType()) + ElidedTree(readType()) case IDENTtpt => untpd.Ident(readName().toTypeName).withType(readType()) case SELECT => diff --git a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala index 492a74c39633..274e9e0febb1 100644 --- a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala @@ -120,10 +120,10 @@ object JavaParsers { // can call it. // This also avoids clashes between the constructor parameter names and member names. if (needsDummyConstr) { + val fakeFlags = Flags.JavaDefined | Flags.PrivateLocal | Flags.Invisible if (constr1 == EmptyTree) constr1 = makeConstructor(List(), Nil, Parsers.unimplementedExpr) stats1 = constr1 :: stats1 - constr1 = - makeConstructor(List(scalaDot(tpnme.Unit)), tparams, EmptyTree, Flags.JavaDefined | Flags.PrivateLocal) + constr1 = makeConstructor(List(scalaDot(tpnme.Unit)), tparams, EmptyTree, fakeFlags) } else if (constr1 == EmptyTree) { constr1 = makeConstructor(List(), tparams, EmptyTree) diff --git a/compiler/src/dotty/tools/dotc/printing/OutlinePrinter.scala b/compiler/src/dotty/tools/dotc/printing/OutlinePrinter.scala index 178335ee4b51..542c80be5663 100644 --- a/compiler/src/dotty/tools/dotc/printing/OutlinePrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/OutlinePrinter.scala @@ -17,6 +17,7 @@ object OutlinePrinter: /** A printer that elides known standard tree forms from the rhs of def and val. * Typically used for printing Java trees which elide the rhs. + * Note that there may still be some differences if you compare before and after pickling. */ class OutlinePrinter private (_ctx: Context) extends RefinedPrinter(_ctx) { diff --git a/compiler/src/dotty/tools/dotc/transform/Pickler.scala b/compiler/src/dotty/tools/dotc/transform/Pickler.scala index d1248230d356..e41c3cae8819 100644 --- a/compiler/src/dotty/tools/dotc/transform/Pickler.scala +++ b/compiler/src/dotty/tools/dotc/transform/Pickler.scala @@ -87,15 +87,6 @@ class Pickler extends Phase { Pickler.ParallelPickling && !ctx.settings.YtestPickler.value && !ctx.settings.YjavaTasty.value // disable parallel pickling when `-Yjava-tasty` is set (internal testing only) - private def adjustPrinter(ictx: Context, isOutline: Boolean): Context = - if isOutline then - // use special printer because Java parser will use `Predef.???` as rhs, - // which conflicts with the unpickling of ELIDED as `Ident(nme.WILDCARD).withType(tpe)` - // In the future we could modify the typer/parser to elide the rhs in the same way. - ictx.fresh.setPrinterFn(OutlinePrinter(_)) - else - ictx - override def run(using Context): Unit = { val unit = ctx.compilationUnit pickling.println(i"unpickling in run ${ctx.runId}") @@ -104,8 +95,7 @@ class Pickler extends Phase { cls <- dropCompanionModuleClasses(topLevelClasses(unit.tpdTree)) tree <- sliceTopLevel(unit.tpdTree, cls) do - if ctx.settings.YtestPickler.value then - beforePickling(cls) = tree.show(using adjustPrinter(ctx, unit.typedAsJava)) + if ctx.settings.YtestPickler.value then beforePickling(cls) = tree.show val sourceRelativePath = val reference = ctx.settings.sourceroot.value @@ -256,12 +246,13 @@ class Pickler extends Phase { if unit.typedAsJava then if unpickler.unpickler.nameAtRef.contents.exists(_ == nme.FromJavaObject) then report.error(em"Pickled reference to FromJavaObject in Java defined $cls in ${cls.source}") - val unpickled = unpickler.rootTrees - val freshUnit = CompilationUnit(rootCtx.compilationUnit.source) - freshUnit.needsCaptureChecking = unit.needsCaptureChecking - freshUnit.knowsPureFuns = unit.knowsPureFuns - inContext(adjustPrinter(rootCtx.fresh.setCompilationUnit(freshUnit), unit.typedAsJava)): - testSame(i"$unpickled%\n%", beforePickling(cls), cls) + else + val unpickled = unpickler.rootTrees + val freshUnit = CompilationUnit(rootCtx.compilationUnit.source) + freshUnit.needsCaptureChecking = unit.needsCaptureChecking + freshUnit.knowsPureFuns = unit.knowsPureFuns + inContext(rootCtx.fresh.setCompilationUnit(freshUnit)): + testSame(i"$unpickled%\n%", beforePickling(cls), cls) private def testSame(unpickled: String, previous: String, cls: ClassSymbol)(using Context) = import java.nio.charset.StandardCharsets.UTF_8 diff --git a/tasty/src/dotty/tools/tasty/TastyFormat.scala b/tasty/src/dotty/tools/tasty/TastyFormat.scala index adadd3267aac..ce3e1a852c74 100644 --- a/tasty/src/dotty/tools/tasty/TastyFormat.scala +++ b/tasty/src/dotty/tools/tasty/TastyFormat.scala @@ -81,8 +81,9 @@ Standard-Section: "ASTs" TopLevelStat* Param = TypeParam TermParam Template = TEMPLATE Length TypeParam* TermParam* parent_Term* Self? - Stat* -- [typeparams] paramss extends parents { self => stats }, where Stat* always starts with the primary constructor. + EndParents? Stat* -- [typeparams] paramss extends parents { self => stats }, where Stat* always starts with the primary constructor. Self = SELFDEF selfName_NameRef selfType_Term -- selfName : selfType + EndParents = SPLITCLAUSE -- explicitly end the template header, e.g. if there is no primary constructor Term = Path -- Paths represent both types and terms IDENT NameRef Type -- Used when term ident’s type is not a TermRef From 7b097242ab793d170aec98415f099eb03ad794b7 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Thu, 14 Dec 2023 18:04:12 +0100 Subject: [PATCH 10/11] fix indentation --- compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index e652d68f1f51..d70b56fca43d 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -203,7 +203,7 @@ class TreePickler(pickler: TastyPickler, attributes: Attributes) { else writeByte(if (tpe.isType) TYPEREF else TERMREF) pickleNameAndSig(sym.name, tpe.signature, sym.targetName) - pickleType(tpe.prefix) + pickleType(tpe.prefix) } if (sym.is(Flags.Package)) { writeByte(if (tpe.isType) TYPEREFpkg else TERMREFpkg) From 67e94bee81721c255d9946bbc52ae58dc45ae901 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Fri, 15 Dec 2023 12:13:47 +0100 Subject: [PATCH 11/11] restore print test for java tasty. Also do not make a fake param accessor for the fake param of the fake constructor. --- compiler/src/dotty/tools/dotc/ast/untpd.scala | 1 - .../tools/dotc/core/tasty/TreeUnpickler.scala | 28 ++++----- .../tools/dotc/parsing/JavaParsers.scala | 59 ++++++++++--------- .../tools/dotc/printing/OutlinePrinter.scala | 12 ++++ .../tools/dotc/printing/RefinedPrinter.scala | 13 +++- .../dotty/tools/dotc/transform/Pickler.scala | 23 +++++--- 6 files changed, 83 insertions(+), 53 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 817ff5c6c9fa..aabfdd97d7bd 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -510,7 +510,6 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def scalaRuntimeDot(name: Name)(using SourceFile): Select = Select(scalaDot(nme.runtime), name) def scalaUnit(implicit src: SourceFile): Select = scalaDot(tpnme.Unit) def scalaAny(implicit src: SourceFile): Select = scalaDot(tpnme.Any) - def javaDotLangDot(name: Name)(implicit src: SourceFile): Select = Select(Select(Ident(nme.java), nme.lang), name) def captureRoot(using Context): Select = Select(scalaDot(nme.caps), nme.CAPTURE_ROOT) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 4a21afa97054..b7a25cb75613 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -1064,37 +1064,37 @@ class TreeUnpickler(reader: TastyReader, selfInfo = if (self.isEmpty) NoType else self.tpt.tpe ).integrateOpaqueMembers - val (constr, stats0) = + val constr = if nextByte == SPLITCLAUSE then assert(unpicklingJava, s"unexpected SPLITCLAUSE at $start") val tag = readByte() def ta = ctx.typeAssigner val flags = Flags.JavaDefined | Flags.PrivateLocal | Flags.Invisible - val pflags = Flags.JavaDefined | Flags.Param - val tdefRefs = tparams.map(_.symbol.asType) val ctorCompleter = new LazyType { def complete(denot: SymDenotation)(using Context) = val sym = denot.symbol - lazy val tparamSyms: List[TypeSymbol] = tparams.map: tdef => + val pflags = flags | Flags.Param + val tparamRefs = tparams.map(_.symbol.asType) + lazy val derivedTparamSyms: List[TypeSymbol] = tparams.map: tdef => val completer = new LazyType { def complete(denot: SymDenotation)(using Context) = - denot.info = tdef.symbol.asType.info.subst(tdefRefs, tparamSyms.map(_.typeRef)) + denot.info = tdef.symbol.asType.info.subst(tparamRefs, derivedTparamRefs) } - newSymbol(sym, tdef.name, pflags, completer, coord = cls.coord) - val paramSym = + newSymbol(sym, tdef.name, Flags.JavaDefined | Flags.Param, completer, coord = cls.coord) + lazy val derivedTparamRefs: List[Type] = derivedTparamSyms.map(_.typeRef) + val vparamSym = newSymbol(sym, nme.syntheticParamName(1), pflags, defn.UnitType, coord = cls.coord) - val paramSymss = tparamSyms :: List(paramSym) :: Nil + val vparamSymss: List[List[Symbol]] = List(vparamSym) :: Nil + val paramSymss = + if derivedTparamSyms.nonEmpty then derivedTparamSyms :: vparamSymss else vparamSymss val res = effectiveResultType(sym, paramSymss) denot.info = methodType(paramSymss, res) denot.setParamss(paramSymss) } val ctorSym = newSymbol(ctx.owner, nme.CONSTRUCTOR, flags, ctorCompleter, coord = coordAt(start)) - val accSym = newSymbol(cls, nme.syntheticParamName(1), flags, defn.UnitType, coord = ctorSym.coord) - val ctorDef = tpd.DefDef(ctorSym, EmptyTree) - val accessor = tpd.ValDef(accSym, ElidedTree(accSym.info)) - (ctorDef.setDefTree, accessor.setDefTree :: Nil) + tpd.DefDef(ctorSym, EmptyTree).setDefTree // fake primary constructor else - readIndexedDef().asInstanceOf[DefDef] -> Nil + readIndexedDef().asInstanceOf[DefDef] val mappedParents: LazyTreeList = if parents.exists(_.isInstanceOf[InferredTypeTree]) then // parents were not read fully, will need to be read again later on demand @@ -1105,7 +1105,7 @@ class TreeUnpickler(reader: TastyReader, val lazyStats = readLater(end, rdr => { val stats = rdr.readIndexedStats(localDummy, end) - tparams ++ vparams ++ stats0 ++ stats + tparams ++ vparams ++ stats }) defn.patchStdLibClass(cls) NamerOps.addConstructorProxies(cls) diff --git a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala index 274e9e0febb1..f7ef86ee5cde 100644 --- a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala @@ -12,6 +12,7 @@ import Scanners.Offset import Parsers.* import core.* import Contexts.* +import Symbols.defn import Names.* import Types.* import ast.Trees.* @@ -27,6 +28,9 @@ object JavaParsers { import ast.untpd.* + + val fakeFlags = Flags.JavaDefined | Flags.PrivateLocal | Flags.Invisible + class JavaParser(source: SourceFile)(using Context) extends ParserCommon(source) { val definitions: Definitions = ctx.definitions @@ -89,16 +93,16 @@ object JavaParsers { // --------- tree building ----------------------------- - def scalaAnnotationDot(name: Name): Select = Select(scalaDot(nme.annotation), name) - def javaDot(name: Name): Tree = Select(rootDot(nme.java), name) def javaLangDot(name: Name): Tree = Select(javaDot(nme.lang), name) - /** Tree representing `java.lang.Object` */ - def javaLangObject(): Tree = javaLangDot(tpnme.Object) + /** Synthetic tree representing `java.lang.Object`. + * The typer will type all references to `java.lang.Object` as `FromJavaObject`. + */ + def ObjectTpt(): Tree = TypeTree(defn.FromJavaObjectType) // javaLangDot(tpnme.Object) /** Tree representing `java.lang.Record` */ def javaLangRecord(): Tree = javaLangDot(tpnme.Record) @@ -107,6 +111,8 @@ object JavaParsers { AppliedTypeTree(scalaDot(tpnme.Array), List(tpt)) def makeTemplate(parents: List[Tree], stats: List[Tree], tparams: List[TypeDef], needsDummyConstr: Boolean): Template = { + def UnitTpt(): Tree = TypeTree(defn.UnitType) + def pullOutFirstConstr(stats: List[Tree]): (Tree, List[Tree]) = stats match { case (meth: DefDef) :: rest if meth.name == nme.CONSTRUCTOR => (meth, rest) case first :: rest => @@ -120,10 +126,9 @@ object JavaParsers { // can call it. // This also avoids clashes between the constructor parameter names and member names. if (needsDummyConstr) { - val fakeFlags = Flags.JavaDefined | Flags.PrivateLocal | Flags.Invisible if (constr1 == EmptyTree) constr1 = makeConstructor(List(), Nil, Parsers.unimplementedExpr) stats1 = constr1 :: stats1 - constr1 = makeConstructor(List(scalaDot(tpnme.Unit)), tparams, EmptyTree, fakeFlags) + constr1 = makeConstructor(List(UnitTpt()), tparams, EmptyTree, fakeFlags) } else if (constr1 == EmptyTree) { constr1 = makeConstructor(List(), tparams, EmptyTree) @@ -134,11 +139,11 @@ object JavaParsers { def makeSyntheticParam(count: Int, tpt: Tree): ValDef = makeParam(nme.syntheticParamName(count), tpt) def makeParam(name: TermName, tpt: Tree): ValDef = - ValDef(name, tpt, EmptyTree).withMods(Modifiers(Flags.JavaDefined | Flags.Param)) + ValDef(name, tpt, EmptyTree).withFlags(Flags.JavaDefined | Flags.Param) def makeConstructor(formals: List[Tree], tparams: List[TypeDef], body: Tree, flags: FlagSet = Flags.JavaDefined): DefDef = { - val vparams = formals.zipWithIndex.map { case (p, i) => makeSyntheticParam(i + 1, p).withMods(Modifiers(flags)) } - DefDef(nme.CONSTRUCTOR, joinParams(tparams, List(vparams)), TypeTree(), body).withMods(Modifiers(flags)) + val vparams = formals.zipWithIndex.map { case (p, i) => makeSyntheticParam(i + 1, p).withAddedFlags(flags) } + DefDef(nme.CONSTRUCTOR, joinParams(tparams, List(vparams)), TypeTree(), body).withFlags(flags) } // ------------- general parsing --------------------------- @@ -307,7 +312,7 @@ object JavaParsers { if (in.token == QMARK) { val offset = in.offset in.nextToken() - val hi = if (in.token == EXTENDS) { in.nextToken() ; typ() } else javaLangObject() + val hi = if (in.token == EXTENDS) { in.nextToken() ; typ() } else ObjectTpt() val lo = if (in.token == SUPER) { in.nextToken() ; typ() } else EmptyTree atSpan(offset) { /* @@ -508,7 +513,7 @@ object JavaParsers { atSpan(in.offset) { annotations() val name = identForType() - val hi = if (in.token == EXTENDS) { in.nextToken() ; bound() } else javaLangObject() + val hi = if (in.token == EXTENDS) { in.nextToken() ; bound() } else ObjectTpt() TypeDef(name, TypeBoundsTree(EmptyTree, hi)).withMods(Modifiers(flags)) } @@ -569,7 +574,7 @@ object JavaParsers { if in.token == IDENTIFIER && in.name == jnme.RECORDid then in.token = RECORD - def termDecl(start: Offset, mods: Modifiers, parentToken: Int, parentTParams: List[TypeDef]): List[Tree] = { + def termDecl(start: Offset, mods: Modifiers, parentToken: Int): List[Tree] = { val inInterface = definesInterface(parentToken) val tparams = if (in.token == LT) typeParams(Flags.JavaDefined | Flags.Param) else List() val isVoid = in.token == VOID @@ -741,11 +746,11 @@ object JavaParsers { ValDef(name, tpt2, if (mods.is(Flags.Param)) EmptyTree else unimplementedExpr).withMods(mods1) } - def memberDecl(start: Offset, mods: Modifiers, parentToken: Int, parentTParams: List[TypeDef]): List[Tree] = in.token match + def memberDecl(start: Offset, mods: Modifiers, parentToken: Int): List[Tree] = in.token match case CLASS | ENUM | RECORD | INTERFACE | AT => typeDecl(start, if definesInterface(parentToken) then mods | Flags.JavaStatic else mods) case _ => - termDecl(start, mods, parentToken, parentTParams) + termDecl(start, mods, parentToken) def makeCompanionObject(cdef: TypeDef, statics: List[Tree]): Tree = atSpan(cdef.span) { @@ -818,9 +823,9 @@ object JavaParsers { typ() } else - javaLangObject() + ObjectTpt() val interfaces = interfacesOpt() - val (statics, body) = typeBody(CLASS, name, tparams) + val (statics, body) = typeBody(CLASS, name) val cls = atSpan(start, nameOffset) { TypeDef(name, makeTemplate(superclass :: interfaces, body, tparams, needsDummyConstr = true)).withMods(mods) } @@ -835,7 +840,7 @@ object JavaParsers { val header = formalParams() val superclass = javaLangRecord() // records always extend java.lang.Record val interfaces = interfacesOpt() // records may implement interfaces - val (statics, body) = typeBody(RECORD, name, tparams) + val (statics, body) = typeBody(RECORD, name) // We need to generate accessors for every param, if no method with the same name is already defined @@ -883,8 +888,8 @@ object JavaParsers { repsep(() => typ(), COMMA) } else - List(javaLangObject()) - val (statics, body) = typeBody(INTERFACE, name, tparams) + List(ObjectTpt()) + val (statics, body) = typeBody(INTERFACE, name) val iface = atSpan(start, nameOffset) { TypeDef( name, @@ -893,14 +898,14 @@ object JavaParsers { addCompanionObject(statics, iface) } - def typeBody(leadingToken: Int, parentName: Name, parentTParams: List[TypeDef]): (List[Tree], List[Tree]) = { + def typeBody(leadingToken: Int, parentName: Name): (List[Tree], List[Tree]) = { accept(LBRACE) - val defs = typeBodyDecls(leadingToken, parentName, parentTParams) + val defs = typeBodyDecls(leadingToken, parentName) accept(RBRACE) defs } - def typeBodyDecls(parentToken: Int, parentName: Name, parentTParams: List[TypeDef]): (List[Tree], List[Tree]) = { + def typeBodyDecls(parentToken: Int, parentName: Name): (List[Tree], List[Tree]) = { val inInterface = definesInterface(parentToken) val statics = new ListBuffer[Tree] val members = new ListBuffer[Tree] @@ -916,7 +921,7 @@ object JavaParsers { else { adaptRecordIdentifier() if (in.token == ENUM || in.token == RECORD || definesInterface(in.token)) mods |= Flags.JavaStatic - val decls = memberDecl(start, mods, parentToken, parentTParams) + val decls = memberDecl(start, mods, parentToken) (if (mods.is(Flags.JavaStatic) || inInterface && !(decls exists (_.isInstanceOf[DefDef]))) statics else @@ -926,7 +931,7 @@ object JavaParsers { (statics.toList, members.toList) } def annotationParents: List[Tree] = List( - javaLangObject(), + ObjectTpt(), Select(javaLangDot(nme.annotation), tpnme.Annotation) ) def annotationDecl(start: Offset, mods: Modifiers): List[Tree] = { @@ -934,7 +939,7 @@ object JavaParsers { accept(INTERFACE) val nameOffset = in.offset val name = identForType() - val (statics, body) = typeBody(AT, name, List()) + val (statics, body) = typeBody(AT, name) val constructorParams = body.collect { case dd: DefDef => makeParam(dd.name, dd.tpt) @@ -969,7 +974,7 @@ object JavaParsers { val (statics, body) = if (in.token == SEMI) { in.nextToken() - typeBodyDecls(ENUM, name, List()) + typeBodyDecls(ENUM, name) } else (List(), List()) @@ -1093,7 +1098,7 @@ object JavaParsers { */ class OutlineJavaParser(source: SourceFile)(using Context) extends JavaParser(source) with OutlineParserCommon { override def skipBracesHook(): Option[Tree] = None - override def typeBody(leadingToken: Int, parentName: Name, parentTParams: List[TypeDef]): (List[Tree], List[Tree]) = { + override def typeBody(leadingToken: Int, parentName: Name): (List[Tree], List[Tree]) = { skipBraces() (List(EmptyValDef), List(EmptyTree)) } diff --git a/compiler/src/dotty/tools/dotc/printing/OutlinePrinter.scala b/compiler/src/dotty/tools/dotc/printing/OutlinePrinter.scala index 542c80be5663..cd8267355201 100644 --- a/compiler/src/dotty/tools/dotc/printing/OutlinePrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/OutlinePrinter.scala @@ -21,6 +21,18 @@ object OutlinePrinter: */ class OutlinePrinter private (_ctx: Context) extends RefinedPrinter(_ctx) { + /** print the symbol infos of type params for the fake java constructor */ + def shouldShowInfo(tsym: Symbol): Boolean = + tsym != NoSymbol && { + val ctor = tsym.owner + ctor.isAllOf(JavaDefined | PrivateLocal | Invisible) && ctor.isConstructor + } + + override def paramsText[T <: Untyped](params: ParamClause[T]): Text = (params: @unchecked) match + case untpd.TypeDefs(tparams) if shouldShowInfo(tparams.head.symbol) => + "[" ~ toText(tparams.map(_.symbol.info), ", ") ~ "]" + case _ => super.paramsText(params) + /* Typical patterns seen in output of typer for Java code, plus the output of unpickling an ELIDED tree */ def isElidableExpr[T <: Untyped](tree: Tree[T]): Boolean = tree match { case tree if tree.isEmpty => false diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index b73f29b4af5e..44ccf5c3c9fe 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -28,6 +28,7 @@ import config.{Config, Feature} import dotty.tools.dotc.util.SourcePosition import dotty.tools.dotc.ast.untpd.{MemberDef, Modifiers, PackageDef, RefTree, Template, TypeDef, ValOrDefDef} import cc.{CaptureSet, CapturingType, toCaptureSet, IllegalCaptureRef} +import dotty.tools.dotc.parsing.JavaParsers class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { @@ -1015,10 +1016,18 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { val (params, rest) = impl.body partition { case stat: TypeDef => stat.symbol.is(Param) case stat: ValOrDefDef => - stat.symbol.is(ParamAccessor) && !stat.symbol.isSetter + val sym = stat.symbol + sym.is(ParamAccessor) && !sym.isSetter + || sym.isAllOf(JavaParsers.fakeFlags | Param) case _ => false } - params ::: rest + val params0 = + if constr.symbol.isAllOf(JavaParsers.fakeFlags) then + // filter out fake param accessors + params.filterNot(_.symbol.isAllOf(JavaParsers.fakeFlags | Param)) + else + params + params0 ::: rest } else impl.body diff --git a/compiler/src/dotty/tools/dotc/transform/Pickler.scala b/compiler/src/dotty/tools/dotc/transform/Pickler.scala index e41c3cae8819..0be66828d58c 100644 --- a/compiler/src/dotty/tools/dotc/transform/Pickler.scala +++ b/compiler/src/dotty/tools/dotc/transform/Pickler.scala @@ -87,6 +87,10 @@ class Pickler extends Phase { Pickler.ParallelPickling && !ctx.settings.YtestPickler.value && !ctx.settings.YjavaTasty.value // disable parallel pickling when `-Yjava-tasty` is set (internal testing only) + private def printerContext(isOutline: Boolean)(using Context): Context = + if isOutline then ctx.fresh.setPrinterFn(OutlinePrinter(_)) + else ctx + override def run(using Context): Unit = { val unit = ctx.compilationUnit pickling.println(i"unpickling in run ${ctx.runId}") @@ -95,7 +99,8 @@ class Pickler extends Phase { cls <- dropCompanionModuleClasses(topLevelClasses(unit.tpdTree)) tree <- sliceTopLevel(unit.tpdTree, cls) do - if ctx.settings.YtestPickler.value then beforePickling(cls) = tree.show + if ctx.settings.YtestPickler.value then beforePickling(cls) = + tree.show(using printerContext(unit.typedAsJava)) val sourceRelativePath = val reference = ctx.settings.sourceroot.value @@ -243,16 +248,16 @@ class Pickler extends Phase { pickling.println("************* entered toplevel ***********") val rootCtx = ctx for ((cls, (unit, unpickler)) <- unpicklers) do - if unit.typedAsJava then + val testJava = unit.typedAsJava + if testJava then if unpickler.unpickler.nameAtRef.contents.exists(_ == nme.FromJavaObject) then report.error(em"Pickled reference to FromJavaObject in Java defined $cls in ${cls.source}") - else - val unpickled = unpickler.rootTrees - val freshUnit = CompilationUnit(rootCtx.compilationUnit.source) - freshUnit.needsCaptureChecking = unit.needsCaptureChecking - freshUnit.knowsPureFuns = unit.knowsPureFuns - inContext(rootCtx.fresh.setCompilationUnit(freshUnit)): - testSame(i"$unpickled%\n%", beforePickling(cls), cls) + val unpickled = unpickler.rootTrees + val freshUnit = CompilationUnit(rootCtx.compilationUnit.source) + freshUnit.needsCaptureChecking = unit.needsCaptureChecking + freshUnit.knowsPureFuns = unit.knowsPureFuns + inContext(printerContext(testJava)(using rootCtx.fresh.setCompilationUnit(freshUnit))): + testSame(i"$unpickled%\n%", beforePickling(cls), cls) private def testSame(unpickled: String, previous: String, cls: ClassSymbol)(using Context) = import java.nio.charset.StandardCharsets.UTF_8