4.4.5 类型检查器

与类型检查器相关的重要数据结构有三个,本节将分析其代码

4.4.5.1. Config

Config用来配置类型检查器,定义在$GCROOT/compile/internal/types2/api.go中:

type Config struct {
	// GoVersion describes the accepted Go language version. The string
	// must follow the format "go%d.%d" (e.g. "go1.12") or ist must be
	// empty; an empty string indicates the latest language version.
	// If the format is invalid, invoking the type checker will cause a
	// panic.
	GoVersion string

	// If IgnoreFuncBodies is set, function bodies are not
	// type-checked.
	IgnoreFuncBodies bool

	// If FakeImportC is set, `import "C"` (for packages requiring Cgo)
	// declares an empty "C" package and errors are omitted for qualified
	// identifiers referring to package C (which won't find an object).
	// This feature is intended for the standard library cmd/api tool.
	//
	// Caution: Effects may be unpredictable due to follow-on errors.
	//          Do not use casually!
	FakeImportC bool

	// If IgnoreLabels is set, correct label use is not checked.
	// TODO(gri) Consolidate label checking and remove this flag.
	IgnoreLabels bool

	// If CompilerErrorMessages is set, errors are reported using
	// cmd/compile error strings to match $GOROOT/test errors.
	// TODO(gri) Consolidate error messages and remove this flag.
	CompilerErrorMessages bool

	// If go115UsesCgo is set, the type checker expects the
	// _cgo_gotypes.go file generated by running cmd/cgo to be
	// provided as a package source file. Qualified identifiers
	// referring to package C will be resolved to cgo-provided
	// declarations within _cgo_gotypes.go.
	//
	// It is an error to set both FakeImportC and go115UsesCgo.
	go115UsesCgo bool

	// If Trace is set, a debug trace is printed to stdout.
	Trace bool

	// If Error != nil, it is called with each error found
	// during type checking; err has dynamic type Error.
	// Secondary errors (for instance, to enumerate all types
	// involved in an invalid recursive type declaration) have
	// error strings that start with a '\t' character.
	// If Error == nil, type-checking stops with the first
	// error found.
	Error func(err error)

	// An importer is used to import packages referred to from
	// import declarations.
	// If the installed importer implements ImporterFrom, the type
	// checker calls ImportFrom instead of Import.
	// The type checker reports an error if an importer is needed
	// but none was installed.
	Importer Importer

	// If Sizes != nil, it provides the sizing functions for package unsafe.
	// Otherwise SizesFor("gc", "amd64") is used instead.
	Sizes Sizes

	// If DisableUnusedImportCheck is set, packages are not checked
	// for unused imports.
	DisableUnusedImportCheck bool
}

其中各个字段的注释写得非常详细,基本都是一些开关属性。比较重要的是Importer以及Sizes, 前者是包加载器,而后者负责处理各类型的对齐问题。

4.4.5.2. Info

Info用来存放类型检查器的检查结果,定义在$GCROOT/compile/internal/types2/api.go中:

// Info holds result type information for a type-checked package.
// Only the information for which a map is provided is collected.
// If the package has type errors, the collected information may
// be incomplete.
type Info struct {
	// Types maps expressions to their types, and for constant
	// expressions, also their values. Invalid expressions are
	// omitted.
	//
	// For (possibly parenthesized) identifiers denoting built-in
	// functions, the recorded signatures are call-site specific:
	// if the call result is not a constant, the recorded type is
	// an argument-specific signature. Otherwise, the recorded type
	// is invalid.
	//
	// The Types map does not record the type of every identifier,
	// only those that appear where an arbitrary expression is
	// permitted. For instance, the identifier f in a selector
	// expression x.f is found only in the Selections map, the
	// identifier z in a variable declaration 'var z int' is found
	// only in the Defs map, and identifiers denoting packages in
	// qualified identifiers are collected in the Uses map.
	Types map[syntax.Expr]TypeAndValue

	// Inferred maps calls of parameterized functions that use
	// type inference to the inferred type arguments and signature
	// of the function called. The recorded "call" expression may be
	// an *ast.CallExpr (as in f(x)), or an *ast.IndexExpr (s in f[T]).
	Inferred map[syntax.Expr]Inferred

	// Defs maps identifiers to the objects they define (including
	// package names, dots "." of dot-imports, and blank "_" identifiers).
	// For identifiers that do not denote objects (e.g., the package name
	// in package clauses, or symbolic variables t in t := x.(type) of
	// type switch headers), the corresponding objects are nil.
	//
	// For an embedded field, Defs returns the field *Var it defines.
	//
	// Invariant: Defs[id] == nil || Defs[id].Pos() == id.Pos()
	Defs map[*syntax.Name]Object

	// Uses maps identifiers to the objects they denote.
	//
	// For an embedded field, Uses returns the *TypeName it denotes.
	//
	// Invariant: Uses[id].Pos() != id.Pos()
	Uses map[*syntax.Name]Object

	// Implicits maps nodes to their implicitly declared objects, if any.
	// The following node and object types may appear:
	//
	//     node               declared object
	//
	//     *syntax.ImportDecl    *PkgName for imports without renames
	//     *syntax.CaseClause    type-specific *Var for each type switch case clause (incl. default)
	//     *syntax.Field         anonymous parameter *Var (incl. unnamed results)
	//
	Implicits map[syntax.Node]Object

	// Selections maps selector expressions (excluding qualified identifiers)
	// to their corresponding selections.
	Selections map[*syntax.SelectorExpr]*Selection

	// Scopes maps syntax.Nodes to the scopes they define. Package scopes are not
	// associated with a specific node but with all files belonging to a package.
	// Thus, the package scope can be found in the type-checked Package object.
	// Scopes nest, with the Universe scope being the outermost scope, enclosing
	// the package scope, which contains (one or more) files scopes, which enclose
	// function scopes which in turn enclose statement and function literal scopes.
	// Note that even though package-level functions are declared in the package
	// scope, the function scopes are embedded in the file scope of the file
	// containing the function declaration.
	//
	// The following node types may appear in Scopes:
	//
	//     *syntax.File
	//     *syntax.FuncType
	//     *syntax.BlockStmt
	//     *syntax.IfStmt
	//     *syntax.SwitchStmt
	//     *syntax.CaseClause
	//     *syntax.CommClause
	//     *syntax.ForStmt
	//
	Scopes map[syntax.Node]*Scope

	// InitOrder is the list of package-level initializers in the order in which
	// they must be executed. Initializers referring to variables related by an
	// initialization dependency appear in topological order, the others appear
	// in source order. Variables without an initialization expression do not
	// appear in this list.
	InitOrder []*Initializer
}

