
3.3 类型推断原理
类型推断依赖编译器的处理能力,编译器执行的过程为:词法解析→语法分析→抽象语法树构建→类型检查→中间代码→代码优化→生成机器码。编译阶段的代码位于go/src/cmd/compile文件中(更详细的过程请查看第1章)。
3.3.1 词法解析与语法分析阶段
在词法解析阶段,会将赋值语句右边的常量解析为一个未定义的类型,例如,ImagLit代表复数,FloatLit代表浮点数,IntLit代表整数。


Go语言源代码采用UTF-8的编码方式,在进行词法解析时,当遇到需要赋值的常量操作时,会逐个读取后面常量的UTF-8字符。字符串的首字符为",数字的首字符为'0'~'9'。具体实现位于syntax.next函数中。

因此对于整数、小数等常量的识别就显得非常简单。如图3-2所示,整数就是字符中全是0~9的数字,浮点数就是字符中有“.”号的数字,字符串的首字符为"或'。

图3-2 词法解析阶段解析未定义的常量示例
下面列出的number函数为语法分析阶段处理数字的具体实现。数字首先会被分为小数部分与整数部分,通过字符.进行区分。如果整数部分是以0开头的,则可能有不同的含义,例如0x代表十六进制数、0b代表二进制数。


以赋值语句a:=333为例,完成词法解析与语法分析时,此赋值语句将以AssignStmt结构表示。

其中Op代表操作符,在这里是赋值操作OAS。Lhs与Rhs分别代表左右两个表达式,左边代表变量a,右边代表常量333,此时其类型为intLit。
3.3.2 抽象语法树生成与类型检查
完成语法解析后,进入抽象语法树阶段。在该阶段会将词法解析阶段生成的AssignStmt结构解析为一个Node,Node结构体是对抽象语法树中节点的抽象。


其中,Left(左节点)代表左边的变量a,Right(右节点)代表整数333,其Op操作为OLITERAL。Right的E接口字段会存储值333,如果前一阶段为IntLit类型,则需要转换为Mpint类型。Mpint类型用于存储整数常量,具体结构如下所示。

从Mpint类型的结构可以看到,在编译时AST阶段整数通过math/big.Int进行高精度存储,浮点数通过big.Float进行高精度存储(关于math/big库,详见第2章)。
在类型检查阶段,右节点中的Type字段存储的类型会变为types.Types[TINT]。types.Types是一个数组(var Types [NTYPE]*Type),存储了不同标识对应的Go语言中的实际类型,其中,types.Types[TINT]对应Go语言内置的int类型。
接着完成最终的赋值操作,并将右边常量的类型赋值给左边变量的类型。具体实现位于typecheckas函数中。

在SSA阶段,变量a中存储的大数类型的333最终会调用big.Int包中的Int64函数并将其转换为int64类型的常量,形如:v4(?)=MOVQconst<int>[333](a[int])。