4.2 代码结构
4.2.1 入口简介
在前言中提到 Go 的编译器团队当前正在重写类型检查器,虽然新版本的代码已经合并到 master 分支,但当前默认仍旧使用的是老版本的类型检查器,新的类型检查器可以通过编译参数 "-G=2" 使用,并且应该会随着泛型的发布而默认启用,因此本文将完全基于新代码进行分析。
老的类型检查代码的入口位置与新的代码位置邻近,相信对老系统感兴趣的同学在阅读完本章之后,也能够无障碍地找到对应的实现,所以本文将不会对老代码做特别的说明。
代码入口
类型检查的输入是语法分析的输出 --- AST,其代码入口位置就在语法解析之后,同在$GCROOT/compile/internal/noder/noder.go
文件内的函数LoadPackage()
中。解析器处理完语法解析的错误信息之后,随即进行类型检查:
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 全局作用域
最后更新于