4.2 代码结构

4.2.1 入口简介

前言中提到 Go 的编译器团队当前正在重写类型检查器,虽然新版本的代码已经合并到 master 分支,但当前默认仍旧使用的是老版本的类型检查器,新的类型检查器可以通过编译参数 "-G=2" 使用,并且应该会随着泛型的发布而默认启用,因此本文将完全基于新代码进行分析。

老的类型检查代码的入口位置与新的代码位置邻近,相信对老系统感兴趣的同学在阅读完本章之后,也能够无障碍地找到对应的实现,所以本文将不会对老代码做特别的说明。

代码入口

类型检查的输入是语法分析的输出 --- AST,其代码入口位置就在语法解析之后,同在$GCROOT/compile/internal/noder/noder.go文件内的函数LoadPackage()中。解析器处理完语法解析的错误信息之后,随即进行类型检查:

if base.Flag.G != 0 { // 编译器通过参数 -G 开启新类型检查
    // Use types2 to type-check and possibly generate IR.
    check2(noders) // 类型检查入口函数
    return
}

check2 在文件$GCROOT/compile/internal/noder/irgen.go中定义,该函数实际上做了两件事情:类型检查;然后生成 IR(Intermediate Representation) Tree,我们当前仅关注前者,下一章将详细介绍如何生成 IR Tree。

主体代码位置

类型检查的逻辑非常复杂,涉及到的主体代码都在目录$GCROOT/compile/internal/types2下,其中对常量表达式的处理又依赖$GOROOT/src/go/constant$GOROOT/src/go/token

Go 语言通过包$GOROOT/src/go 将词法分析器、语法分析器、包加载器(Importer)以及类型检查器作为库开放了出来,以提供给需要对 go 语言进行分析的第三方工具使用,像gopls, guru等工具都使用该包来对 go 的源代码进行分析,甚至编译器本身也对该库有依赖。

就核心功能而言,该包的功能基本上是编译器完整功能的一个子集,代码也有很大部分的重复,但是因为该包需要保持稳定以及向前兼容,而编译器的开发非常活跃,所以 Go 团队还是保持了两份代码,虽然违背了工程上的一些准则,但从实践上来说维护起来更加简单。

4.2.2 索引文件

新类型检查器的所有代码都在目录$GCROOT/compile/internal/types2/下,各文件及功能简介如下:

  • examples: 泛型使用示例

  • testdata: 各种测试用例,例如各种循环引用、类型检查等

  • api.go: 类型检查器对外提供的重要接口类数据结构及方法,例如 Info, Config, Identical() 等

  • assignments.go: 处理赋值语句的检查工作,检查一个类型的值能够赋给另一个类型;常量、变量的初始化函数也在该文件内

  • builtins.go: 检查对内置函数的调用是否合法

  • call.go: 对函数、方法调用进行类型检查

  • check.go: 整个类型检查的主控逻辑

  • conversions.go: 对类型转换进行类型检查,例如 A(b)

  • decl.go: 对单个 Object 对象进行类型检查的主控逻辑

  • errors.go: 类型检查错误处理逻辑,包括 error 定义与一些通用逻辑定义

  • expr.go: 对表达式进行类型检查

  • infer.go: 根据实际类型对类型参数进行推导

  • initorder.go: 构建初始化顺序

  • labels.go: 对程序中 label 的使用是否正确进行检查

  • lookup.go: 属性、方法查找逻辑,以及判断给定类型是否实现了特定接口

  • object.go: 定义 Object 对象,该对象是类型检查展开的核心数据结构

  • operand.go: 定义数据结构 operand, 该结构用来封装类型检查过程中的操作数。该文件还包括判断一个 operand 能够赋值给指定类型的方法

  • package.go: 定义了 Package 数据结构及辅助方法

  • pos.go: 主要用来计算给定 syntax.Node 在源文件中的位置,包括开始位置及结束位置

  • predicates.go: 定义了很多对类型各种性质进行判断的断言函数,例如 isBoolean(), isOrdered() 等;最重要的是判断两个类型是否等价的 identical() 函数

  • resolver.go: 类型检查的预处理函数及入口函数,基本用于辅助 check.go 中的函数

  • return.go: 判断语句是否包含 return, 是否包含 break 等

  • scope.go: 定义作用域

  • selection.go: 定义并处理形如 X.sel 这类表达式的逻辑

  • stmt.go: 对程序内的语句进行类型检查,例如函数体

  • subst.go: 对包含泛型的类型进行实例化,即根据实际的类型参数创建具体的类型

  • type.go: 定义与所有类型相关的数据结构

  • typexpr.go: 根据类型表达式创建具体类型,例如根据 ~[]int~ 创建出切片类型

  • unify.go: 判断包含泛型的两个类型是否等价

  • universe.go: 初始化 Universe 全局作用域

最后更新于