func (check *Checker) collectObjects() {
type methodInfo struct {
obj *Func // method
ptr bool // true if pointer receiver
recv *syntax.Name // receiver type name
}
var methods []methodInfo // collected methods with valid receivers and non-blank _ names
var fileScopes []*Scope
// 1. 处理 AST 中的顶级申明,创建 Object 与 declInfo 对象
for fileNo, file := range check.files {
// 创建文件作用域
fileScope := NewScope(check.pkg.scope, startPos(file), endPos(file), check.filename(fileNo))
fileScopes = append(fileScopes, fileScope)
check.recordScope(file, fileScope)
for index, decl := range file.DeclList {
switch s := decl.(type) {
case *syntax.ImportDecl:
// 通过包加载器将引用的包加载进来
imp := check.importPackage(s.Path.Pos(), path, fileDir)
pkgName := NewPkgName(s.Pos(), pkg, name, imp)
if s.LocalPkgName != nil {
// 如果是 import db "runtime/debug", 则将 LocalPkgName, 即这里的包别名 db, 注册到 info.Defs 中
check.recordDef(s.LocalPkgName, pkgName)
} else {
check.recordImplicit(s, pkgName)
}
check.imports = append(check.imports, pkgName)
if name == "." {
// 如果是 import . "xxxxx", 则将包 xxxxx 中的 public 符号导入当前文件作用域
if check.dotImportMap == nil {
check.dotImportMap = make(map[dotImportKey]*PkgName)
}
for _, obj := range imp.scope.elems {
if obj.Exported() {
fileScope.Insert(obj)
check.dotImportMap[dotImportKey{fileScope, obj}] = pkgName
}
}
} else {
// 将包对象插入当前的文件作用域
check.declare(fileScope, nil, pkgName, nopos)
}
case *syntax.ConstDecl:
// 得到常量的初始化表达式列表
values := unpackExpr(last.Values)
for i, name := range s.NameList {
// 创建常量的 Object 对象
obj := NewConst(name.Pos(), pkg, name.Value, nil, iota) // iota 表示当前 ConstDecl 在当前常量组内的索引。常量组表示用括号括起来的一组常量申明
var init syntax.Expr
if i < len(values) {
init = values[i]
}
// 创建常量对应的 declInfo 对象
d := &declInfo{file: fileScope, vtyp: last.Type, init: init, inherited: inherited}
// 将 obj 放入包作用域,并保存 obj -> d 的映射关系到 check.objMap 中
check.declarePkgObj(name, obj, d)
}
// arity 方法来用判断常量名称(s.NameList)与初始化表达式(values)的个数是否相等
check.arity(s.Pos(), s.NameList, values, true, inherited)
case *syntax.VarDecl:
// lhs - left hand side, 用来对应变量申明中左边的变量名列表,这里会为每个变量创建一个 Object 对象。
// 与变量申明不同的是,变量的初始化表达式的个数有两种情况:
// 1. 数目与 lhs 变量名个数一样。此时每个变量的 Object 对象都对应自己的初始化表达式的 declInfo 对象
// 2. 一个表达式,返回值的个数与 lhs 变量名个数一样。此时所有的 Object 对象都复用同一个 declInfo 对象
lhs := make([]*Var, len(s.NameList))
// 所以如果初始化表达式 s.Values 不是一个列表的话,则创建一个所有变量 Object 复用的 declInfo 对象
var d1 *declInfo
if _, ok := s.Values.(*syntax.ListExpr); !ok {
d1 = &declInfo{file: fileScope, lhs: lhs, vtyp: s.Type, init: s.Values}
}
values := unpackExpr(s.Values) // 尝试着将初始化表达式转换为列表
for i, name := range s.NameList {
// 为每个变量创建 Object 对象
obj := NewVar(name.Pos(), pkg, name.Value, nil)
lhs[i] = obj
d := d1
if d == nil {
// 如果没有复用的 declInfo 对象,则从初始化列表中取出对应的表达式,并创建 declInfo 对象
var init syntax.Expr
if i < len(values) {
init = values[i]
}
d = &declInfo{file: fileScope, vtyp: s.Type, init: init}
}
// 将 obj 放入包作用域,并保存 obj -> d 的映射关系到 check.objMap 中
check.declarePkgObj(name, obj, d)
}
// 如果变量类型为空的话,则必定包含初始化表达式,此时需要校验变量名与初始化表达式的个数是否合理
if s.Type == nil || values != nil {
check.arity(s.Pos(), s.NameList, values, false, false)
}
case *syntax.TypeDecl:
// 对于类型申明,直接创建 TypeName 对象与 declInfo 对象
obj := NewTypeName(s.Name.Pos(), pkg, s.Name.Value, nil)
check.declarePkgObj(s.Name, obj, &declInfo{file: fileScope, tdecl: s})
case *syntax.FuncDecl:
d := s
name := d.Name.Value
obj := NewFunc(d.Name.Pos(), pkg, name, nil) // 创建函数的 Object 对象,函数与方法的申明都对应的是 FuncDecl, 接下来对二者分开处理
if d.Recv == nil {
if name == "init" {
obj.parent = pkg.scope
check.recordDef(d.Name, obj) // 如果是 init 方法,则只存入 info.Defs 中而不插入当前的包作用域内,因为 init 方法不能被其他方法调用
} else {
check.declare(pkg.scope, d.Name, obj, nopos) // 如果是普通方法,则需要插入当前的包作用域内,用于符号解析
}
} else {
ptr, recv, _ := check.unpackRecv(d.Recv.Type, false)
if recv != nil && name != "_" {
methods = append(methods, methodInfo{obj, ptr, recv}) // 在本方法的最后需要将所有的方法与其 Receiver 绑定,所以此处记录一下方法对象
}
check.recordDef(d.Name, obj) // 方法的解析通过 Receiver 来完成,所以只将该对象放入 info.Defs 即可,不能插入包作用域内
}
info := &declInfo{file: fileScope, fdecl: d} // 创建函数对应的 declInfo
check.objMap[obj] = info // 保存 obj -> declInfo 的映射关系
obj.setOrder(uint32(len(check.objMap))) // 用于对方法进行排序
default:
check.errorf(s, invalidAST+"unknown syntax.Decl node %T", s)
}
}
}
// 2. 检查包内的命名冲突
for _, scope := range fileScopes {
for _, obj := range scope.elems {
if alt := pkg.scope.Lookup(obj.Name()); alt != nil {
var err error_
if pkg, ok := obj.(*PkgName); ok {
err.errorf(alt, "%s already declared through import of %s", alt.Name(), pkg.Imported())
err.recordAltDecl(pkg)
} else {
err.errorf(alt, "%s already declared through dot-import of %s", alt.Name(), obj.Pkg())
// TODO(gri) dot-imported objects don't have a position; recordAltDecl won't print anything
err.recordAltDecl(obj)
}
check.report(&err)
}
}
}
// 3. 将所有的方法与其 receiver 绑定起来
if methods != nil {
check.methods = make(map[*TypeName][]*Func)
for i := range methods {
m := &methods[i]
ptr, base := check.resolveBaseTypeName(m.ptr, m.recv) // 解析 receiver 的基础类型
if base != nil {
m.obj.hasPtrRecv = ptr
check.methods[base] = append(check.methods[base], m.obj)
}
}
}
}
这里的代码只是为了展现核心的处理思路,为了节约篇幅,很多细节以及错误检查的部分都删除了。在三个步骤中,最重要的是第一部分:Object 对象与 declInfo 的创建与注册。其中涉及到的几个注册方法的概要逻辑如下:
// id 不能为 nil, 将对象放入 info.Defs 中
func (check *Checker) recordDef(id *syntax.Name, obj Object) {
if m := check.Defs; m != nil {
m[id] = obj
}
}
// 将 obj 插入 scope 中,如果 id 不为 nil, 则同时放入 info.Defs 中
func (check *Checker) declare(scope *Scope, id *syntax.Name, obj Object, pos syntax.Pos) {
if obj.Name() != "_" {
if alt := scope.Insert(obj); alt != nil {
// 忽略错误处理代码
return
}
obj.setScopePos(pos)
}
if id != nil {
check.recordDef(id, obj)
}
}
// 将 obj 注册到包作用域内,并建立 obj -> d 的映射关系
func (check *Checker) declarePkgObj(ident *syntax.Name, obj Object, d *declInfo) {
// 忽略对象名称校验逻辑:对象名称不能为 init, main
check.declare(check.pkg.scope, ident, obj, nopos)
check.objMap[obj] = d
obj.setOrder(uint32(len(check.objMap)))
}
细心的读者可能已经发现:这里只是创建了顶级申明的类型检查对象,但是对于源代码中的每个表达式,或者涉及到类型使用的语句(例如赋值语句 a = foo()),类型检查都是需要的,那么这些地方的类型检查是如何完成的呢?答案是类型检查是通过递归下降的方式进行的,当类型检查器对顶级申明的对象进行类型检查时,其会递归地对当前 AST 节点的所有子节点进行类型检查,这里的逻辑相当于只是类型检查的初始推动力,完成了这里的工作之后,真正的类型检查就开始了。