From b80d85e12baf1050947097c19f0b1ce9cc8e5cf7 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Sun, 24 Dec 2017 18:15:01 +0100 Subject: [PATCH 1/6] Add decompiler tests --- .../decompiler/DecompilationPrinter.scala | 33 ++- .../dotty/tools/dotc/FromTastyTests.scala | 10 +- .../dotty/tools/vulpix/ParallelTesting.scala | 204 ++++++++++++------ tests/pos/lambda.decompiled | 13 ++ tests/pos/lambda.scala | 4 + tests/pos/methodTypes.decompiled | 11 + tests/pos/methodTypes.scala | 5 + tests/pos/simpleCaseObject.decompiled | 25 +++ tests/pos/simpleClass.decompiled | 7 + tests/run/puzzle.decompiled | 18 ++ 10 files changed, 249 insertions(+), 81 deletions(-) create mode 100644 tests/pos/lambda.decompiled create mode 100644 tests/pos/lambda.scala create mode 100644 tests/pos/methodTypes.decompiled create mode 100644 tests/pos/methodTypes.scala create mode 100644 tests/pos/simpleCaseObject.decompiled create mode 100644 tests/pos/simpleClass.decompiled create mode 100644 tests/run/puzzle.decompiled diff --git a/compiler/src/dotty/tools/dotc/decompiler/DecompilationPrinter.scala b/compiler/src/dotty/tools/dotc/decompiler/DecompilationPrinter.scala index b189b44133f5..9b7e6e7d1568 100644 --- a/compiler/src/dotty/tools/dotc/decompiler/DecompilationPrinter.scala +++ b/compiler/src/dotty/tools/dotc/decompiler/DecompilationPrinter.scala @@ -1,9 +1,12 @@ package dotty.tools.dotc package decompiler +import java.io.{OutputStream, PrintStream} + import dotty.tools.dotc.core.Contexts._ import dotty.tools.dotc.core.Phases.Phase import dotty.tools.dotc.core.tasty.TastyPrinter +import dotty.tools.io.{File, Path} /** Phase that prints the trees in all loaded compilation units. * @@ -14,23 +17,39 @@ class DecompilationPrinter extends Phase { override def phaseName: String = "decompilationPrinter" override def run(implicit ctx: Context): Unit = { - val unit = ctx.compilationUnit + val outputDir = ctx.settings.outputDir.value + if (outputDir == ".") printToOutput(System.out) + else { + var os: OutputStream = null + var ps: PrintStream = null + try { + os = File(outputDir + ".decompiled").outputStream() + ps = new PrintStream(os) + printToOutput(ps) + } finally { + if (os ne null) os.close() + if (ps ne null) ps.close() + } + } + } + private def printToOutput(out: PrintStream)(implicit ctx: Context): Unit = { + val unit = ctx.compilationUnit val pageWidth = ctx.settings.pageWidth.value val doubleLine = "=" * pageWidth val line = "-" * pageWidth - println(doubleLine) - println(unit.source) - println(line) + out.println(doubleLine) + out.println(unit.source) + out.println(line) - println(unit.tpdTree.show) - println(line) + out.println(unit.tpdTree.show) + out.println(line) if (ctx.settings.printTasty.value) { new TastyPrinter(unit.pickled.head._2).printContents() - println(line) + out.println(line) } } } diff --git a/compiler/test/dotty/tools/dotc/FromTastyTests.scala b/compiler/test/dotty/tools/dotc/FromTastyTests.scala index 91617aa027f0..caddc61caf1a 100644 --- a/compiler/test/dotty/tools/dotc/FromTastyTests.scala +++ b/compiler/test/dotty/tools/dotc/FromTastyTests.scala @@ -26,7 +26,7 @@ class FromTastyTests extends ParallelTesting { // > dotc -Ythrough-tasty -Ycheck:all implicit val testGroup: TestGroup = TestGroup("posTestFromTasty") - val (step1, step2) = compileTastyInDir("../tests/pos", defaultOptions, + val (step1, step2, step3) = compileTastyInDir("../tests/pos", defaultOptions, blacklist = Set( "NoCyclicReference.scala", "depfuntype.scala", @@ -53,7 +53,8 @@ class FromTastyTests extends ParallelTesting { ) step1.checkCompile() // Compile all files to generate the class files with tasty step2.checkCompile() // Compile from tasty - (step1 + step2).delete() + step3.checkCompile() // Decompile from tasty + (step1 + step2 + step3).delete() } @Test def runTestFromTasty: Unit = { @@ -63,7 +64,7 @@ class FromTastyTests extends ParallelTesting { // > dotr Test implicit val testGroup: TestGroup = TestGroup("runTestFromTasty") - val (step1, step2) = compileTastyInDir("../tests/run", defaultOptions, + val (step1, step2, step3) = compileTastyInDir("../tests/run", defaultOptions, blacklist = Set( "Course-2002-13.scala", "bridges.scala", @@ -93,7 +94,8 @@ class FromTastyTests extends ParallelTesting { ) step1.checkCompile() // Compile all files to generate the class files with tasty step2.checkRuns() // Compile from tasty and run the result - (step1 + step2).delete() + step3.checkCompile() // Decompile from tasty + (step1 + step2 + step3).delete() } private implicit class tastyCompilationTuples(tup: (CompilationTest, CompilationTest)) { diff --git a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala index 062eb929e3e8..9e907ea5cb2b 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -22,6 +22,7 @@ import dotc.reporting.diagnostic.MessageContainer import dotc.interfaces.Diagnostic.ERROR import dotc.util.DiffUtil import dotc.{ Driver, Compiler } +import dotc.decompiler /** A parallel testing suite whose goal is to integrate nicely with JUnit * @@ -47,7 +48,7 @@ trait ParallelTesting extends RunnerOrchestration { self => /** A test source whose files or directory of files is to be compiled * in a specific way defined by the `Test` */ - private sealed trait TestSource { self => + protected sealed trait TestSource { self => def name: String def outDir: JFile def flags: TestFlags @@ -133,7 +134,8 @@ trait ParallelTesting extends RunnerOrchestration { self => files: Array[JFile], flags: TestFlags, outDir: JFile, - fromTasty: Boolean = false + fromTasty: Boolean = false, + decompilation: Boolean = false ) extends TestSource { def sourceFiles: Array[JFile] = files.filter(isSourceFile) @@ -215,7 +217,7 @@ trait ParallelTesting extends RunnerOrchestration { self => private val filteredSources = if (!testFilter.isDefined) testSources else testSources.filter { - case JointCompilationSource(_, files, _, _, _) => + case JointCompilationSource(_, files, _, _, _, _) => files.exists(file => file.getAbsolutePath.contains(testFilter.get)) case SeparateCompilationSource(_, dir, _, _) => dir.getAbsolutePath.contains(testFilter.get) @@ -422,6 +424,34 @@ trait ParallelTesting extends RunnerOrchestration { self => reporter } + protected def decompile(flags0: TestFlags, suppressErrors: Boolean, targetDir: JFile): TestReporter = { + val decompilationOutput = new JFile(targetDir.getPath) + decompilationOutput.mkdir() + val flags = + flags0 and ("-d", decompilationOutput.getAbsolutePath) and + "-decompile" and "-pagewidth" and "80" + + def hasTastyFileToClassName(f: JFile): String = + targetDir.toPath.relativize(f.toPath).toString.dropRight(".hasTasty".length).replace('/', '.') + val classes = flattenFiles(targetDir).filter(isHasTastyFile).map(hasTastyFileToClassName) + + val reporter = + TestReporter.reporter(realStdout, logLevel = + if (suppressErrors || suppressAllOutput) ERROR + 1 else ERROR) + + val driver = decompiler.Main + + // Compile with a try to catch any StackTrace generated by the compiler: + try { + driver.process(flags.all ++ classes, reporter = reporter) + } + catch { + case NonFatal(ex) => reporter.logStackTrace(ex) + } + + reporter + } + private[ParallelTesting] def executeTestSuite(): this.type = { assert(_testSourcesCompleted == 0, "not allowed to re-use a `CompileRun`") @@ -474,9 +504,52 @@ trait ParallelTesting extends RunnerOrchestration { self => protected def encapsulatedCompilation(testSource: TestSource) = new LoggedRunnable { def checkTestSource(): Unit = tryCompile(testSource) { testSource match { - case testSource @ JointCompilationSource(_, files, flags, outDir, fromTasty) => + case testSource @ JointCompilationSource(name, files, flags, outDir, fromTasty, decompilation) => val reporter = - if (fromTasty) compileFromTasty(flags, false, outDir) + if (decompilation) { + val rep = decompile(flags, false, outDir) + + val checkFileOpt = files.flatMap { file => + if (file.isDirectory) Nil + else { + val fname = file.getAbsolutePath.reverse.dropWhile(_ != '.').reverse + "decompiled" + val checkFile = new JFile(fname) + if (checkFile.exists) List(checkFile) + else Nil + } + }.headOption + checkFileOpt match { + case Some(checkFile) => + val stripTrailingWhitespaces = "(.*\\S|)\\s+".r + val output = Source.fromFile(outDir + ".decompiled").getLines().map {line => + stripTrailingWhitespaces.unapplySeq(line).map(_.head).getOrElse(line) + }.mkString("\n") + .replaceFirst("@scala\\.annotation\\.internal\\.SourceFile\\([^\\)]+\\)( |\\n )", "") // FIXME: should not be printed in the decompiler + + checkDiff(output, checkFile, testSource, 0) match { + case Some(diff) => + println("Expected:") + println(checkFile) + println("Actual output;") + println(output) + println("Diff;") + echo(diff) + addFailureInstruction(diff) + + // Print build instructions to file and summary: + val buildInstr = testSource.buildInstructions(0, rep.warningCount) + addFailureInstruction(buildInstr) + + // Fail target: + failTestSource(testSource) + case None => + } + case _ => + } + + rep + } + else if (fromTasty) compileFromTasty(flags, false, outDir) else compile(testSource.sourceFiles, flags, false, outDir) registerCompletion(reporter.errorCount) @@ -526,39 +599,20 @@ trait ParallelTesting extends RunnerOrchestration { self => if (Properties.testsNoRun) addNoRunWarning() else runMain(testSource.runClassPath) match { case Success(_) if !checkFile.isDefined || !checkFile.get.exists => // success! - case Success(output) => { - val outputLines = output.lines.toArray :+ DiffUtil.EOF - val checkLines: Array[String] = Source.fromFile(checkFile.get).getLines().toArray :+ DiffUtil.EOF - val sourceTitle = testSource.title - - def linesMatch = - outputLines - .zip(checkLines) - .forall { case (x, y) => x == y } - - if (outputLines.length != checkLines.length || !linesMatch) { - // Print diff to files and summary: - val expectedSize = DiffUtil.EOF.length max checkLines.map(_.length).max - val diff = outputLines.padTo(checkLines.length, "").zip(checkLines.padTo(outputLines.length, "")).map { case (act, exp) => - DiffUtil.mkColoredLineDiff(exp, act, expectedSize) - }.mkString("\n") - - val msg = - s"""|Output from '$sourceTitle' did not match check file. - |Diff (expected on the left, actual right): - |""".stripMargin + diff + "\n" - echo(msg) - addFailureInstruction(msg) - - // Print build instructions to file and summary: - val buildInstr = testSource.buildInstructions(0, warnings) - addFailureInstruction(buildInstr) - - // Fail target: - failTestSource(testSource) + case Success(output) => + checkDiff(output, checkFile.get, testSource, warnings) match { + case Some(msg) => + echo(msg) + addFailureInstruction(msg) + + // Print build instructions to file and summary: + val buildInstr = testSource.buildInstructions(0, warnings) + addFailureInstruction(buildInstr) + + // Fail target: + failTestSource(testSource) + case None => } - } - case Failure(output) => echo(s"Test '${testSource.title}' failed with output:") echo(output) @@ -573,7 +627,7 @@ trait ParallelTesting extends RunnerOrchestration { self => protected def encapsulatedCompilation(testSource: TestSource) = new LoggedRunnable { def checkTestSource(): Unit = tryCompile(testSource) { val (compilerCrashed, errorCount, warningCount, verifier: Function0[Unit]) = testSource match { - case testSource @ JointCompilationSource(_, files, flags, outDir, fromTasty) => + case testSource @ JointCompilationSource(_, files, flags, outDir, fromTasty, decompilation) => val checkFile = files.flatMap { file => if (file.isDirectory) Nil else { @@ -682,7 +736,7 @@ trait ParallelTesting extends RunnerOrchestration { self => } val (compilerCrashed, expectedErrors, actualErrors, hasMissingAnnotations, errorMap) = testSource match { - case testSource @ JointCompilationSource(_, files, flags, outDir, fromTasty) => + case testSource @ JointCompilationSource(_, files, flags, outDir, fromTasty, decompilation) => val sourceFiles = testSource.sourceFiles val (errorMap, expectedErrors) = getErrorMapAndExpectedCount(sourceFiles) val reporter = compile(sourceFiles, flags, true, outDir) @@ -736,6 +790,31 @@ trait ParallelTesting extends RunnerOrchestration { self => } } + private def checkDiff(output: String, checkFile: JFile, testSource: TestSource, warnings: Int): Option[String] = { + val outputLines = output.lines.toArray :+ DiffUtil.EOF + val checkLines: Array[String] = Source.fromFile(checkFile).getLines().toArray :+ DiffUtil.EOF + val sourceTitle = testSource.title + + def linesMatch = + outputLines + .zip(checkLines) + .forall { case (x, y) => x == y } + + if (outputLines.length != checkLines.length || !linesMatch) { + // Print diff to files and summary: + val expectedSize = DiffUtil.EOF.length max checkLines.map(_.length).max + val diff = outputLines.padTo(checkLines.length, "").zip(checkLines.padTo(outputLines.length, "")).map { case (act, exp) => + DiffUtil.mkColoredLineDiff(exp, act, expectedSize) + }.mkString("\n") + + val msg = + s"""|Output from '$sourceTitle' did not match check file. + |Diff (expected on the left, actual right): + |""".stripMargin + diff + "\n" + Some(msg) + } else None + } + /** The `CompilationTest` is the main interface to `ParallelTesting`, it * can be instantiated via one of the following methods: * @@ -967,7 +1046,7 @@ trait ParallelTesting extends RunnerOrchestration { self => */ def copyToTarget(): CompilationTest = new CompilationTest ( targets.map { - case target @ JointCompilationSource(_, files, _, outDir, _) => + case target @ JointCompilationSource(_, files, _, outDir, _, _) => target.copy(files = files.map(copyToDir(outDir,_))) case target @ SeparateCompilationSource(_, dir, _, outDir) => target.copy(dir = copyToDir(outDir, dir)) @@ -1083,34 +1162,6 @@ trait ParallelTesting extends RunnerOrchestration { self => new CompilationTest(target) } - /** Compiles a single file from the string path `f` using the supplied flags - * - * Tests in the first part of the tuple must be executed before the second. - * Both testsRequires explicit delete(). - */ - def compileTasty(f: String, flags: TestFlags)(implicit testGroup: TestGroup): (CompilationTest, CompilationTest) = { - val sourceFile = new JFile(f) - val parent = sourceFile.getParentFile - val outDir = - defaultOutputDir + testGroup + "/" + - sourceFile.getName.substring(0, sourceFile.getName.lastIndexOf('.')) + "/" - - require( - sourceFile.exists && !sourceFile.isDirectory && - (parent ne null) && parent.exists && parent.isDirectory, - s"Source file: $f, didn't exist" - ) - val tastySource = createOutputDirsForFile(sourceFile, parent, outDir) - val target = JointCompilationSource( - testGroup.name, - Array(sourceFile), - flags.withClasspath(tastySource.getPath) and "-from-tasty", - tastySource, - fromTasty = true - ) - (compileFile(f, flags).keepOutput, new CompilationTest(target).keepOutput) - } - /** Compiles a directory `f` using the supplied `flags`. This method does * deep compilation, that is - it compiles all files and subdirectories * contained within the directory `f`. @@ -1216,7 +1267,7 @@ trait ParallelTesting extends RunnerOrchestration { self => * Both testsRequires explicit delete(). */ def compileTastyInDir(f: String, flags0: TestFlags, blacklist: Set[String] = Set.empty)( - implicit testGroup: TestGroup): (CompilationTest, CompilationTest) = { + implicit testGroup: TestGroup): (CompilationTest, CompilationTest, CompilationTest) = { val outDir = defaultOutputDir + testGroup + "/" val flags = flags0 and "-Yretain-trees" val sourceDir = new JFile(f) @@ -1231,9 +1282,22 @@ trait ParallelTesting extends RunnerOrchestration { self => } // TODO add SeparateCompilationSource from tasty? + val targets2 = + files + .filter(f => dotty.tools.io.File(f.toPath).changeExtension("decompiled").exists) + .map { f => + val classpath = createOutputDirsForFile(f, sourceDir, outDir) + JointCompilationSource(testGroup.name, Array(f), flags.withClasspath(classpath.getPath), classpath, decompilation = true) + } + // Create a CompilationTest and let the user decide whether to execute a pos or a neg test val generateClassFiles = compileFilesInDir(f, flags0, blacklist) - (generateClassFiles.keepOutput, new CompilationTest(targets).keepOutput) + + ( + generateClassFiles.keepOutput, + new CompilationTest(targets).keepOutput, + new CompilationTest(targets2).keepOutput + ) } diff --git a/tests/pos/lambda.decompiled b/tests/pos/lambda.decompiled new file mode 100644 index 000000000000..75e5e32589f0 --- /dev/null +++ b/tests/pos/lambda.decompiled @@ -0,0 +1,13 @@ +================================================================================ +../out/posTestFromTasty/pos/lambda/foo/Foo.class +-------------------------------------------------------------------------------- +package foo { + class Foo() extends Object() { + val a: Int => Int = + { + def $anonfun(x: Int): Int = x.*(x) + closure($anonfun) + } + } +} +-------------------------------------------------------------------------------- diff --git a/tests/pos/lambda.scala b/tests/pos/lambda.scala new file mode 100644 index 000000000000..952e28826894 --- /dev/null +++ b/tests/pos/lambda.scala @@ -0,0 +1,4 @@ +package foo +class Foo { + val a = (x: Int) => x * x +} diff --git a/tests/pos/methodTypes.decompiled b/tests/pos/methodTypes.decompiled new file mode 100644 index 000000000000..776508a9e5cd --- /dev/null +++ b/tests/pos/methodTypes.decompiled @@ -0,0 +1,11 @@ +================================================================================ +../out/posTestFromTasty/pos/methodTypes/Foo.class +-------------------------------------------------------------------------------- +package { + class Foo() extends Object() { + val x: Int = 1 + def y: Int = 2 + def z(): Int = 3 + } +} +-------------------------------------------------------------------------------- \ No newline at end of file diff --git a/tests/pos/methodTypes.scala b/tests/pos/methodTypes.scala new file mode 100644 index 000000000000..be509a0e5c30 --- /dev/null +++ b/tests/pos/methodTypes.scala @@ -0,0 +1,5 @@ +class Foo { + val x = 1 + def y = 2 + def z() = 3 +} diff --git a/tests/pos/simpleCaseObject.decompiled b/tests/pos/simpleCaseObject.decompiled new file mode 100644 index 000000000000..1affe040e09f --- /dev/null +++ b/tests/pos/simpleCaseObject.decompiled @@ -0,0 +1,25 @@ +================================================================================ +../out/posTestFromTasty/pos/simpleCaseObject/foo/Foo.class +-------------------------------------------------------------------------------- +package foo { + final lazy module case val Foo: foo.Foo = new foo.Foo() + final module case class Foo() extends Object() with _root_.scala.Product { + this: foo.Foo.type => + + override def hashCode(): Int = + { + var acc: Int = 329201766 + scala.runtime.Statics#finalizeHash(acc, 0) + } + override def toString(): String = "Foo" + override def canEqual(that: Any): Boolean = that.isInstanceOf[foo.Foo] + override def productArity: Int = 0 + override def productPrefix: String = "Foo" + override def productElement(n: Int): Any = + n match + { + case _ => throw new IndexOutOfBoundsException(n.toString()) + } + } +} +-------------------------------------------------------------------------------- \ No newline at end of file diff --git a/tests/pos/simpleClass.decompiled b/tests/pos/simpleClass.decompiled new file mode 100644 index 000000000000..86b7cf6b1204 --- /dev/null +++ b/tests/pos/simpleClass.decompiled @@ -0,0 +1,7 @@ +================================================================================ +../out/posTestFromTasty/pos/simpleClass/foo/A.class +-------------------------------------------------------------------------------- +package foo { + class A() extends Object() {} +} +-------------------------------------------------------------------------------- \ No newline at end of file diff --git a/tests/run/puzzle.decompiled b/tests/run/puzzle.decompiled new file mode 100644 index 000000000000..c578bead0631 --- /dev/null +++ b/tests/run/puzzle.decompiled @@ -0,0 +1,18 @@ +================================================================================ +../out/runTestFromTasty/run/puzzle/Test.class +-------------------------------------------------------------------------------- +package { + final lazy module val Test: Test = new Test() + final module class Test() extends Object() { this: Test.type => + def main(args: Array[String]): Unit = + { + println(if false then 5.0 else 53.0) + val x: Double = if false then 5.0 else 53.0 + println(x) + val z: Long = 1L + val y: Float = Long.long2float(z) + () + } + } +} +-------------------------------------------------------------------------------- \ No newline at end of file From bfb38d3d7ab97fa5d9a71dbc601f3c08a476562f Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 16 Jan 2018 13:50:14 +0100 Subject: [PATCH 2/6] Output to file and not show diff in terminal --- .gitignore | 2 + .../dotty/tools/vulpix/ParallelTesting.scala | 37 ++++++++++--------- tests/pos/simpleCaseObject.decompiled | 6 +-- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/.gitignore b/.gitignore index 11f3d71e8025..b8e3c9e0f96e 100644 --- a/.gitignore +++ b/.gitignore @@ -71,3 +71,5 @@ compiler/test/debug/Gen.jar compiler/before-pickling.txt compiler/after-pickling.txt *.dotty-ide-version + +*.decompiled.out diff --git a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala index 9e907ea5cb2b..6612e8671f41 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -526,23 +526,26 @@ trait ParallelTesting extends RunnerOrchestration { self => }.mkString("\n") .replaceFirst("@scala\\.annotation\\.internal\\.SourceFile\\([^\\)]+\\)( |\\n )", "") // FIXME: should not be printed in the decompiler - checkDiff(output, checkFile, testSource, 0) match { - case Some(diff) => - println("Expected:") - println(checkFile) - println("Actual output;") - println(output) - println("Diff;") - echo(diff) - addFailureInstruction(diff) - - // Print build instructions to file and summary: - val buildInstr = testSource.buildInstructions(0, rep.warningCount) - addFailureInstruction(buildInstr) - - // Fail target: - failTestSource(testSource) - case None => + val check: String = Source.fromFile(checkFile).getLines().mkString("\n") + + + if (output != check) { + val outFile = dotty.tools.io.File(checkFile.toPath).addExtension(".out") + outFile.writeAll(output) + val msg = + s"""Output differed for test $name, use the following command to see the diff: + | > diff $checkFile $outFile + """.stripMargin + + echo(msg) + addFailureInstruction(msg) + + // Print build instructions to file and summary: + val buildInstr = testSource.buildInstructions(0, rep.warningCount) + addFailureInstruction(buildInstr) + + // Fail target: + failTestSource(testSource) } case _ => } diff --git a/tests/pos/simpleCaseObject.decompiled b/tests/pos/simpleCaseObject.decompiled index 1affe040e09f..35761b4ac5d7 100644 --- a/tests/pos/simpleCaseObject.decompiled +++ b/tests/pos/simpleCaseObject.decompiled @@ -6,11 +6,7 @@ package foo { final module case class Foo() extends Object() with _root_.scala.Product { this: foo.Foo.type => - override def hashCode(): Int = - { - var acc: Int = 329201766 - scala.runtime.Statics#finalizeHash(acc, 0) - } + override def hashCode(): Int = 1045991777 override def toString(): String = "Foo" override def canEqual(that: Any): Boolean = that.isInstanceOf[foo.Foo] override def productArity: Int = 0 From 8ae50fe6e76a115fad50c404141452a5579972d3 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 16 Jan 2018 13:52:57 +0100 Subject: [PATCH 3/6] Cleanup --- .../dotty/tools/vulpix/ParallelTesting.scala | 72 +++++++++---------- 1 file changed, 33 insertions(+), 39 deletions(-) diff --git a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala index 6612e8671f41..32df00e60db7 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -48,7 +48,7 @@ trait ParallelTesting extends RunnerOrchestration { self => /** A test source whose files or directory of files is to be compiled * in a specific way defined by the `Test` */ - protected sealed trait TestSource { self => + private sealed trait TestSource { self => def name: String def outDir: JFile def flags: TestFlags @@ -602,20 +602,39 @@ trait ParallelTesting extends RunnerOrchestration { self => if (Properties.testsNoRun) addNoRunWarning() else runMain(testSource.runClassPath) match { case Success(_) if !checkFile.isDefined || !checkFile.get.exists => // success! - case Success(output) => - checkDiff(output, checkFile.get, testSource, warnings) match { - case Some(msg) => - echo(msg) - addFailureInstruction(msg) - - // Print build instructions to file and summary: - val buildInstr = testSource.buildInstructions(0, warnings) - addFailureInstruction(buildInstr) - - // Fail target: - failTestSource(testSource) - case None => + case Success(output) => { + val outputLines = output.lines.toArray :+ DiffUtil.EOF + val checkLines: Array[String] = Source.fromFile(checkFile.get).getLines().toArray :+ DiffUtil.EOF + val sourceTitle = testSource.title + + def linesMatch = + outputLines + .zip(checkLines) + .forall { case (x, y) => x == y } + + if (outputLines.length != checkLines.length || !linesMatch) { + // Print diff to files and summary: + val expectedSize = DiffUtil.EOF.length max checkLines.map(_.length).max + val diff = outputLines.padTo(checkLines.length, "").zip(checkLines.padTo(outputLines.length, "")).map { case (act, exp) => + DiffUtil.mkColoredLineDiff(exp, act, expectedSize) + }.mkString("\n") + + val msg = + s"""|Output from '$sourceTitle' did not match check file. + |Diff (expected on the left, actual right): + |""".stripMargin + diff + "\n" + echo(msg) + addFailureInstruction(msg) + + // Print build instructions to file and summary: + val buildInstr = testSource.buildInstructions(0, warnings) + addFailureInstruction(buildInstr) + + // Fail target: + failTestSource(testSource) } + } + case Failure(output) => echo(s"Test '${testSource.title}' failed with output:") echo(output) @@ -793,31 +812,6 @@ trait ParallelTesting extends RunnerOrchestration { self => } } - private def checkDiff(output: String, checkFile: JFile, testSource: TestSource, warnings: Int): Option[String] = { - val outputLines = output.lines.toArray :+ DiffUtil.EOF - val checkLines: Array[String] = Source.fromFile(checkFile).getLines().toArray :+ DiffUtil.EOF - val sourceTitle = testSource.title - - def linesMatch = - outputLines - .zip(checkLines) - .forall { case (x, y) => x == y } - - if (outputLines.length != checkLines.length || !linesMatch) { - // Print diff to files and summary: - val expectedSize = DiffUtil.EOF.length max checkLines.map(_.length).max - val diff = outputLines.padTo(checkLines.length, "").zip(checkLines.padTo(outputLines.length, "")).map { case (act, exp) => - DiffUtil.mkColoredLineDiff(exp, act, expectedSize) - }.mkString("\n") - - val msg = - s"""|Output from '$sourceTitle' did not match check file. - |Diff (expected on the left, actual right): - |""".stripMargin + diff + "\n" - Some(msg) - } else None - } - /** The `CompilationTest` is the main interface to `ParallelTesting`, it * can be instantiated via one of the following methods: * From 6f8198391fae9835580f62f7a5c88db09ba58053 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 22 Dec 2017 09:38:39 +0100 Subject: [PATCH 4/6] Create DecompilerPrinter and ReplPrinter --- .../decompiler/DecompilationPrinter.scala | 7 +- .../dotc/printing/DecompilerPrinter.scala | 64 ++++ .../tools/dotc/printing/RefinedPrinter.scala | 314 ++++++++++-------- .../tools/dotc/printing/ReplPrinter.scala | 35 ++ .../dotc/printing/UserFacingPrinter.scala | 39 --- .../tools/dotc/quoted/QuotePrinter.scala | 5 +- .../tools/dotc/typer/ErrorReporting.scala | 5 +- compiler/src/dotty/tools/repl/package.scala | 12 +- compiler/test-resources/repl/defs | 2 +- compiler/test-resources/type-printer/defs | 2 +- compiler/test/dotty/tools/repl/ReplTest.scala | 2 +- .../dotty/tools/vulpix/ParallelTesting.scala | 1 - tests/pos/lambda.decompiled | 5 +- tests/pos/simpleCaseObject.decompiled | 3 +- .../quote-run-staged-interpreter.check | 3 +- tests/run/puzzle.decompiled | 3 +- 16 files changed, 295 insertions(+), 207 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/printing/DecompilerPrinter.scala create mode 100644 compiler/src/dotty/tools/dotc/printing/ReplPrinter.scala delete mode 100644 compiler/src/dotty/tools/dotc/printing/UserFacingPrinter.scala diff --git a/compiler/src/dotty/tools/dotc/decompiler/DecompilationPrinter.scala b/compiler/src/dotty/tools/dotc/decompiler/DecompilationPrinter.scala index 9b7e6e7d1568..c41a27281c5f 100644 --- a/compiler/src/dotty/tools/dotc/decompiler/DecompilationPrinter.scala +++ b/compiler/src/dotty/tools/dotc/decompiler/DecompilationPrinter.scala @@ -6,6 +6,7 @@ import java.io.{OutputStream, PrintStream} import dotty.tools.dotc.core.Contexts._ import dotty.tools.dotc.core.Phases.Phase import dotty.tools.dotc.core.tasty.TastyPrinter +import dotty.tools.dotc.printing.DecompilerPrinter import dotty.tools.io.{File, Path} /** Phase that prints the trees in all loaded compilation units. @@ -36,7 +37,7 @@ class DecompilationPrinter extends Phase { private def printToOutput(out: PrintStream)(implicit ctx: Context): Unit = { val unit = ctx.compilationUnit val pageWidth = ctx.settings.pageWidth.value - + val printLines = ctx.settings.printLines.value val doubleLine = "=" * pageWidth val line = "-" * pageWidth @@ -44,7 +45,9 @@ class DecompilationPrinter extends Phase { out.println(unit.source) out.println(line) - out.println(unit.tpdTree.show) + val printer = new DecompilerPrinter(ctx) + + out.println(printer.toText(unit.tpdTree).mkString(pageWidth, printLines)) out.println(line) if (ctx.settings.printTasty.value) { diff --git a/compiler/src/dotty/tools/dotc/printing/DecompilerPrinter.scala b/compiler/src/dotty/tools/dotc/printing/DecompilerPrinter.scala new file mode 100644 index 000000000000..85cf5ee81437 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/printing/DecompilerPrinter.scala @@ -0,0 +1,64 @@ +package dotty.tools.dotc.printing + +import dotty.tools.dotc.ast.Trees.{Closure, DefDef, Untyped, ValDef} +import dotty.tools.dotc.ast.untpd.{PackageDef, Template, TypeDef} +import dotty.tools.dotc.ast.{Trees, untpd} +import dotty.tools.dotc.printing.Texts._ +import dotty.tools.dotc.core.Contexts._ +import dotty.tools.dotc.core.Flags._ +import dotty.tools.dotc.core.Symbols._ + +import scala.language.implicitConversions + +class DecompilerPrinter(_ctx: Context) extends RefinedPrinter(_ctx) { + + override protected def modText(mods: untpd.Modifiers, kw: String): Text = { // DD + val suppressKw = if (enclDefIsClass) mods is ParamAndLocal else mods is Param + var flagMask = + if (ctx.settings.YdebugFlags.value) AnyFlags + else if (suppressKw) PrintableFlags &~ Private + else PrintableFlags + if (homogenizedView && mods.flags.isTypeFlags) flagMask &~= Implicit // drop implicit from classes + val flags = mods.flags & flagMask + val flagsText = if (flags.isEmpty) "" else keywordStr((mods.flags & flagMask).toString) + val annotations = mods.annotations.filter(_.tpe != defn.SourceFileAnnotType) + Text(annotations.map(annotText), " ") ~~ flagsText ~~ (Str(kw) provided !suppressKw) + } + + override protected def blockText[T >: Untyped](trees: List[Trees.Tree[T]]): Text = { + super.blockText(trees.filterNot(_.isInstanceOf[Closure[_]])) + } + + override protected def packageDefText(tree: PackageDef): Text = { + val stats = tree.stats.filter { + case vdef: ValDef[_] => !vdef.symbol.is(Module) + case _ => true + } + val statsText = stats match { + case (pdef: PackageDef) :: Nil => toText(pdef) + case _ => toTextGlobal(stats, "\n") + } + val bodyText = + if (currentPrecedence == TopLevelPrec) "\n" ~ statsText else " {" ~ statsText ~ "}" + keywordStr("package ") ~ toTextPackageId(tree.pid) ~ bodyText + } + + override protected def templateText(tree: TypeDef, impl: Template): Text = { + val decl = + if (!tree.mods.is(Module)) modText(tree.mods, keywordStr(if ((tree).mods is Trait) "trait" else "class")) + else modText(tree.mods &~ (Final | Module), keywordStr("object")) + decl ~~ typeText(nameIdText(tree)) ~ withEnclosingDef(tree) { toTextTemplate(impl) } ~ "" + } + + override protected def defDefToText[T >: Untyped](tree: DefDef[T]): Text = { + import untpd.{modsDeco => _, _} + dclTextOr(tree) { + val printLambda = tree.symbol.isAnonymousFunction + val prefix = modText(tree.mods, keywordStr("def")) ~~ valDefText(nameIdText(tree)) provided (!printLambda) + withEnclosingDef(tree) { + addVparamssText(prefix ~ tparamsText(tree.tparams), tree.vparamss) ~ optAscription(tree.tpt).provided(!printLambda) ~ + optText(tree.rhs)((if (printLambda) " => " else " = ") ~ _) + } + } + } +} diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 0e5b33c52d18..f8c7ee3150b3 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -2,23 +2,36 @@ package dotty.tools.dotc package printing import core._ -import Texts._, Types._, Flags._, Names._, Symbols._, NameOps._, Constants._ +import Texts._ +import Types._ +import Flags._ +import Names._ +import Symbols._ +import NameOps._ +import Constants._ import TypeErasure.ErasedValueType -import Contexts.Context, Scopes.Scope, Denotations._, SymDenotations._, Annotations.Annotation +import Contexts.Context +import Scopes.Scope +import Denotations._ +import SymDenotations._ +import Annotations.Annotation import StdNames.{nme, tpnme} -import ast.{Trees, untpd, tpd} -import typer.{Namer, Inliner, Implicits} -import typer.ProtoTypes.{SelectionProto, ViewProto, FunProto, IgnoredProto, dummyTreeOfType} +import ast.{Trees, tpd, untpd} +import typer.{Implicits, Inliner, Namer} +import typer.ProtoTypes._ import Trees._ import TypeApplications._ import Decorators._ import config.Config import util.Positions._ -import transform.SymUtils._ +import dotty.tools.dotc.transform.SymUtils._ +import dotty.tools.dotc.transform.FirstTransform + import scala.annotation.switch import language.implicitConversions import dotty.tools.dotc.util.SourcePosition import Highlighting._ +import dotty.tools.dotc.ast.untpd.{MemberDef, Modifiers, PackageDef, RefTree, Template, TypeDef, ValOrDefDef} class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { @@ -27,6 +40,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { private[this] var myCtx: Context = _ctx private[this] var printPos = ctx.settings.YprintPos.value private[this] val printLines = ctx.settings.printLines.value + override protected[this] implicit def ctx: Context = myCtx def withEnclosingDef(enclDef: Tree[_ >: Untyped])(op: => Text): Text = { @@ -53,8 +67,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { try op finally printPos = savedPrintPos } - private def enclDefIsClass = enclosingDef match { - case owner: TypeDef[_] => owner.isClassDef + protected def enclDefIsClass = enclosingDef match { + case owner: TypeDef => owner.isClassDef case owner: untpd.ModuleDef => true case _ => false } @@ -64,7 +78,9 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { protected val PrintableFlags = (SourceModifierFlags | Label | Module | Local).toCommonFlags override def nameString(name: Name): String = - if (ctx.settings.YdebugNames.value) name.debugString else name.toString + if (name.isReplAssignName) name.decode.toString.takeWhile(_ != '$') + else if (ctx.settings.YdebugNames.value) name.debugString + else name.toString override protected def simpleNameString(sym: Symbol): String = nameString(if (ctx.property(XprintMode).isEmpty) sym.originalName else sym.name) @@ -176,8 +192,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { return nameString(tp.symbol) case _ => } - case ExprType(result) => - return "=> " ~ toText(result) + case tp: ExprType => + return exprToText(tp) case ErasedValueType(tycon, underlying) => return "ErasedValueType(" ~ toText(tycon) ~ ", " ~ toText(underlying) ~ ")" case tp: ClassInfo => @@ -209,24 +225,15 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { super.toText(tp) } - def blockText[T >: Untyped](trees: List[Tree[T]]): Text = + protected def exprToText(tp: ExprType): Text = + "=> " ~ toText(tp.resType) + + protected def blockText[T >: Untyped](trees: List[Tree[T]]): Text = ("{" ~ toText(trees, "\n") ~ "}").close override def toText[T >: Untyped](tree: Tree[T]): Text = controlled { - import untpd.{modsDeco => _, _} - /** Print modifiers from symbols if tree has type, overriding the untpd behavior. */ - implicit def modsDeco(mdef: untpd.MemberDef)(implicit ctx: Context): untpd.ModsDecorator = - new untpd.ModsDecorator { - def mods = if (mdef.hasType) Modifiers(mdef.symbol) else mdef.rawMods - } - - def Modifiers(sym: Symbol)(implicit ctx: Context): Modifiers = untpd.Modifiers( - sym.flags & (if (sym.isType) ModifierFlags | VarianceFlags else ModifierFlags), - if (sym.privateWithin.exists) sym.privateWithin.asType.name else tpnme.EMPTY, - sym.annotations map (_.tree)) - def isLocalThis(tree: Tree) = tree.typeOpt match { case tp: ThisType => tp.cls == ctx.owner.enclosingClass case _ => false @@ -234,21 +241,13 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { def optDotPrefix(tree: This) = optText(tree.qual)(_ ~ ".") provided !isLocalThis(tree) - def optAscription(tpt: untpd.Tree) = optText(tpt)(": " ~ _) - // Dotty deviation: called with an untpd.Tree, so cannot be a untpd.Tree[T] (seems to be a Scala2 problem to allow this) - // More deviations marked below as // DD - - def tparamsText[T >: Untyped](params: List[Tree]): Text = - "[" ~ toText(params, ", ") ~ "]" provided params.nonEmpty - - def addVparamssText(txt: Text, vparamss: List[List[ValDef]]): Text = - (txt /: vparamss)((txt, vparams) => txt ~ "(" ~ toText(vparams, ", ") ~ ")") - def caseBlockText(tree: Tree): Text = tree match { case Block(stats, expr) => toText(stats :+ expr, "\n") case expr => toText(expr) } + // Dotty deviation: called with an untpd.Tree, so cannot be a untpd.Tree[T] (seems to be a Scala2 problem to allow this) + // More deviations marked below as // DD def enumText(tree: untpd.Tree) = tree match { // DD case _: untpd.GenFrom | _: untpd.GenAlias => toText(tree) case _ => keywordStr("if ") ~ toText(tree) @@ -262,25 +261,6 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case untpd.Function(_, tpt) => " <% " ~ toText(tpt) } - def constrText(tree: untpd.Tree): Text = toTextLocal(tree).stripPrefix(keywordStr("new ")) // DD - - def annotText(tree: untpd.Tree): Text = "@" ~ constrText(tree) // DD - - def useSymbol = - tree.hasType && tree.symbol.exists && ctx.settings.YprintSyms.value - - def modText(mods: untpd.Modifiers, kw: String): Text = { // DD - val suppressKw = if (enclDefIsClass) mods is ParamAndLocal else mods is Param - var flagMask = - if (ctx.settings.YdebugFlags.value) AnyFlags - else if (suppressKw) PrintableFlags &~ Private - else PrintableFlags - if (homogenizedView && mods.flags.isTypeFlags) flagMask &~= Implicit // drop implicit from classes - val flags = mods.flags & flagMask - val flagsText = if (flags.isEmpty) "" else keywordStr((mods.flags & flagMask).toString) - Text(mods.annotations.map(annotText), " ") ~~ flagsText ~~ (Str(kw) provided !suppressKw) - } - def varianceText(mods: untpd.Modifiers) = if (mods is Covariant) "+" else if (mods is Contravariant) "-" @@ -296,66 +276,6 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case _ => toTextGlobal(arg) } - def dclTextOr(treeText: => Text) = - if (useSymbol) - annotsText(tree.symbol) ~~ dclText(tree.symbol) ~ - ( " " provided ctx.settings.YdebugOwners.value) - else treeText - - def idText(tree: untpd.Tree): Text = { - if ((ctx.settings.uniqid.value || Printer.debugPrintUnique) && tree.hasType && tree.symbol.exists) s"#${tree.symbol.id}" else "" - } - - def nameIdText(tree: untpd.NameTree): Text = { - if (tree.hasType && tree.symbol.exists) { - val str: Text = nameString(tree.symbol) - tree match { - case tree: RefTree => withPos(str, tree.pos) - case tree: MemberDef => withPos(str, tree.namePos) - case _ => str - } - } - else toText(tree.name) ~ idText(tree) - } - - def toTextTemplate(impl: Template, ofNew: Boolean = false): Text = { - val Template(constr @ DefDef(_, tparams, vparamss, _, _), parents, self, _) = impl - val tparamsTxt = withEnclosingDef(constr) { tparamsText(tparams) } - val primaryConstrs = if (constr.rhs.isEmpty) Nil else constr :: Nil - val prefix: Text = - if (vparamss.isEmpty || primaryConstrs.nonEmpty) tparamsTxt - else { - var modsText = modText(constr.mods, "") - if (!modsText.isEmpty) modsText = " " ~ modsText - if (constr.mods.hasAnnotations && !constr.mods.hasFlags) modsText = modsText ~~ " this" - withEnclosingDef(constr) { addVparamssText(tparamsTxt ~~ modsText, vparamss) } - } - val parentsText = Text(parents map constrText, keywordStr(" with ")) - val selfText = { - val selfName = if (self.name == nme.WILDCARD) keywordStr("this") else self.name.toString - (selfName ~ optText(self.tpt)(": " ~ _) ~ " =>").close - } provided !self.isEmpty - - val body = if (ctx.settings.YtestPickler.value) { - // Pickling/unpickling reorders the body members, so we need to homogenize - val (params, rest) = impl.body partition { - case stat: TypeDef => stat.symbol.is(Param) - case stat: ValOrDefDef => - stat.symbol.is(ParamAccessor) && !stat.symbol.isSetter - case _ => false - } - params ::: rest - } else impl.body - - val bodyText = "{" ~~ selfText ~~ toTextGlobal(primaryConstrs ::: body, "\n") ~ "}" - - prefix ~ (keywordText(" extends") provided !ofNew) ~~ parentsText ~~ bodyText - } - - def toTextPackageId(pid: Tree): Text = - if (homogenizedView && pid.hasType) toTextLocal(pid.tpe) - else toTextLocal(pid) - def toTextCore(tree: Tree): Text = tree match { case id: Trees.BackquotedIdent[_] if !homogenizedView => "`" ~ toText(id.name) ~ "`" @@ -478,31 +398,19 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { toTextLocal(extractor) ~ "(" ~ toTextGlobal(patterns, ", ") ~ ")" ~ ("(" ~ toTextGlobal(implicits, ", ") ~ ")" provided implicits.nonEmpty) - case tree @ ValDef(name, tpt, _) => - dclTextOr { - modText(tree.mods, keywordStr(if (tree.mods is Mutable) "var" else "val")) ~~ - valDefText(nameIdText(tree)) ~ optAscription(tpt) ~ - withEnclosingDef(tree) { optText(tree.rhs)(" = " ~ _) } - } - case tree @ DefDef(name, tparams, vparamss, tpt, _) => - dclTextOr { - val prefix = modText(tree.mods, keywordStr("def")) ~~ valDefText(nameIdText(tree)) - withEnclosingDef(tree) { - addVparamssText(prefix ~ tparamsText(tparams), vparamss) ~ optAscription(tpt) ~ - optText(tree.rhs)(" = " ~ _) - } - } + case tree @ ValDef(_, _, _) => + valDefToText(tree) + case tree @ DefDef(_, _, _, _, _) => + defDefToText(tree) case tree @ TypeDef(name, rhs) => def typeDefText(tparamsText: => Text, rhsText: => Text) = - dclTextOr { + dclTextOr(tree) { modText(tree.mods, keywordStr("type")) ~~ (varianceText(tree.mods) ~ typeText(nameIdText(tree))) ~ withEnclosingDef(tree) { tparamsText ~ rhsText } } def recur(rhs: Tree, tparamsTxt: => Text): Text = rhs match { case impl: Template => - modText(tree.mods, keywordStr(if ((tree).mods is Trait) "trait" else "class")) ~~ - typeText(nameIdText(tree)) ~ withEnclosingDef(tree) { toTextTemplate(impl) } ~ - (if (tree.hasType && ctx.settings.verbose.value) i"[decls = ${tree.symbol.info.decls}]" else "") + templateText(tree, impl) case rhs: TypeBoundsTree => typeDefText(tparamsTxt, toText(rhs)) case LambdaTypeTree(tparams, body) => @@ -523,14 +431,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case _ => "{" ~ Text(selectors map selectorText, ", ") ~ "}" } keywordStr("import ") ~ toTextLocal(expr) ~ "." ~ selectorsText - case PackageDef(pid, stats) => - val statsText = stats match { - case (pdef: PackageDef) :: Nil => toText(pdef) - case _ => toTextGlobal(stats, "\n") - } - val bodyText = - if (currentPrecedence == TopLevelPrec) "\n" ~ statsText else " {" ~ statsText ~ "}" - keywordStr("package ") ~ toTextPackageId(pid) ~ bodyText + case packageDef: PackageDef => + packageDefText(packageDef) case tree: Template => toTextTemplate(tree) case Annotated(arg, annot) => @@ -659,6 +561,140 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { } } + /** Print modifiers from symbols if tree has type, overriding the untpd behavior. */ + implicit def modsDeco(mdef: untpd.MemberDef)(implicit ctx: Context): untpd.ModsDecorator = + new untpd.ModsDecorator { + def mods = if (mdef.hasType) Modifiers(mdef.symbol) else mdef.rawMods + } + + def Modifiers(sym: Symbol)(implicit ctx: Context): Modifiers = untpd.Modifiers( + sym.flags & (if (sym.isType) ModifierFlags | VarianceFlags else ModifierFlags), + if (sym.privateWithin.exists) sym.privateWithin.asType.name else tpnme.EMPTY, + sym.annotations map (_.tree)) + + protected def optAscription[T >: Untyped](tpt: Tree[T]) = optText(tpt)(": " ~ _) + + private def idText(tree: untpd.Tree): Text = { + if ((ctx.settings.uniqid.value || Printer.debugPrintUnique) && tree.hasType && tree.symbol.exists) s"#${tree.symbol.id}" else "" + } + + private def useSymbol(tree: untpd.Tree) = + tree.hasType && tree.symbol.exists && ctx.settings.YprintSyms.value + + protected def nameIdText[T >: Untyped](tree: NameTree[T]): Text = { + if (tree.hasType && tree.symbol.exists) { + val str: Text = nameString(tree.symbol) + tree match { + case tree: RefTree => withPos(str, tree.pos) + case tree: MemberDef => withPos(str, tree.namePos) + case _ => str + } + } + else toText(tree.name) ~ idText(tree) + } + + protected def dclTextOr[T >: Untyped](tree: Tree[T])(treeText: => Text) = + if (useSymbol(tree)) + annotsText(tree.symbol) ~~ dclText(tree.symbol) ~ + ( " " provided ctx.settings.YdebugOwners.value) + else treeText + + def tparamsText[T >: Untyped](params: List[Tree[T]]): Text = + "[" ~ toText(params, ", ") ~ "]" provided params.nonEmpty + + def addVparamssText[T >: Untyped](txt: Text, vparamss: List[List[ValDef[T]]]): Text = + (txt /: vparamss)((txt, vparams) => txt ~ "(" ~ toText(vparams, ", ") ~ ")") + + protected def valDefToText[T >: Untyped](tree: ValDef[T]): Text = { + import untpd.{modsDeco => _, _} + dclTextOr(tree) { + modText(tree.mods, keywordStr(if (tree.mods is Mutable) "var" else "val")) ~~ + valDefText(nameIdText(tree)) ~ optAscription(tree.tpt) ~ + withEnclosingDef(tree) { optText(tree.rhs)(" = " ~ _) } + } + } + + protected def defDefToText[T >: Untyped](tree: DefDef[T]): Text = { + import untpd.{modsDeco => _, _} + dclTextOr(tree) { + val prefix = modText(tree.mods, keywordStr("def")) ~~ valDefText(nameIdText(tree)) + withEnclosingDef(tree) { + addVparamssText(prefix ~ tparamsText(tree.tparams), tree.vparamss) ~ optAscription(tree.tpt) ~ + optText(tree.rhs)(" = " ~ _) + } + } + } + + protected def toTextTemplate(impl: Template, ofNew: Boolean = false): Text = { + val Template(constr @ DefDef(_, tparams, vparamss, _, _), parents, self, _) = impl + val tparamsTxt = withEnclosingDef(constr) { tparamsText(tparams) } + val primaryConstrs = if (constr.rhs.isEmpty) Nil else constr :: Nil + val prefix: Text = + if (vparamss.isEmpty || primaryConstrs.nonEmpty) tparamsTxt + else { + var modsText = modText(constr.mods, "") + if (!modsText.isEmpty) modsText = " " ~ modsText + if (constr.mods.hasAnnotations && !constr.mods.hasFlags) modsText = modsText ~~ " this" + withEnclosingDef(constr) { addVparamssText(tparamsTxt ~~ modsText, vparamss) } + } + val parentsText = Text(parents map constrText, keywordStr(" with ")) + val selfText = { + val selfName = if (self.name == nme.WILDCARD) keywordStr("this") else self.name.toString + (selfName ~ optText(self.tpt)(": " ~ _) ~ " =>").close + } provided !self.isEmpty + + val body = if (ctx.settings.YtestPickler.value) { + // Pickling/unpickling reorders the body members, so we need to homogenize + val (params, rest) = impl.body partition { + case stat: TypeDef => stat.symbol.is(Param) + case stat: ValOrDefDef => + stat.symbol.is(ParamAccessor) && !stat.symbol.isSetter + case _ => false + } + params ::: rest + } else impl.body + + val bodyText = "{" ~~ selfText ~~ toTextGlobal(primaryConstrs ::: body, "\n") ~ "}" + + prefix ~ (keywordText(" extends") provided !ofNew) ~~ parentsText ~~ bodyText + } + + protected def templateText(tree: TypeDef, impl: Template): Text = { + val decl = modText(tree.mods, keywordStr(if ((tree).mods is Trait) "trait" else "class")) + decl ~~ typeText(nameIdText(tree)) ~ withEnclosingDef(tree) { toTextTemplate(impl) } ~ + (if (tree.hasType && ctx.settings.verbose.value) i"[decls = ${tree.symbol.info.decls}]" else "") + } + + protected def toTextPackageId[T >: Untyped](pid: Tree[T]): Text = + if (homogenizedView && pid.hasType) toTextLocal(pid.tpe.asInstanceOf[Showable]) + else toTextLocal(pid) + + protected def packageDefText(tree: PackageDef): Text = { + val statsText = tree.stats match { + case (pdef: PackageDef) :: Nil => toText(pdef) + case _ => toTextGlobal(tree.stats, "\n") + } + val bodyText = + if (currentPrecedence == TopLevelPrec) "\n" ~ statsText else " {" ~ statsText ~ "}" + keywordStr("package ") ~ toTextPackageId(tree.pid) ~ bodyText + } + + protected def constrText(tree: untpd.Tree): Text = toTextLocal(tree).stripPrefix(keywordStr("new ")) // DD + + protected def annotText(tree: untpd.Tree): Text = "@" ~ constrText(tree) // DD + + protected def modText(mods: untpd.Modifiers, kw: String): Text = { // DD + val suppressKw = if (enclDefIsClass) mods is ParamAndLocal else mods is Param + var flagMask = + if (ctx.settings.YdebugFlags.value) AnyFlags + else if (suppressKw) PrintableFlags &~ Private + else PrintableFlags + if (homogenizedView && mods.flags.isTypeFlags) flagMask &~= Implicit // drop implicit from classes + val flags = mods.flags & flagMask + val flagsText = if (flags.isEmpty) "" else keywordStr((mods.flags & flagMask).toString) + Text(mods.annotations.map(annotText), " ") ~~ flagsText ~~ (Str(kw) provided !suppressKw) + } + def optText(name: Name)(encl: Text => Text): Text = if (name.isEmpty) "" else encl(toText(name)) diff --git a/compiler/src/dotty/tools/dotc/printing/ReplPrinter.scala b/compiler/src/dotty/tools/dotc/printing/ReplPrinter.scala new file mode 100644 index 000000000000..5a3bfd6bf7ab --- /dev/null +++ b/compiler/src/dotty/tools/dotc/printing/ReplPrinter.scala @@ -0,0 +1,35 @@ +package dotty.tools.dotc.printing + +import dotty.tools.dotc.core.Constants +import dotty.tools.dotc.core.Constants.Constant +import dotty.tools.dotc.core.Contexts._ +import dotty.tools.dotc.core.Flags._ +import dotty.tools.dotc.core.NameOps._ +import dotty.tools.dotc.core.Symbols._ +import dotty.tools.dotc.core.Types.{ExprType, TypeAlias} +import dotty.tools.dotc.printing.Texts._ + +import scala.language.implicitConversions + +class ReplPrinter(_ctx: Context) extends DecompilerPrinter(_ctx) { + + override protected def exprToText(tp: ExprType): Text = + ": " ~ toText(tp.resType) + + override def toText(sym: Symbol): Text = + if (sym.name.isReplAssignName) nameString(sym.name) + else keyString(sym) ~~ nameString(sym.name.stripModuleClassSuffix) + + override def toText(const: Constant): Text = + if (const.tag == Constants.StringTag) Str('"' + const.value.toString + '"') + else Str(const.value.toString) + + override def dclText(sym: Symbol): Text = { + toText(sym) ~ { + if (sym.is(Method)) toText(sym.info) + else if (sym.isType && sym.info.isInstanceOf[TypeAlias]) toText(sym.info) + else if (sym.isType || sym.isClass) "" + else ":" ~~ toText(sym.info) + } + } +} diff --git a/compiler/src/dotty/tools/dotc/printing/UserFacingPrinter.scala b/compiler/src/dotty/tools/dotc/printing/UserFacingPrinter.scala deleted file mode 100644 index 9a00180017fe..000000000000 --- a/compiler/src/dotty/tools/dotc/printing/UserFacingPrinter.scala +++ /dev/null @@ -1,39 +0,0 @@ -package dotty.tools.dotc -package printing - -import core._ -import Constants.Constant, Contexts.Context, Denotations._, Flags._, Names._ -import NameOps._, StdNames._, Decorators._, Scopes.Scope, Types._, Texts._ -import SymDenotations.NoDenotation, Symbols.{ Symbol, ClassSymbol, defn } - -class UserFacingPrinter(_ctx: Context) extends RefinedPrinter(_ctx) { - - private[this] def getPkgCls(path: String) = - _ctx.requiredPackage(path).moduleClass.asClass - - override protected def keyString(sym: Symbol): String = - if (sym.flagsUNSAFE is Package) "" else super.keyString(sym) - - override def nameString(name: Name): String = - if (name.isReplAssignName) name.decode.toString.takeWhile(_ != '$') - else name.decode.toString - - override def toText(sym: Symbol): Text = - if (sym.name.isReplAssignName) nameString(sym.name) - else keyString(sym) ~~ nameString(sym.name.stripModuleClassSuffix) - - override def dclText(sym: Symbol): Text = toText(sym) ~ { - if (sym.is(Method)) toText(sym.info) - else if (sym.isType && sym.info.isInstanceOf[TypeAlias]) toText(sym.info) - else if (sym.isType || sym.isClass) "" - else ":" ~~ toText(sym.info) - } - - override def toText(const: Constant): Text = Str(const.value.toString) - - override def toText(tp: Type): Text = tp match { - case ExprType(result) => ":" ~~ toText(result) - case tp: ConstantType => toText(tp.value) - case tp => super.toText(tp) - } -} diff --git a/compiler/src/dotty/tools/dotc/quoted/QuotePrinter.scala b/compiler/src/dotty/tools/dotc/quoted/QuotePrinter.scala index 96ac9e8390e1..ea66d892e457 100644 --- a/compiler/src/dotty/tools/dotc/quoted/QuotePrinter.scala +++ b/compiler/src/dotty/tools/dotc/quoted/QuotePrinter.scala @@ -4,6 +4,7 @@ import java.io.PrintStream import dotty.tools.dotc.core.Contexts._ import dotty.tools.dotc.core.Phases.Phase +import dotty.tools.dotc.printing.DecompilerPrinter /** Pretty prints the compilation unit to an output stream */ class QuotePrinter(out: PrintStream) extends Phase { @@ -12,6 +13,8 @@ class QuotePrinter(out: PrintStream) extends Phase { override def run(implicit ctx: Context): Unit = { val unit = ctx.compilationUnit - out.print(unit.tpdTree.show) + val printer = new DecompilerPrinter(ctx) + val pageWidth = ctx.settings.pageWidth.value + out.print(printer.toText(unit.tpdTree).mkString(pageWidth, withLineNumbers = false)) } } diff --git a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala index d24127c9aa01..fdcee4ee6570 100644 --- a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -4,12 +4,9 @@ package typer import ast._ import core._ -import Trees._ import Types._, ProtoTypes._, Contexts._, Decorators._, Denotations._, Symbols._ -import Applications._, Implicits._, Flags._ +import Implicits._, Flags._ import util.Positions._ -import printing.{Showable, RefinedPrinter} -import scala.collection.mutable import java.util.regex.Matcher.quoteReplacement import reporting.diagnostic.Message import reporting.diagnostic.messages._ diff --git a/compiler/src/dotty/tools/repl/package.scala b/compiler/src/dotty/tools/repl/package.scala index 7643a6a95102..915f70199ac8 100644 --- a/compiler/src/dotty/tools/repl/package.scala +++ b/compiler/src/dotty/tools/repl/package.scala @@ -2,15 +2,9 @@ package dotty.tools import dotc.core.Contexts.Context import dotc.core.Symbols.Symbol -import dotc.core.Denotations.Denotation import dotc.reporting.diagnostic.MessageContainer -import dotc.printing.UserFacingPrinter - -import dotc.reporting.{ - StoreReporter, - UniqueMessagePositions, - HideNonSensicalMessages -} +import dotc.printing.ReplPrinter +import dotc.reporting.{HideNonSensicalMessages, StoreReporter, UniqueMessagePositions} package object repl { /** Create empty outer store reporter */ @@ -20,7 +14,7 @@ package object repl { private[repl] implicit class ShowUser(val s: Symbol) extends AnyVal { def showUser(implicit ctx: Context): String = { - val printer = new UserFacingPrinter(ctx) + val printer = new ReplPrinter(ctx) val text = printer.dclText(s) text.mkString(ctx.settings.pageWidth.value, ctx.settings.printLines.value) } diff --git a/compiler/test-resources/repl/defs b/compiler/test-resources/repl/defs index 6db260b48f26..8e70f2eacfc0 100644 --- a/compiler/test-resources/repl/defs +++ b/compiler/test-resources/repl/defs @@ -7,6 +7,6 @@ def baz(): Int scala> def qux(): Int = 2 def qux(): Int scala> def id(x: 4): 4 = x -def id(x: 4): 4 +def id(x: Int(4)): Int(4) scala> id(4) val res0: Int = 4 diff --git a/compiler/test-resources/type-printer/defs b/compiler/test-resources/type-printer/defs index f4f6514b1980..34b336f8a26a 100644 --- a/compiler/test-resources/type-printer/defs +++ b/compiler/test-resources/type-printer/defs @@ -7,4 +7,4 @@ def baz[A](a: A): A scala> def qux[A](a: A): a.type = a def qux[A](a: A): a.type scala> def singleton: 1 = 1 -def singleton: 1 +def singleton: Int(1) diff --git a/compiler/test/dotty/tools/repl/ReplTest.scala b/compiler/test/dotty/tools/repl/ReplTest.scala index 82d1baac52a2..6d3679f9c4b7 100644 --- a/compiler/test/dotty/tools/repl/ReplTest.scala +++ b/compiler/test/dotty/tools/repl/ReplTest.scala @@ -36,7 +36,7 @@ sealed class StoringPrintStream extends PrintStream(new NullPrintStream) { } class ReplTest extends ReplDriver( - Array("-classpath", List(Jars.dottyLib, Jars.dottyInterfaces).mkString(":")), + Array("-classpath", List(Jars.dottyLib, Jars.dottyInterfaces).mkString(":"), "-color:never"), new StoringPrintStream ) with MessageRendering { diff --git a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala index 32df00e60db7..b377bc4acc15 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -524,7 +524,6 @@ trait ParallelTesting extends RunnerOrchestration { self => val output = Source.fromFile(outDir + ".decompiled").getLines().map {line => stripTrailingWhitespaces.unapplySeq(line).map(_.head).getOrElse(line) }.mkString("\n") - .replaceFirst("@scala\\.annotation\\.internal\\.SourceFile\\([^\\)]+\\)( |\\n )", "") // FIXME: should not be printed in the decompiler val check: String = Source.fromFile(checkFile).getLines().mkString("\n") diff --git a/tests/pos/lambda.decompiled b/tests/pos/lambda.decompiled index 75e5e32589f0..3c249e11b65f 100644 --- a/tests/pos/lambda.decompiled +++ b/tests/pos/lambda.decompiled @@ -5,9 +5,8 @@ package foo { class Foo() extends Object() { val a: Int => Int = { - def $anonfun(x: Int): Int = x.*(x) - closure($anonfun) + (x: Int) => x.*(x) } } } --------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- \ No newline at end of file diff --git a/tests/pos/simpleCaseObject.decompiled b/tests/pos/simpleCaseObject.decompiled index 35761b4ac5d7..d06cd60b36d4 100644 --- a/tests/pos/simpleCaseObject.decompiled +++ b/tests/pos/simpleCaseObject.decompiled @@ -2,8 +2,7 @@ ../out/posTestFromTasty/pos/simpleCaseObject/foo/Foo.class -------------------------------------------------------------------------------- package foo { - final lazy module case val Foo: foo.Foo = new foo.Foo() - final module case class Foo() extends Object() with _root_.scala.Product { + case object Foo() extends Object() with _root_.scala.Product { this: foo.Foo.type => override def hashCode(): Int = 1045991777 diff --git a/tests/run-with-compiler/quote-run-staged-interpreter.check b/tests/run-with-compiler/quote-run-staged-interpreter.check index bacb3969c81b..b38370ccd67b 100644 --- a/tests/run-with-compiler/quote-run-staged-interpreter.check +++ b/tests/run-with-compiler/quote-run-staged-interpreter.check @@ -1,10 +1,9 @@ { { - def $anonfun(x: Int): Int = + (x: Int) => { 2.+(x).+(4) } - closure($anonfun) } } 6 diff --git a/tests/run/puzzle.decompiled b/tests/run/puzzle.decompiled index c578bead0631..9254fc930534 100644 --- a/tests/run/puzzle.decompiled +++ b/tests/run/puzzle.decompiled @@ -2,8 +2,7 @@ ../out/runTestFromTasty/run/puzzle/Test.class -------------------------------------------------------------------------------- package { - final lazy module val Test: Test = new Test() - final module class Test() extends Object() { this: Test.type => + object Test() extends Object() { this: Test.type => def main(args: Array[String]): Unit = { println(if false then 5.0 else 53.0) From 241bd91786f43c93b79a9dfbf0185dd0997e0b7e Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 17 Jan 2018 18:22:18 +0100 Subject: [PATCH 5/6] Move repl logic to ReplPrinter --- compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala | 4 +--- compiler/src/dotty/tools/dotc/printing/ReplPrinter.scala | 5 +++++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index f8c7ee3150b3..c6a94a0cac7b 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -78,9 +78,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { protected val PrintableFlags = (SourceModifierFlags | Label | Module | Local).toCommonFlags override def nameString(name: Name): String = - if (name.isReplAssignName) name.decode.toString.takeWhile(_ != '$') - else if (ctx.settings.YdebugNames.value) name.debugString - else name.toString + if (ctx.settings.YdebugNames.value) name.debugString else name.toString override protected def simpleNameString(sym: Symbol): String = nameString(if (ctx.property(XprintMode).isEmpty) sym.originalName else sym.name) diff --git a/compiler/src/dotty/tools/dotc/printing/ReplPrinter.scala b/compiler/src/dotty/tools/dotc/printing/ReplPrinter.scala index 5a3bfd6bf7ab..acd43d1848f7 100644 --- a/compiler/src/dotty/tools/dotc/printing/ReplPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/ReplPrinter.scala @@ -5,6 +5,7 @@ import dotty.tools.dotc.core.Constants.Constant import dotty.tools.dotc.core.Contexts._ import dotty.tools.dotc.core.Flags._ import dotty.tools.dotc.core.NameOps._ +import dotty.tools.dotc.core.Names.Name import dotty.tools.dotc.core.Symbols._ import dotty.tools.dotc.core.Types.{ExprType, TypeAlias} import dotty.tools.dotc.printing.Texts._ @@ -13,6 +14,10 @@ import scala.language.implicitConversions class ReplPrinter(_ctx: Context) extends DecompilerPrinter(_ctx) { + override def nameString(name: Name): String = + if (name.isReplAssignName) name.decode.toString.takeWhile(_ != '$') + else super.nameString(name) + override protected def exprToText(tp: ExprType): Text = ": " ~ toText(tp.resType) From a5f967e03589eb823a18c3b340d72c0c63ad2242 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 17 Jan 2018 18:23:12 +0100 Subject: [PATCH 6/6] Simplify mods.annotations filtering --- .../tools/dotc/printing/DecompilerPrinter.scala | 14 ++------------ .../dotty/tools/dotc/printing/RefinedPrinter.scala | 5 ++++- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/printing/DecompilerPrinter.scala b/compiler/src/dotty/tools/dotc/printing/DecompilerPrinter.scala index 85cf5ee81437..392d719cfefd 100644 --- a/compiler/src/dotty/tools/dotc/printing/DecompilerPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/DecompilerPrinter.scala @@ -12,18 +12,8 @@ import scala.language.implicitConversions class DecompilerPrinter(_ctx: Context) extends RefinedPrinter(_ctx) { - override protected def modText(mods: untpd.Modifiers, kw: String): Text = { // DD - val suppressKw = if (enclDefIsClass) mods is ParamAndLocal else mods is Param - var flagMask = - if (ctx.settings.YdebugFlags.value) AnyFlags - else if (suppressKw) PrintableFlags &~ Private - else PrintableFlags - if (homogenizedView && mods.flags.isTypeFlags) flagMask &~= Implicit // drop implicit from classes - val flags = mods.flags & flagMask - val flagsText = if (flags.isEmpty) "" else keywordStr((mods.flags & flagMask).toString) - val annotations = mods.annotations.filter(_.tpe != defn.SourceFileAnnotType) - Text(annotations.map(annotText), " ") ~~ flagsText ~~ (Str(kw) provided !suppressKw) - } + override protected def filterModTextAnnots(annots: List[untpd.Tree]): List[untpd.Tree] = + annots.filter(_.tpe != defn.SourceFileAnnotType) override protected def blockText[T >: Untyped](trees: List[Trees.Tree[T]]): Text = { super.blockText(trees.filterNot(_.isInstanceOf[Closure[_]])) diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index c6a94a0cac7b..2133ea1dd455 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -690,9 +690,12 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { if (homogenizedView && mods.flags.isTypeFlags) flagMask &~= Implicit // drop implicit from classes val flags = mods.flags & flagMask val flagsText = if (flags.isEmpty) "" else keywordStr((mods.flags & flagMask).toString) - Text(mods.annotations.map(annotText), " ") ~~ flagsText ~~ (Str(kw) provided !suppressKw) + val annotations = filterModTextAnnots(mods.annotations) + Text(annotations.map(annotText), " ") ~~ flagsText ~~ (Str(kw) provided !suppressKw) } + protected def filterModTextAnnots(annots: List[untpd.Tree]): List[untpd.Tree] = annots + def optText(name: Name)(encl: Text => Text): Text = if (name.isEmpty) "" else encl(toText(name))