Initializer表示的是当前 package 的初始化顺序,其余的所有属性都是一个以 AST 节点为 key 的 map, 类型检查器会在类型检查的过程中收集对应的对象,Info 包含了所有类型检查的结果,其会在后续生成 IR Tree 时使用。其中涉及到的TypeAndValue以及Inferred都在本文件中定义。

4.4.5.3. Checker

类型检查的整体逻辑由Checker驱动,其定义在文件$GCROOT/compile/internal/types2/check.go中:

type Checker struct {
	conf    *Config
	pkg     *Package                    // 当前正在编译的包
	*Info                               // 保存类型检查的结果
	version version                     // 支持的语言版本,例如 go1.16
	nextId  uint64                      // 泛型的结构 TypeParam 包含唯一 id, 通过该字段来自增生成
	objMap  map[Object]*declInfo        // maps package-level objects and (non-interface) methods to declaration info
	impMap  map[importKey]*Package      // maps (import path, source directory) to (complete or fake) package
	posMap  map[*Interface][]syntax.Pos // maps interface types to lists of embedded interface positions
	typMap  map[string]*Named           // maps an instantiated named type hash to a *Named type
	pkgCnt  map[string]int              // counts number of imported packages with a given name (for better error messages)

	// information collected during type-checking of a set of package files
	// (initialized by Files, valid only for the duration of check.Files;
	// maps and lists are allocated on demand)
	files        []*syntax.File            // list of package files
	imports      []*PkgName                // list of imported packages
	dotImportMap map[dotImportKey]*PkgName // maps dot-imported objects to the package they were dot-imported through

	firstErr error                    // first error encountered
	methods  map[*TypeName][]*Func    // 保存类型到其方法的映射
	untyped  map[syntax.Expr]exprInfo // map of expressions without final type
	delayed  []func()                 // stack of delayed action segments; segments are processed in FIFO order
	finals   []func()                 // list of final actions; processed at the end of type-checking the current set of files
	objPath  []Object                 // path of object dependencies during type inference (for cycle reporting)

	// context within which the current object is type-checked
	// (valid only for the duration of type-checking a specific object)
	context // 用来存放当前正在进行类型检查对象的上下文

	// debugging
	indent int // indentation for tracing
}

Checker定义了非常多的方法,几乎包含了所有的类型检查逻辑,而其属性封装了类型检查需要的各种数据结构,包括用于存放各种中间状态的属性,其中有几个属性还需要单独介绍一下:

  • declInfo

该结构用来封装 package-level 的顶级申明,与 AST 中的“申明(Declaration)”对应。其定义在文件$GCROOT/compile/internal/types2/resolver.go中:

type declInfo struct {
    file      *Scope           // scope of file containing this declaration
    lhs       []*Var           // lhs of n:1 variable declarations, or nil
    vtyp      syntax.Expr      // type, or nil (for const and var declarations only)
    init      syntax.Expr      // init/orig expression, or nil (for const and var declarations only)
    inherited bool             // if set, the init expression is inherited from a previous constant declaration
    tdecl     *syntax.TypeDecl // type declaration, or nil
    fdecl     *syntax.FuncDecl // func declaration, or nil

    // 用来保存当前 declInfo 所依赖的对象,用来构建初始化依赖图
    deps map[Object]bool // lazily initialized
}

其内部封装了一个申明的各个语法树节点,类型检查主要围绕这个结构体展开。其中deps属性用来保存当前 declInfo 所依赖的对象,用于构建初始化顺序

  • delayed & finals

类型检查是分阶段进行的,有些模块的工作需要依赖其他部分的检查结果,这两个属性用来保存该类工作,所以其是两个函数列表。该属性在类型检查逻辑中会有使用。

最后更新于