4.5.3-1.3a 总体介绍

类型检查分为两个阶段,第一阶段由方法check.packageObjects()展开,通过递归下降的方式对 check.objMap 中的对象进行类型检查;第二阶段由方法check.processDelayed()处理,之所以需要该步骤是因为某些逻辑需要在全局申明的类型检查结束后才能进行,例如检查类型合法性(是否有环形依赖)、对函数体进行检查等。

check.packageObjects()是类型检查的入口,其定义在$GCROOT/compile/internal/types2/resolver.go中,其逻辑很简单:将 check.objMap 中的 Object 对象按照申明的顺序排序,然后先对非类型别名(通过 type A = B 这种形式申明的类型)的对象进行类型检查,再对类型别名的对象进行类型检查。其代码如下:

func (check *Checker) packageObjects() {
	// 将 Object 对象按照申明的顺序排序
	objList := make([]Object, len(check.objMap))
	i := 0
	for obj := range check.objMap {
		objList[i] = obj
		i++
	}
	sort.Sort(inSourceOrder(objList))

	// 对于已经完成类型检查的对象,收集该对象的方法。因为 check.Files 可能被调用多次,所以第一次调用时该 for 循环不会有任何作用
	for _, obj := range objList {
		if obj, _ := obj.(*TypeName); obj != nil && obj.typ != nil {
			check.collectMethods(obj)
		}
	}

	var aliasList []*TypeName
	// 步骤一
	for _, obj := range objList {
		if tname, _ := obj.(*TypeName); tname != nil && check.objMap[tname].tdecl.Alias {
			// 如果是类型别名,则收集起来在步骤二中处理
			aliasList = append(aliasList, tname)
			continue
		}

		// 对非类型别名的对象进行类型检查
		check.objDecl(obj, nil)
	}
	// 步骤二:对类型别名进行类型检查
	for _, obj := range aliasList {
		check.objDecl(obj, nil)
	}

	check.methods = nil // 释放内存
}

对于每个 Object 对象都会调用方法check.objDecl()进行类型检查,该方法也是类型检查真正开始的地方。根据之前的分析,我们知道每个 Object 对象都与 AST 中的一个 Node 对应,所以从根本上来说, check.objDecl()将会对该 Node 的所有子节点进行类型检查,而在对子节点进行检查的过程中,可能又会递归调用到check.objDecl(),下图描述了该图的一个大概调用栈:

check.objDecl() 会根据 Object 对象的不同类别调用对应的方法进行检查,其中右边方框表示的是类型检查的底层方法,这类方法非常多,基本上对于每类 AST 节点都有一个,这里只罗列了核心的几个,事实上整个$GCROOT/compile/internal/types2/目录下绝大多数逻辑都可以包含在该方框内。

接下来我们对核心逻辑的处理思路进行单独分析。

最后更新于