TASTy 概述

语言
此文档页面特定于 Scala 3,并且可能涵盖 Scala 2 中不可用的新概念。除非另有说明,此页面中的所有代码示例均假定你使用的是 Scala 3。

如果你创建一个名为 Hello.scala 的 Scala 3 源代码文件

@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 文件包含的信息比在 JVM 上运行而生成的 .class 文件多得多。(稍后会详细介绍。)

在 Scala 3 中,编译过程如下所示

         +-------------+    +-------------+    +-------------+
$ scalac | Hello.scala | -> | Hello.tasty | -> | Hello.class |
         +-------------+    +-------------+    +-------------+
                ^                  ^                  ^
                |                  |                  |
           Your source        TASTy file         Class file
               code           for scalac        for the JVM
                              (contains         (incomplete
                               complete         information)
                             information)

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

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

.class 文件的问题

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

类型擦除 意味着当您编写如下 Scala 代码时,该代码应在 JVM 上运行

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

该代码被编译为一个 .class 文件,该文件需要与 JVM 兼容。由于该兼容性要求,该类文件中的代码(您可以使用 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 构造,都有类似的问题,包括联合、交集、带参数的特征以及更多 Scala 3 特性。

TASTy 的救援

因此,TASTy 格式在类型检查后存储了完整的抽象语法树 (AST),而不是在 .class 文件中不包含有关原始类型的任何信息,或仅包含公共 API(如 Scala 2.13 “Pickle” 格式)。存储整个 AST 具有很多优势:它支持独立编译、为不同的 JVM 版本重新编译、对程序进行静态分析,以及更多功能。

要点

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

第二个要点是要理解在 编译时运行时 可用的信息之间存在差异

  • 编译时,当 scalac 读取并分析你的代码时,它知道 xsList[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 语言服务器协议 的语言服务器使用它来支持超链接、命令补全、文档,以及全局操作,如查找引用和重命名。
  • Tasty 为新一代 基于反射的宏奠定了良好的基础。
  • 优化器和分析器可以使用它进行深度代码分析和高级代码生成。

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

更多信息

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

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

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

TASTyViz 是一个用于直观检查 TASTy 文件的工具。在撰写本文时,它仍处于开发的早期阶段,因此你可能会遇到缺失的功能和不太理想的用户体验,但它在调试时仍然可能很有用。你可以在 此处 查看它。

此页面的贡献者