Skip to content

Commit 89496e6

Browse files
Merge pull request #5513 from tuvior/vscodedotty-tasty
Decompile .tasty files with VS Code Extension
2 parents 01078c4 + 360e0d7 commit 89496e6

File tree

17 files changed

+540
-50
lines changed

17 files changed

+540
-50
lines changed

compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ object PickledQuotes {
8989
val pickled = pickler.assembleParts()
9090

9191
if (quotePickling ne noPrinter)
92-
new TastyPrinter(pickled).printContents()
92+
println(new TastyPrinter(pickled).printContents())
9393

9494
pickled
9595
}
@@ -98,7 +98,7 @@ object PickledQuotes {
9898
private def unpickle(bytes: Array[Byte], splices: Seq[Any], isType: Boolean)(implicit ctx: Context): Tree = {
9999
if (quotePickling ne noPrinter) {
100100
println(i"**** unpickling quote from TASTY")
101-
new TastyPrinter(bytes).printContents()
101+
println(new TastyPrinter(bytes).printContents())
102102
}
103103

104104
val mode = if (isType) UnpickleMode.TypeTree else UnpickleMode.Term
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package dotty.tools.dotc
2+
package core
3+
package tasty
4+
5+
import Contexts._, Decorators._
6+
import Names.Name
7+
import TastyUnpickler._
8+
import TastyBuffer.NameRef
9+
import util.Positions.offsetToInt
10+
import printing.Highlighting._
11+
12+
class TastyHTMLPrinter(bytes: Array[Byte])(implicit ctx: Context) extends TastyPrinter(bytes) {
13+
override protected def nameColor(str: String): String = s"<span class='name'>$str</span>"
14+
override protected def treeColor(str: String): String = s"<span class='tree'>$str</span>"
15+
override protected def lengthColor(str: String): String = s"<span class='length'>$str</span>"
16+
}

compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala

Lines changed: 59 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import printing.Highlighting._
1111

1212
class TastyPrinter(bytes: Array[Byte])(implicit ctx: Context) {
1313

14+
private[this] val sb: StringBuilder = new StringBuilder
15+
1416
val unpickler: TastyUnpickler = new TastyUnpickler(bytes)
1517
import unpickler.{nameAtRef, unpickle}
1618

@@ -21,41 +23,56 @@ class TastyPrinter(bytes: Array[Byte])(implicit ctx: Context) {
2123
def printNames(): Unit =
2224
for ((name, idx) <- nameAtRef.contents.zipWithIndex) {
2325
val index = nameColor("%4d".format(idx))
24-
println(index + ": " + nameToString(name))
26+
sb.append(index).append(": ").append(nameToString(name)).append("\n")
2527
}
2628

27-
def printContents(): Unit = {
28-
println("Names:")
29+
def printContents(): String = {
30+
sb.append("Names:\n")
2931
printNames()
30-
println()
31-
println("Trees:")
32-
unpickle(new TreeSectionUnpickler)
33-
unpickle(new PositionSectionUnpickler)
34-
unpickle(new CommentSectionUnpickler)
32+
sb.append("\n")
33+
sb.append("Trees:\n")
34+
unpickle(new TreeSectionUnpickler) match {
35+
case Some(s) => sb.append(s)
36+
case _ => Unit
37+
}
38+
sb.append("\n\n")
39+
unpickle(new PositionSectionUnpickler) match {
40+
case Some(s) => sb.append(s)
41+
case _ => Unit
42+
}
43+
sb.append("\n\n")
44+
unpickle(new CommentSectionUnpickler) match {
45+
case Some(s) => sb.append(s)
46+
case _ => Unit
47+
}
48+
sb.result
3549
}
3650

37-
class TreeSectionUnpickler extends SectionUnpickler[Unit](TreePickler.sectionName) {
51+
class TreeSectionUnpickler extends SectionUnpickler[String](TreePickler.sectionName) {
3852
import TastyFormat._
39-
def unpickle(reader: TastyReader, tastyName: NameTable): Unit = {
53+
54+
private[this] val sb: StringBuilder = new StringBuilder
55+
56+
def unpickle(reader: TastyReader, tastyName: NameTable): String = {
4057
import reader._
4158
var indent = 0
4259
def newLine() = {
4360
val length = treeColor("%5d".format(index(currentAddr) - index(startAddr)))
44-
print(s"\n $length:" + " " * indent)
61+
sb.append(s"\n $length:" + " " * indent)
4562
}
46-
def printNat() = print(Yellow(" " + readNat()).show)
63+
def printNat() = sb.append(treeColor(" " + readNat()))
4764
def printName() = {
4865
val idx = readNat()
49-
print(nameColor(" " + idx + " [" + nameRefToString(NameRef(idx)) + "]"))
66+
sb.append(nameColor(" " + idx + " [" + nameRefToString(NameRef(idx)) + "]"))
5067
}
5168
def printTree(): Unit = {
5269
newLine()
5370
val tag = readByte()
54-
print(" ");print(astTagToString(tag))
71+
sb.append(" ").append(astTagToString(tag))
5572
indent += 2
5673
if (tag >= firstLengthTreeTag) {
5774
val len = readNat()
58-
print(s"(${lengthColor(len.toString)})")
75+
sb.append(s"(${lengthColor(len.toString)})")
5976
val end = currentAddr + len
6077
def printTrees() = until(end)(printTree())
6178
tag match {
@@ -76,7 +93,7 @@ class TastyPrinter(bytes: Array[Byte])(implicit ctx: Context) {
7693
printTrees()
7794
}
7895
if (currentAddr != end) {
79-
println(s"incomplete read, current = $currentAddr, end = $end")
96+
sb.append(s"incomplete read, current = $currentAddr, end = $end\n")
8097
goto(end)
8198
}
8299
}
@@ -96,42 +113,51 @@ class TastyPrinter(bytes: Array[Byte])(implicit ctx: Context) {
96113
}
97114
indent -= 2
98115
}
99-
println(i"start = ${reader.startAddr}, base = $base, current = $currentAddr, end = $endAddr")
100-
println(s"${endAddr.index - startAddr.index} bytes of AST, base = $currentAddr")
116+
sb.append(i"start = ${reader.startAddr}, base = $base, current = $currentAddr, end = $endAddr\n")
117+
sb.append(s"${endAddr.index - startAddr.index} bytes of AST, base = $currentAddr\n")
101118
while (!isAtEnd) {
102119
printTree()
103120
newLine()
104121
}
122+
sb.result
105123
}
106124
}
107125

108-
class PositionSectionUnpickler extends SectionUnpickler[Unit]("Positions") {
109-
def unpickle(reader: TastyReader, tastyName: NameTable): Unit = {
110-
print(s" ${reader.endAddr.index - reader.currentAddr.index}")
126+
class PositionSectionUnpickler extends SectionUnpickler[String]("Positions") {
127+
128+
private[this] val sb: StringBuilder = new StringBuilder
129+
130+
def unpickle(reader: TastyReader, tastyName: NameTable): String = {
131+
sb.append(s" ${reader.endAddr.index - reader.currentAddr.index}")
111132
val positions = new PositionUnpickler(reader).positions
112-
println(s" position bytes:")
133+
sb.append(s" position bytes:\n")
113134
val sorted = positions.toSeq.sortBy(_._1.index)
114135
for ((addr, pos) <- sorted) {
115-
print(treeColor("%10d".format(addr.index)))
116-
println(s": ${offsetToInt(pos.start)} .. ${pos.end}")
136+
sb.append(treeColor("%10d".format(addr.index)))
137+
sb.append(s": ${offsetToInt(pos.start)} .. ${pos.end}\n")
117138
}
139+
sb.result
118140
}
119141
}
120142

121-
class CommentSectionUnpickler extends SectionUnpickler[Unit]("Comments") {
122-
def unpickle(reader: TastyReader, tastyName: NameTable): Unit = {
123-
print(s" ${reader.endAddr.index - reader.currentAddr.index}")
143+
class CommentSectionUnpickler extends SectionUnpickler[String]("Comments") {
144+
145+
private[this] val sb: StringBuilder = new StringBuilder
146+
147+
def unpickle(reader: TastyReader, tastyName: NameTable): String = {
148+
sb.append(s" ${reader.endAddr.index - reader.currentAddr.index}")
124149
val comments = new CommentUnpickler(reader).comments
125-
println(s" comment bytes:")
150+
sb.append(s" comment bytes:\n")
126151
val sorted = comments.toSeq.sortBy(_._1.index)
127152
for ((addr, cmt) <- sorted) {
128-
print(treeColor("%10d".format(addr.index)))
129-
println(s": ${cmt.raw} (expanded = ${cmt.isExpanded})")
153+
sb.append(treeColor("%10d".format(addr.index)))
154+
sb.append(s": ${cmt.raw} (expanded = ${cmt.isExpanded})\n")
130155
}
156+
sb.result
131157
}
132158
}
133159

134-
private def nameColor(str: String): String = Magenta(str).show
135-
private def treeColor(str: String): String = Yellow(str).show
136-
private def lengthColor(str: String): String = Cyan(str).show
160+
protected def nameColor(str: String): String = Magenta(str).show
161+
protected def treeColor(str: String): String = Yellow(str).show
162+
protected def lengthColor(str: String): String = Cyan(str).show
137163
}

compiler/src/dotty/tools/dotc/decompiler/DecompilationPrinter.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class DecompilationPrinter extends Phase {
3939
private def printToOutput(out: PrintStream)(implicit ctx: Context): Unit = {
4040
val unit = ctx.compilationUnit
4141
if (ctx.settings.printTasty.value) {
42-
new TastyPrinter(unit.pickled.head._2).printContents()
42+
println(new TastyPrinter(unit.pickled.head._2).printContents())
4343
} else {
4444
val unitFile = unit.source.toString.replace("\\", "/").replace(".class", ".tasty")
4545
out.println(s"/** Decompiled from $unitFile */")
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package dotty.tools
2+
package dotc
3+
package decompiler
4+
5+
import dotty.tools.dotc.core.Contexts._
6+
import dotty.tools.dotc.core._
7+
import dotty.tools.dotc.core.tasty.TastyHTMLPrinter
8+
import dotty.tools.dotc.reporting._
9+
import dotty.tools.dotc.tastyreflect.ReflectionImpl
10+
11+
/**
12+
* Decompiler to be used with IDEs
13+
*/
14+
class IDEDecompilerDriver(val settings: List[String]) extends dotc.Driver {
15+
16+
private val myInitCtx: Context = {
17+
val rootCtx = initCtx.fresh.addMode(Mode.Interactive).addMode(Mode.ReadPositions).addMode(Mode.ReadComments)
18+
rootCtx.setSetting(rootCtx.settings.YretainTrees, true)
19+
rootCtx.setSetting(rootCtx.settings.fromTasty, true)
20+
val ctx = setup(settings.toArray :+ "dummy.scala", rootCtx)._2
21+
ctx.initialize()(ctx)
22+
ctx
23+
}
24+
25+
private val decompiler = new PartialTASTYDecompiler
26+
27+
def run(className: String): (String, String) = {
28+
val reporter = new StoreReporter(null) with HideNonSensicalMessages
29+
30+
val run = decompiler.newRun(myInitCtx.fresh.setReporter(reporter))
31+
32+
implicit val ctx = run.runContext
33+
34+
run.compile(List(className))
35+
run.printSummary()
36+
val unit = ctx.run.units.head
37+
38+
val decompiled = new ReflectionImpl(ctx).showSourceCode.showTree(unit.tpdTree)
39+
val tree = new TastyHTMLPrinter(unit.pickled.head._2).printContents()
40+
41+
reporter.removeBufferedMessages.foreach(message => System.err.println(message))
42+
(tree, decompiled)
43+
}
44+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package dotty.tools.dotc.decompiler
2+
3+
import dotty.tools.dotc.core.Phases.Phase
4+
5+
/** Partial TASTYDecompiler that doesn't execute the backendPhases
6+
* allowing to control decompiler output by manually running it
7+
* on the CompilationUnits
8+
*/
9+
class PartialTASTYDecompiler extends TASTYDecompiler {
10+
override protected def backendPhases: List[List[Phase]] = Nil
11+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package dotty.tools.languageserver
2+
3+
/**
4+
* A `LanguageClient` that regroups all language server features
5+
*/
6+
trait DottyClient extends worksheet.WorksheetClient

language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,13 @@ import reporting._, reporting.diagnostic.{Message, MessageContainer, messages}
2626
import typer.Typer
2727
import util.{Set => _, _}
2828
import interactive._, interactive.InteractiveDriver._
29+
import decompiler.IDEDecompilerDriver
2930
import Interactive.Include
3031
import config.Printers.interactiv
3132

3233
import languageserver.config.ProjectConfig
33-
import languageserver.worksheet.{Worksheet, WorksheetClient, WorksheetService}
34+
import languageserver.worksheet.{Worksheet, WorksheetService}
35+
import languageserver.decompiler.{TastyDecompilerService}
3436

3537
import lsp4j.services._
3638

@@ -43,7 +45,7 @@ import lsp4j.services._
4345
* - This implementation is based on the LSP4J library: https://github.com/eclipse/lsp4j
4446
*/
4547
class DottyLanguageServer extends LanguageServer
46-
with TextDocumentService with WorkspaceService with WorksheetService { thisServer =>
48+
with TextDocumentService with WorkspaceService with WorksheetService with TastyDecompilerService { thisServer =>
4749
import ast.tpd._
4850

4951
import DottyLanguageServer._
@@ -56,8 +58,8 @@ class DottyLanguageServer extends LanguageServer
5658

5759
private[this] var rootUri: String = _
5860

59-
private[this] var myClient: WorksheetClient = _
60-
def client: WorksheetClient = myClient
61+
private[this] var myClient: DottyClient = _
62+
def client: DottyClient = myClient
6163

6264
private[this] var myDrivers: mutable.Map[ProjectConfig, InteractiveDriver] = _
6365

@@ -128,6 +130,25 @@ class DottyLanguageServer extends LanguageServer
128130
drivers(configFor(uri))
129131
}
130132

133+
/** The driver instance responsible for decompiling `uri` in `classPath` */
134+
def decompilerDriverFor(uri: URI, classPath: String): IDEDecompilerDriver = thisServer.synchronized {
135+
val config = configFor(uri)
136+
val defaultFlags = List("-color:never")
137+
138+
implicit class updateDeco(ss: List[String]) {
139+
def update(pathKind: String, pathInfo: String) = {
140+
val idx = ss.indexOf(pathKind)
141+
val ss1 = if (idx >= 0) ss.take(idx) ++ ss.drop(idx + 2) else ss
142+
ss1 ++ List(pathKind, pathInfo)
143+
}
144+
}
145+
val settings =
146+
defaultFlags ++
147+
config.compilerArguments.toList
148+
.update("-classpath", (classPath +: config.dependencyClasspath).mkString(File.pathSeparator))
149+
new IDEDecompilerDriver(settings)
150+
}
151+
131152
/** A mapping from project `p` to the set of projects that transitively depend on `p`. */
132153
def dependentProjects: Map[ProjectConfig, Set[ProjectConfig]] = thisServer.synchronized {
133154
if (myDependentProjects == null) {
@@ -148,7 +169,7 @@ class DottyLanguageServer extends LanguageServer
148169
myDependentProjects
149170
}
150171

151-
def connect(client: WorksheetClient): Unit = {
172+
def connect(client: DottyClient): Unit = {
152173
myClient = client
153174
}
154175

@@ -184,7 +205,8 @@ class DottyLanguageServer extends LanguageServer
184205
rootUri = params.getRootUri
185206
assert(rootUri != null)
186207

187-
class DottyServerCapabilities(val worksheetRunProvider: Boolean = true) extends lsp4j.ServerCapabilities
208+
class DottyServerCapabilities(val worksheetRunProvider: Boolean = true,
209+
val tastyDecompiler: Boolean = true) extends lsp4j.ServerCapabilities
188210

189211
val c = new DottyServerCapabilities
190212
c.setTextDocumentSync(TextDocumentSyncKind.Full)

language-server/src/dotty/tools/languageserver/Main.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,9 @@ object Main {
6767

6868
println("Starting server")
6969
val launcher =
70-
new Launcher.Builder[worksheet.WorksheetClient]()
70+
new Launcher.Builder[DottyClient]()
7171
.setLocalService(server)
72-
.setRemoteInterface(classOf[worksheet.WorksheetClient])
72+
.setRemoteInterface(classOf[DottyClient])
7373
.setInput(in)
7474
.setOutput(out)
7575
// For debugging JSON messages:
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package dotty.tools.languageserver.decompiler
2+
3+
import org.eclipse.lsp4j.TextDocumentIdentifier
4+
5+
// All case classes in this file should have zero-parameters secondary
6+
// constructors to allow Gson to reflectively create instances on
7+
// deserialization without relying on sun.misc.Unsafe.
8+
9+
/** The parameter for the `tasty/decompile` request. */
10+
case class TastyDecompileParams(textDocument: TextDocumentIdentifier) {
11+
def this() = this(null)
12+
}
13+
14+
/** The response to a `tasty/decompile` request. */
15+
case class TastyDecompileResult(tastyTree: String = null, scala: String = null, error: Int = 0) {
16+
def this() = this(null, null, 0)
17+
}
18+
19+
object TastyDecompileResult {
20+
val ErrorTastyVersion = 1
21+
val ErrorClassNotFound = 2
22+
val ErrorOther = -1
23+
}

0 commit comments

Comments
 (0)