TASTy 概览

Language

假定你创建了一个 Scala 3 源代码文件叫 Hello.scala:

@main def hello = println("Hello, world")

然后用 scalac 编译了该文件:

$ scalac Hello.scala

你会发现在 scalac 生成的其它文件结果中,有些文件是以 .tasty 为扩展名:

$ ls -1
Hello$package$.class
Hello$package.class
Hello$package.tasty
Hello.scala
hello.class
hello.tasty

这自然地会引出一个问题,“什么是 tasty?”

什么是 TASTy?

TASTy 是从 Typed Abstract Syntax Trees 这个术语的首字母缩写来的。它是 Scala 3 的高级交换格式,在本文档中,我们将它称为 Tasty

首先要知道的是,Tasty 文件是由 scalac 编译器生成的,并且包含 所有 有关源代码的信息,这些信息包括程序的语法结构,以及有关类型,位置甚至文档的 所有 信息。Tasty 文件包含的信息比 .class 文件多得多,后者是为在 JVM 上运行而生成的。(后面有详细介绍)。

在 Scala 3 中,编译流程像这样:

         +-------------+    +-------------+    +-------------+
$ scalac | Hello.scala | -> | Hello.tasty | -> | Hello.class |
         +-------------+    +-------------+    +-------------+
                ^                  ^                  ^
                |                  |                  |
            你的代码          TASTy 文件          Class 文件
                              用于 scalac          用于 JVM
                            (包括完整信息)     (不完整信息)

您可以通过使用 -print-tasty 标志在 .tasty 文件上运行编译器,以人类可读的形式查看 .tasty 文件的内容。 您还可以使用 -decompile 标志以类似于 Scala 源代码的形式查看反编译的内容。

$ scalac -print-tasty hello.tasty
$ scalac -decompile hello.tasty

The issue with .class files

由于象 类型擦除 等问题,.class 文件实际上是代码的不完整表示形式。 演示这一点的一种简单方法是使用 List 示例。

类型擦除 意味着当你编写这样的Scala代码,并假定它是在JVM上运行时:

val xs: List[Int] = List(1, 2, 3)

该代码被编译为需要与 JVM 兼容的 .class 文件。由于该兼容性要求,该类文件中的代码 — 您可以使用 javap 命令看到它,— 最终看起来像这样:

public scala.collection.immutable.List<java.lang.Object> xs();

javap 命令输出显示了类文件中包含的内容,该内容是 Java 的表示形式。请注意,在此输出中,xs 不是 定义为 List[Int] ;它真正表示的是 List[java.lang.Object] 。为了使您的 Scala 代码与 JVM 配合使用,Int 类型已被擦除。

稍后,当您在 Scala 代码中访问 List[Int] 的元素时,像这样:

val x = xs(0)

生成的类文件对此行代码进行强制转换操作,您可以将其想象成:

int x = (Int) xs.get(0)               // Java-ish
val x = xs.get(0).asInstanceOf[Int]   // more Scala-like

同样,这样做是为了兼容性,因此您的 Scala 代码可以在 JVM 上运行。但是,我们已经有的整数列表的信息在类文件中丢失了。 当尝试使用已编译的库来编译 Scala 程序时,会带来问题。为此,我们需要的信息比类文件中通常可用的信息更多。

此讨论仅涵盖类型擦除的主题。对于 JVM 没有意识到的所有其他 Scala 结构,也存在类似的问题,包括 unions, intersections, 带有参数的 traits 以及更多 Scala 3 特性。

TASTy to the Rescue

因此,TASTy 格式不是像 .class 文件那样没有原始类型的信息,或者只有公共 API(如Scala 2.13 “Pickle” 格式),而是在类型检查后存储完整的抽象语法树(AST)。存储整个 AST 有很多优点:它支持单独编译,针对不同的 JVM 版本重新编译,程序的静态分析等等。

重点

因此,这是本节的第一个要点:您在 Scala 代码中指定的类型在 .class 文件中没有完全准确地表示。

第二个关键点是要了解 编译时运行时 提供的信息之间存在差异:

  • 编译时,当 scalac 读取和分析你的代码时,它知道 xs 是一个 List[Int]
  • 当编译器将你的代码写入类文件时,它会写 xsList[Object] ,并在访问 xs 的任何地方添加转换信息
  • 然后在运行时 — 你的代码在 JVM 中运行,— JVM 不知道你的列表是一个 List[Int]

对于 Scala 3 和 Tasty,这里有一个关于编译时的重要说明:

  • 当您编写使用其他 Scala 3 库的 Scala 3 代码时,scalac 不必再读取其 .class 文件;它可以读取其 .tasty 文件,如前所述,这些文件是代码的 准确 表示形式。这对于在 Scala 2.13 和 Scala 3 之间实现单独编译和兼容性非常重要。

Tasty 的好处

可以想象,拥有代码的完整表示形式具有许多好处

  • 编译器使用它来支持单独的编译。
  • Scala 基于 Language Server Protocol 的语言服务器使用它来支持超链接、命令补全、文档以及全局操作,如查找引用和重命名。
  • Tasty 为新一代基于反射的宏奠定了良好的基础。
  • 优化器和分析器可以使用它进行深度代码分析和高级代码生成。

在相关的说明中,Scala 2.13.6 有一个 TASTy 读取器,Scala 3 编译器也可以读取2.13“Pickle”格式。Scala 3 迁移指南中的 类路径兼容性页面 解释了此交叉编译功能的好处。

更多信息

总之,Tasty 是 Scala 3 的高级交换格式,.tasty 文件包含源代码的完整表示形式,从而带来了上一节中概述的好处。

有关更多详细信息,请参阅以下资源:

这些文章提供了有关 Scala 3 宏的更多信息:

Contributors to this page: