blob: ee12bef9f867cdf32e30dc8776d3e8747b071d1b [file] [log] [blame] [view]
<!-- Autogenerated by weave; DO NOT EDIT -->
# `go/types`: The Go Type Checker
This document is maintained by Alan Donovan `adonovan@google.com`.
[October 2015 GothamGo talk on go/types](https://docs.google.com/presentation/d/13OvHYozAUBeISPRoLgG7kMBuja1NsU1D_mMlmbaYojk/view)
# Contents
1. [Changes in Go 1.18](#changes-in-go-1.18)
1. [Introduction](#introduction)
1. [An Example](#an-example)
1. [Objects](#objects)
1. [Identifier Resolution](#identifier-resolution)
1. [Scopes](#scopes)
1. [Initialization Order](#initialization-order)
1. [Types](#types)
1. [Basic types](#basic-types)
1. [Simple Composite Types](#simple-composite-types)
1. [Struct Types](#struct-types)
1. [Tuple Types](#tuple-types)
1. [Function and Method Types](#function-and-method-types)
1. [Named Types](#named-types)
1. [Interface Types](#interface-types)
1. [TypeAndValue](#typeandvalue)
1. [Selections](#selections)
1. [Ids](#ids)
1. [Method Sets](#method-sets)
1. [Constants](#constants)
1. [Size and Alignment](#size-and-alignment)
1. [Imports](#imports)
1. [Formatting support](#formatting-support)
1. [Getting from A to B](#getting-from-a-to-b)
# Changes in Go 1.18
Go 1.18 introduces generics, and several corresponding new APIs for `go/types`.
This document is not yet up-to-date for these changes, but a guide to the new
changes exists at
[`x/exp/typeparams/example`](https://github.com/golang/exp/tree/master/typeparams/example).
# Introduction
The [`go/types` package]('https://golang.org/pkg/go/types) is a
type-checker for Go programs, designed by Robert Griesemer.
It became part of Go's standard library in Go 1.5.
Measured by lines of code and by API surface area, it is one of the
most complex packages in Go's standard library, and using it requires
a firm grasp of the structure of Go programs.
This tutorial will help you find your bearings.
It comes with several example programs that you can obtain with `go get` and play with.
We assume you are a proficient Go programmer who wants to build tools
to analyze or manipulate Go programs and that you have some knowledge
of how a typical compiler works.
The type checker complements several existing
standard packages for analyzing Go programs.
We've listed them below.
→ go/types
go/constant
go/parser
go/ast
go/scanner
go/token
Starting at the bottom, the
[`go/token` package](http://golang.org/pkg/go/token)
defines the lexical tokens of Go.
The [`go/scanner` package](http://golang.org/pkg/go/scanner) tokenizes an input stream and records
file position information for use in diagnostics
or for file surgery in a refactoring tool.
The [`go/ast` package](http://golang.org/pkg/go/ast)
defines the data types of the abstract syntax tree (AST).
The [`go/parser` package](http://golang.org/pkg/go/parser)
provides a robust recursive-descent parser that constructs the AST.
And [`go/constant`](http://golang.org/pkg/go/constant)
provides representations and arithmetic operations for the values of compile-time
constant expressions, as we'll see in
[Constants](#constants).
The [`golang.org/x/tools/go/loader` package](https://pkg.go.dev/golang.org/x/tools/go/loader)
from the `x/tools` repository is a client of the type
checker that loads, parses, and type-checks a complete Go program from
source code.
We use it in some of our examples and you may find it useful too.
The Go type checker does three main things.
First, for every name in the program, it determines which declaration
the name refers to; this is known as _identifier resolution_.
Second, for every expression in the program, it determines what type
that expression has, or reports an error if the expression has no
type, or has an inappropriate type for its context; this is known as
_type deduction_.
Third, for every constant expression in the program, it determines the
value of that constant; this is known as _constant evaluation_.
Superficially, it appears that these three processes could be done
sequentially, in the order above, but perhaps surprisingly, they must
be done together.
For example, the value of a constant may depend on the type of an
expression due to operators like `unsafe.Sizeof`.
Conversely, the type of an expression may depend on the value of a
constant, since array types contain constants.
As a result, type deduction and constant evaluation must be done
together.
As another example, we cannot resolve the identifier `k` in the composite
literal `T{k: 0}` until we know whether `T` is a struct type.
If it is, then `k` must be found among `T`'s fields.
If not, then `k` is an ordinary reference
to a constant or variable in the lexical environment.
Consequently, identifier resolution and type deduction are also
inseparable in the general case.
Nonetheless, the three processes of identifier resolution, type
deduction, and constant evaluation can be separated for the purpose of
explanation.
# An Example
The code below shows the most basic use of the type checker to check
the _hello, world_ program, supplied as a string.
Later examples will be variations on this one, and we'll often omit
boilerplate details such as parsing.
To check out and build the examples,
run `go get golang.org/x/example/gotypes/...`.
// go get golang.org/x/example/gotypes/pkginfo
```
package main
import (
"fmt"
"go/ast"
"go/importer"
"go/parser"
"go/token"
"go/types"
"log"
)
const hello = `package main
import "fmt"
func main() {
fmt.Println("Hello, world")
}`
func main() {
fset := token.NewFileSet()
// Parse the input string, []byte, or io.Reader,
// recording position information in fset.
// ParseFile returns an *ast.File, a syntax tree.
f, err := parser.ParseFile(fset, "hello.go", hello, 0)
if err != nil {
log.Fatal(err) // parse error
}
// A Config controls various options of the type checker.
// The defaults work fine except for one setting:
// we must specify how to deal with imports.
conf := types.Config{Importer: importer.Default()}
// Type-check the package containing only file f.
// Check returns a *types.Package.
pkg, err := conf.Check("cmd/hello", fset, []*ast.File{f}, nil)
if err != nil {
log.Fatal(err) // type error
}
fmt.Printf("Package %q\n", pkg.Path())
fmt.Printf("Name: %s\n", pkg.Name())
fmt.Printf("Imports: %s\n", pkg.Imports())
fmt.Printf("Scope: %s\n", pkg.Scope())
}
```
First, the program creates a
[`token.FileSet`](http://golang.org/pkg/go/token/#FileSet).
To avoid the need to store file names and line and column
numbers in every node of the syntax tree, the `go/token` package
provides `FileSet`, a data structure that stores this information
compactly for a sequence of files.
A `FileSet` records each file name only once, and records
only the byte offsets of each newline, allowing a position within
any file to be identified using a small integer called a
`token.Pos`.
Many tools create a single `FileSet` at startup.
Any part of the program that needs to convert a `token.Pos` into
an intelligible location---as part of an error message, for
instance---must have access to the `FileSet`.
Second, the program parses the input string.
More realistic packages contain several source files, so the parsing
step must be repeated for each one, or better, done in parallel.
Third, it creates a `Config` that specifies type-checking options.
Since the _hello, world_ program uses imports, we must indicate
how to locate the imported packages.
Here we use `importer.Default()`, which loads compiler-generated
export data, but we'll explore alternatives in [Imports](#imports).
Fourth, the program calls `Check`.
This creates a `Package` whose path is `"cmd/hello"`, and
type-checks each of the specified files---just one in this example.
The final (nil) argument is a pointer to an optional `Info`
struct that returns additional deductions from the type checker; more
on that later.
`Check` returns a `Package` even when it also returns an error.
The type checker is robust to ill-formed input,
and goes to great lengths to report accurate
partial information even in the vicinity of syntax or type errors.
`Package` has this definition:
type Package struct{ ... }
func (*Package) Path() string
func (*Package) Name() string
func (*Package) Scope() *Scope
func (*Package) Imports() []*Package
Finally, the program prints the attributes of the package, shown below.
(The hexadecimal number may vary from one run to the next.)
```
$ go build golang.org/x/example/gotypes/pkginfo
$ ./pkginfo
Package "cmd/hello"
Name: main
Imports: [package fmt ("fmt")]
Scope: package "cmd/hello" scope 0x820533590 {
. func cmd/hello.main()
}
```
A package's `Path`, such as `"encoding/json"`, is the string
by which import declarations identify it.
It is unique within a `$GOPATH` workspace,
and for published packages it must be globally unique.
A package's `Name` is the identifier in the `package`
declaration of each source file within the package, such as `json`.
The type checker reports an error if not all the package declarations in
the package agree.
The package name determines how the package is known when it is
imported into a file (unless a renaming import is used),
but is otherwise not visible to a program.
`Scope` returns the package's [_lexical block_](#scopes),
which provides access to all the named entities or
[_objects_](#objects) declared at package level.
`Imports` returns the set of packages directly imported by this
one, and may be useful for computing dependencies
([Initialization Order](#initialization-order)).
# Objects
The task of identifier resolution is to map every identifier in the
syntax tree, that is, every `ast.Ident`, to an object.
For our purposes, an _object_ is a named entity created by a
declaration, such as a `var`, `type`, or `func`
declaration.
(This is different from the everyday meaning of object in
object-oriented programming.)
Objects are represented by the `Object` interface:
type Object interface {
Name() string // package-local object name
Exported() bool // reports whether the name starts with a capital letter
Type() Type // object type
Pos() token.Pos // position of object identifier in declaration
Parent() *Scope // scope in which this object is declared
Pkg() *Package // nil for objects in the Universe scope and labels
Id() string // object id (see Ids section below)
}
The first four methods are straightforward; we'll explain the other
three later.
`Name` returns the object's name---an identifier.
`Exported` is a convenience method that reports whether the first
letter of `Name` is a capital, indicating that the object may be
visible from outside the package.
It's a shorthand for `ast.IsExported(obj.Name())`.
`Type` returns the object's type; we'll come back to that in
[Types](#types).
`Pos` returns the source position of the object's declaring identifier.
To make sense of a `token.Pos`, we need to call the
`(*token.FileSet).Position` method, which returns a struct with
individual fields for the file name, line number, column, and byte
offset, though usually we just call its `String` method:
fmt.Println(fset.Position(obj.Pos())) // "hello.go:10:6"
Not all objects carry position information.
Since the file format for compiler export data ([Imports](#imports))
does not record position information, calling `Pos` on an object
imported from such a file returns zero, also known as
`token.NoPos`.
There are eight kinds of objects in the Go type checker.
Most familiar are the kinds that can be declared at package level:
constants, variables, functions, and types.
Less familiar are statement labels, imported package names
(such as `json` in a file containing an `import "encoding/json"`
declaration), built-in functions (such as `append` and
`len`), and the pre-declared `nil`.
The eight types shown below are the only concrete types that satisfy
the `Object` interface.
In other words, `Object` is a _discriminated union_ of 8
possible types, and we commonly use a type switch to distinguish them.
Object = *Func // function, concrete method, or abstract method
| *Var // variable, parameter, result, or struct field
| *Const // constant
| *TypeName // type name
| *Label // statement label
| *PkgName // package name, e.g. json after import "encoding/json"
| *Builtin // predeclared function such as append or len
| *Nil // predeclared nil
`Object`s are canonical.
That is, two `Object`s `x` and `y` denote the same
entity if and only if `x==y`.
Object identity is significant, and objects are routinely compared by
the addresses of the underlying pointers.
Although a package-level object is uniquely identified by its name
and enclosing package, for other objects there is no simple way to
obtain a string that uniquely identifies it.
The `Parent` method returns the `Scope` (lexical block) in
which the object was declared; we'll come back to this in
[Scopes](#scopes).
Fields and methods are not found in the lexical environment, so
their objects have no `Parent`.
<!-- TODO check this -->
The `Pkg` method returns the `Package` to which this object
belongs, even for objects not declared at package level.
Only predeclared objects have no package.
The `Id` method will be explained in [Ids](#ids).
Not all methods make sense for each kind of object. For instance,
the last four kinds above have no meaningful `Type` method.
And some kinds of objects have methods in addition to those required by the
`Object` interface:
func (*Func) Scope() *Scope
func (*Var) Anonymous() bool
func (*Var) IsField() bool
func (*Const) Val() constant.Value
func (*TypeName) IsAlias() bool
func (*PkgName) Imported() *Package
`(*Func).Scope` returns the [lexical block](#scopes)
containing the function's parameters, results,
and other local declarations.
`(*Var).IsField` distinguishes struct fields from ordinary
variables, and `(*Var).Anonymous` discriminates named fields like
the one in `struct{T T}` from anonymous fields like the one in `struct{T}`.
`(*Const).Val` returns the value of a named [constant](#constants).
`(*TypeName).IsAlias`, introduced in Go 1.9, reports whether the
type name is simply an alias for a type (as in `type I = int`),
as opposed to a definition of a [`Named`](#named-types) type, as
in `type Celsius float64`.
`(*PkgName).Imported` returns the package (for instance,
`encoding/json`) denoted by a given import name such as `json`.
Each time a package is imported, a new `PkgName` object is
created, usually with the same name as the `Package` it
denotes, but not always, as in the case of a renaming import.
`PkgName`s are objects, but `Package`s are not.
We'll look more closely at this in [Imports](#imports).
All relationships between the syntax trees (`ast.Node`s) and type
checker data structures such as `Object`s and `Type`s are
stored in mappings outside the syntax tree itself.
Be aware that the `go/ast` package also defines a type called
`Object` that resembles---and predates---the type checker's
`Object`, and that `ast.Object`s are held directly by
identifiers in the AST.
They are created by the parser, which has a necessarily limited view
of the package, so the information they represent is at best partial and
in some cases wrong, as in the `T{k: 0}` example mentioned above.
If you are using the type checker, there is no reason to use the older
`ast.Object` mechanism.
# Identifier Resolution
Identifier resolution computes the relationship between
identifiers and objects.
Its results are recorded in the `Info` struct optionally passed
to `Check`.
The fields related to identifier resolution are shown below.
type Info struct {
Defs map[*ast.Ident]Object
Uses map[*ast.Ident]Object
Implicits map[ast.Node]Object
Selections map[*ast.SelectorExpr]*Selection
Scopes map[ast.Node]*Scope
...
}
Since not all facts computed by the type checker are needed by every
client, the API lets clients control which components of the result
should be recorded and which discarded: only fields that hold a
non-nil map will be populated during the call to `Check`.
The two fields of type `map[*ast.Ident]Object` are the most important:
`Defs` records _declaring_ identifiers and
`Uses` records _referring_ identifiers.
In the example below, the comments indicate which identifiers are of
which kind.
var x int // def of x, use of int
fmt.Println(x) // uses of fmt, Println, and x
type T struct{U} // def of T, use of U (type), def of U (field)
The final line above illustrates why we don't combine `Defs` and
`Uses` into one map.
In the anonymous field declaration `struct{U}`, the
identifier `U` is both a use of the type `U` (a
`TypeName`) and a definition of the anonymous field (a
`Var`).
The function below prints the location of each referring and defining
identifier in the input program, and the object it refers to.
// go get golang.org/x/example/gotypes/defsuses
```
func PrintDefsUses(fset *token.FileSet, files ...*ast.File) error {
conf := types.Config{Importer: importer.Default()}
info := &types.Info{
Defs: make(map[*ast.Ident]types.Object),
Uses: make(map[*ast.Ident]types.Object),
}
_, err := conf.Check("hello", fset, files, info)
if err != nil {
return err // type error
}
for id, obj := range info.Defs {
fmt.Printf("%s: %q defines %v\n",
fset.Position(id.Pos()), id.Name, obj)
}
for id, obj := range info.Uses {
fmt.Printf("%s: %q uses %v\n",
fset.Position(id.Pos()), id.Name, obj)
}
return nil
}
```
Let's use the _hello, world_ program again as the input:
// go get golang.org/x/example/gotypes/hello
```
package main
import "fmt"
func main() {
fmt.Println("Hello, 世界")
}
```
This is what it prints:
```
$ go build golang.org/x/example/gotypes/defsuses
$ ./defsuses
hello.go:1:9: "main" defines <nil>
hello.go:5:6: "main" defines func hello.main()
hello.go:6:9: "fmt" uses package fmt
hello.go:6:13: "Println" uses func fmt.Println(a ...interface{}) (n int, err error)
```
Notice that the `Defs` mapping may contain nil entries in a few
cases.
The first line of output reports that the package identifier
`main` is present in the `Defs` mapping, but has no
associated object.
The `Implicits` mapping handles two cases of the syntax in
which an `Object` is declared without an `ast.Ident`, namely type
switches and import declarations.
<!--
A third case: an anonymous function parameter or result variable.
These objects are returned by Signature.Param and Signature.Result.
-->
In the type switch below, which declares a local variable `y`,
the type of `y` is different in each case of the switch:
switch y := x.(type) {
case int:
fmt.Printf("%d", y)
case string:
fmt.Printf("%q", y)
default:
fmt.Print(y)
}
To represent this, for each single-type case, the type checker creates
a separate `Var` object for `y` with the appropriate type,
and `Implicits` maps each `ast.CaseClause` to the `Var`
for that case.
The `default` case, the `nil` case, and cases with more than one
type all use the regular `Var` object that is associated with the
identifier `y`, which is found in the `Defs` mapping.
The import declaration below defines the name `json` without an
`ast.Ident`:
import "encoding/json"
`Implicits` maps this `ast.ImportSpec` to the `PkgName`
object named `json` that it implicitly declares.
The `Selections` mapping, of type
`map[*ast.SelectorExpr]*Selection`, records the meaning of each
expression of the form _`expr`_`.f`, where _`expr`_ is
an expression or type and `f` is the name of a field or method.
These expressions, called _selections_, are represented by
`ast.SelectorExpr` nodes in the AST.
We'll talk more about the `Selection` type in [Selections](#selections).
Not all `ast.SelectorExpr` nodes represent selections.
Expressions like `fmt.Println`, in which a package name precedes
the dot, are _qualified identifiers_.
They do not appear in the `Selections` mapping, but their
constituent identifiers (such as `fmt` and `Println`) both
appear in `Uses`.
Referring identifiers that are not part of an `ast.SelectorExpr`
are _lexical references_.
That is, they are resolved to an object by searching for the
innermost enclosing lexical declaration of that name.
We'll see how that search works in the next section.
# Scopes
The `Scope` type is a mapping from names to objects.
type Scope struct{ ... }
func (s *Scope) Names() []string
func (s *Scope) Lookup(name string) Object
`Names` returns the set of names in the mapping, in sorted order.
(It is not a simple accessor though, so call it sparingly.)
The `Lookup ` method returns the object for a given name, so we
can print all the entries or _bindings_ in a scope like this:
for _, name := range scope.Names() {
fmt.Println(scope.Lookup(name))
}
The _scope_ of a declaration of a name is the region of
program source in which a reference to the name resolves to that
declaration. That is, scope is a property of a declaration.
However, in the `go/types` API, the `Scope` type represents
a _lexical block_, which is one component of the lexical
environment.
Consider the _hello, world_ program again:
package main
import "fmt"
func main() {
const message = "hello, world"
fmt.Println(message)
}
There are four lexical blocks in this program.
The outermost one is the _universe block_, which maps the
pre-declared names like `int`, `true`, and `append` to
their objects---a `TypeName`, a `Const`, and a
`Builtin`, respectively.
The universe block is represented by the global variable
`Universe`, of type `*Scope`, although it's logically a
constant so you shouldn't modify it.
Next is the _package block_, which maps `"main"` to the
`main` function.
Following that is the _file block_, which maps `"fmt"` to
the `PkgName` object for this import of the `fmt` package.
And finally, the innermost block is that of function `main`, a
local block, which contains the declaration of `message`, a `Const`.
The `main` function is trivial, but many functions contain
several blocks since each `if`, `for`, `switch`,
`case`, or `select` statement creates at least one
additional block.
Local blocks nest to arbitrary depths.
The structure of the lexical environment thus forms a tree, with the
universe block at the root, the package blocks beneath it, the file
blocks beneath them, and then any number of local blocks beneath the
files.
We can access and navigate this tree structure with the following
methods of `Scope`:
func (s *Scope) Parent() *Scope
func (s *Scope) NumChildren() int
func (s *Scope) Child(i int) *Scope
`Parent` lets us walk up the tree, and `Child`
lets us walk down it.
Note that although the `Parent` of every package `Scope` is
`Universe`, `Universe` has no children.
This asymmetry is a consequence of using a global variable to hold
`Universe`.
To obtain the universe block, we use the `Universe` global variable.
To obtain the lexical block of a `Package`, we call its
`Scope` method.
To obtain the scope of a file (`*ast.File`), or any smaller piece
of syntax such as an `*ast.IfStmt`, we consult the `Scopes`
mapping in the `Info` struct, which maps each block-creating
syntax node to its block.
The lexical block of a named function or method can also be obtained
by calling its `(*Func).Scope` method.
<!--
TODO: explain explicit and implicit blocks
TODO: explain Dot imports.
TODO: explain that Func blocks are associated with FuncType (not FuncDecl or FuncLit)
-->
To look up a name in the lexical environment, we must search the tree
of lexical blocks, starting at a particular `Scope` and walking
up to the root until a declaration of the name is found.
For convenience, the `LookupParent` method does this, returning
not just the object, if found, but also the `Scope` in which it was
declared, which may be an ancestor of the initial one:
func (s *Scope) LookupParent(name string, pos token.Pos) (*Scope, Object)
The `pos` parameter determines the position in the source code at
which the name should be resolved.
The effective lexical environment is different at each point in the
block because it depends on which local declarations appear
before or after that point.
(We'll see an illustration in a moment.)
`Scope` has several other methods relating to source positions:
func (s *Scope) Pos() token.Pos
func (s *Scope) End() token.Pos
func (s *Scope) Contains(pos token.Pos) bool
func (s *Scope) Innermost(pos token.Pos) *Scope
`Pos` and `End` report the `Scope`'s start and end
position which, for explicit blocks, coincide with its curly
braces.
`Contains` is a convenience method that reports whether a
position lies in this interval.
`Innermost` returns the innermost scope containing the specified
position, which may be a child or other descendent of the initial
scope.
These features are useful for tools that wish to resolve names or
evaluate constant expressions as if they had appeared at a particular
point within the program.
The next example program finds all the comments in the input,
treating the contents of each one as a name. It looks up each name in
the environment at the position of the comment, and prints what it
finds.
Observe that the `ParseComments` flag directs the parser to
preserve comments in the input.
// go get golang.org/x/example/gotypes/lookup
```
func main() {
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "hello.go", hello, parser.ParseComments)
if err != nil {
log.Fatal(err) // parse error
}
conf := types.Config{Importer: importer.Default()}
pkg, err := conf.Check("cmd/hello", fset, []*ast.File{f}, nil)
if err != nil {
log.Fatal(err) // type error
}
// Each comment contains a name.
// Look up that name in the innermost scope enclosing the comment.
for _, comment := range f.Comments {
pos := comment.Pos()
name := strings.TrimSpace(comment.Text())
fmt.Printf("At %s,\t%q = ", fset.Position(pos), name)
inner := pkg.Scope().Innermost(pos)
if _, obj := inner.LookupParent(name, pos); obj != nil {
fmt.Println(obj)
} else {
fmt.Println("not found")
}
}
}
```
The expression `pkg.Scope().Innermost(pos)` finds the innermost
`Scope` that encloses the comment, and `LookupParent(name, pos)`
does a name lookup at a specific position in that lexical block.
A typical input is shown below.
The first comment causes a lookup of `"append"` in the file block.
The second comment looks up `"fmt"` in the `main` function's block,
and so on.
```
const hello = `
package main
import "fmt"
// append
func main() {
// fmt
fmt.Println("Hello, world")
// main
main, x := 1, 2
// main
print(main, x)
// x
}
// x
`
```
Here's the output:
```
$ go build golang.org/x/example/gotypes/lookup
$ ./lookup
At hello.go:6:1, "append" = builtin append
At hello.go:8:9, "fmt" = package fmt
At hello.go:10:9, "main" = func cmd/hello.main()
At hello.go:12:9, "main" = var main int
At hello.go:14:9, "x" = var x int
At hello.go:16:1, "x" = not found
```
Notice how the two lookups of `main` return different results,
even though they occur in the same block, because one precedes the
declaration of the local variable named `main` and the other
follows it.
Also notice that there are two lookups of the name `x` but only
the first one, in the function block, succeeds.
Download the program and modify both the input program and
the set of comments to get a better feel for how name resolution works.
The table below summarizes which kinds of objects may be declared at
each level of the tree of lexical blocks.
Universe File Package Local
Builtin ✔
Nil ✔
Const ✔ ✔ ✔
TypeName ✔ ✔ ✔
Func ✔
Var ✔ ✔
PkgName ✔
Label ✔
# Initialization Order
In the course of identifier resolution, the type checker constructs a
graph of references among declarations of package-level variables and
functions.
The type checker reports an error if the initializer expression for a
variable refers to that variable, whether directly or indirectly.
The reference graph determines the initialization order of the
package-level variables, as required by the Go spec, using a
breadth-first algorithm.
First, variables in the graph with no successors are removed, sorted
into the order in which they appear in the source code, then added
to a list. This creates more variables that have no successors.
The process repeats until they have all been removed.
The result is available in the `InitOrder` field of the
`Info` struct, whose type is `[]Initializer`.
type Info struct {
...
InitOrder []Initializer
...
}
type Initializer struct {
Lhs []*Var // var Lhs = Rhs
Rhs ast.Expr
}
Each element of the list represents a single initializer expression
that must be executed, and the variables to which it is assigned.
The variables may number zero, one, or more, as in these examples:
var _ io.Writer = new(bytes.Buffer)
var rx = regexp.MustCompile("^b(an)*a$")
var cwd, cwdErr = os.Getwd()
This process governs the initialization order of variables within a
package.
Across packages, dependencies must be initialized first, although the
order among them is not specified.
That is, any topological order of the import graph will do.
The `(*Package).Imports` method returns the set of direct
dependencies of a package.
# Types
The main job of the type checker is, of course, to deduce the type
of each expression and to report type errors.
Like `Object`, `Type` is an interface type used as a
discriminated union of several concrete types but, unlike
`Object`, `Type` has very few methods because types have
little in common with each other.
Here is the interface:
type Type interface {
Underlying() Type
}
And here are the eleven concrete types that satisfy it:
Type = *Basic
| *Pointer
| *Array
| *Slice
| *Map
| *Chan
| *Struct
| *Tuple
| *Signature
| *Named
| *Interface
With the exception of `Named` types, instances of `Type` are
not canonical.
That is, it is usually a mistake to compare types using `t1==t2`
since this equivalence is not the same as the
[type identity relation](https://golang.org/ref/spec#Type_identity)
defined by the Go spec.
Use this function instead:
func Identical(t1, t2 Type) bool
For the same reason, you should not use a `Type` as a key in a map.
The [`golang.org/x/tools/go/types/typeutil` package](https://pkg.go.dev/golang.org/x/tools/go/types/typeutil)
provides a map keyed by types that uses the correct
equivalence relation.
The Go spec defines three relations over types.
[_Assignability_](https://golang.org/ref/spec#Assignability)
governs which pairs of types may appear on the
left- and right-hand side of an assignment, including implicit
assignments such as function calls, map and channel operations, and so
on.
[_Comparability_](https://golang.org/ref/spec#Comparison_operators)
determines which types may appear in a comparison `x==y` or a
switch case or may be used as a map key.
[_Convertibility_](https://golang.org/ref/spec#Conversions)
governs which pairs of types are allowed in a conversion operation
`T(v)`.
You can query these relations with the following predicate functions:
func AssignableTo(V, T Type) bool
func Comparable(T Type) bool
func ConvertibleTo(V, T Type) bool
Let's take a look at each kind of type.
## Basic types
`Basic` represents all types that are not composed from simpler
types.
This is essentially the set of underlying types that a constant expression is
permitted to have--strings, booleans, and numbers---but it also
includes `unsafe.Pointer` and untyped nil.
type Basic struct{...}
func (*Basic) Kind() BasicKind
func (*Basic) Name() string
func (*Basic) Info() BasicInfo
The `Kind` method returns an "enum" value that indicates which
basic type this is.
The kinds `Bool`, `String`, `Int16`, and so on,
represent the corresponding predeclared boolean, string, or numeric
types.
There are two synonyms: `Byte` is equivalent to `Uint8`
and `Rune` is equivalent to `Int32`.
The kind `UnsafePointer` represents `unsafe.Pointer`.
The kinds `UntypedBool`, `UntypedInt` and so on represent
the six kinds of "untyped" constant types: boolean, integer, rune,
float, complex, and string.
The kind `UntypedNil` represents the type of the predeclared
`nil` value.
And the kind `Invalid` indicates the invalid type, which is used
for expressions containing errors, or for objects without types, like
`Label`, `Builtin`, or `PkgName`.
The `Name` method returns the name of the type, such as
`"float64"`, and the `Info` method returns a bitfield that
encodes information about the type, such as whether it is signed or
unsigned, integer or floating point, or real or complex.
`Typ` is a table of canonical basic types, indexed by
kind, so `Typ[String]` returns the `*Basic` that represents
`string`, for instance.
Like `Universe`, `Typ` is logically a constant, so don't
modify it.
A few minor subtleties:
According to the Go spec, pre-declared types such as `int` are
named types for the purposes of assignability, even though the type
checker does not represent them using `Named`.
And `unsafe.Pointer` is a pointer type for the purpose of
determining whether the receiver type of a method is legal, even
though the type checker does not represent it using `Pointer`.
The "untyped" types are usually only ascribed to constant expressions,
but there is one exception.
A comparison `x==y` has type "untyped bool", so the result of
this expression may be assigned to a variable of type `bool` or
any other named boolean type.
## Simple Composite Types
The types `Pointer`, `Array`, `Slice`, `Map`,
and `Chan` are pretty self-explanatory.
All have an `Elem` method that returns the element type `T`
for a pointer `*T`, an array `[n]T`, a slice `[]T`, a
map `map[K]T`, or a channel `chan T`.
This should feel familiar if you've used the `reflect.Value` API.
In addition, the `*Map`, `*Chan`, and `*Array` types
have accessor methods that return their key type, direction, and
length, respectively:
func (*Map) Key() Type
func (*Chan) Dir() ChanDir // = Send | Recv | SendRecv
func (*Array) Len() int64
## Struct Types
A struct type has an ordered list of fields and a corresponding
ordered list of field tags.
type Struct struct{ ... }
func (*Struct) NumFields() int
func (*Struct) Field(i int) *Var
func (*Struct) Tag(i int) string
Each field is a `Var` object whose `IsField` method returns true.
Field objects have no `Parent` scope, because they are
resolved through selections, not through the lexical environment.
<!-- TODO check this -->
Thanks to embedding, the expression `new(S).f` may be a shorthand
for a longer expression such as `new(S).d.e.f`, but in the
representation of `Struct` types, these field selection
operations are explicit.
That is, the set of fields of struct type `S` does not include `f`.
An anonymous field is represented like a regular field, but its
`Anonymous` method returns true.
One subtlety is relevant to tools that generate documentation.
When analyzing a declaration such as this,
type T struct{x int}
it may be tempting to consider the `Var` object for field `x` as if it
had the name `"T.x"`, but beware: field objects do not have
canonical names and there is no way to obtain the name `"T"`
from the `Var` for `x`.
That's because several types may have the same underlying struct type,
as in this code:
type T struct{x int}
type U T
Here, the `Var` for field `x` belongs equally to `T`
and to `U`, and short of inspecting source positions or walking
the AST---neither of which is possible for objects loaded from compiler
export data---it is not possible to ascertain that `x` was declared as
part of `T`.
The type checker builds the exact same data structures given this input:
type T U
type U struct{x int}
A similar issue applies to the methods of named interface types.
## Tuple Types
Like a struct, a tuple type has an ordered list of fields, and fields
may be named.
type Tuple struct{ ... }
func (*Tuple) Len() int
func (*Tuple) At(i int) *Var
Although tuples are not the type of any variable in Go, they are
the type of some expressions, such as the right-hand sides of these
assignments:
v, ok = m[key]
v, ok = <-ch
v, ok = x.(T)
f, err = os.Open(filename)
Tuples also represent the types of the parameter list and the result
list of a function, as we will see.
Since empty tuples are common, the nil `*Tuple` pointer is a valid empty tuple.
## Function and Method Types
The types of functions and methods are represented by a `Signature`,
which has a tuple of parameter types and a tuple of result types.
type Signature struct{ ... }
func (*Signature) Recv() *Var
func (*Signature) Params() *Tuple
func (*Signature) Results() *Tuple
func (*Signature) Variadic() bool
Variadic functions such as `fmt.Println` have the `Variadic`
flag set.
The final parameter of such functions is always a slice, or in the
special case of certain calls to `append`, a string.
A `Signature` for a method, whether concrete or abstract, has a
non-nil receiver parameter, `Recv`.
The type of the receiver is usually a named type or a pointer to a named type,
but it may be an unnamed struct or interface type in some cases.
Method types are rather second-class: they are only used for the
`Func` objects created by method declarations, and no Go
expression has a method type.
When printing a method type, the receiver does not appear, and the
`Identical` predicate ignores the receiver.
The types of `Builtin` objects like `append` cannot be
expressed as a `Signature` since those types require parametric
polymorphism.
`Builtin` objects are thus ascribed the `Invalid` basic type.
However, the type of each _call_ to a built-in function has a specific
and expressible Go type.
These types are recorded during type checking for later use
([TypeAndValue](#typeandvalue)).
## Named Types
Type declarations come in two forms.
The simplest kind, introduced in Go 1.9,
merely declares a (possibly alternative) name for an existing type.
Type names used in this way are informally called _type aliases_.
For example, this declaration lets you use the type
`Dictionary` as an alias for `map[string]string`:
type Dictionary = map[string]string
The declaration creates a `TypeName` object for `Dictionary`. The
object's `IsAlias` method returns true, and its `Type` method returns
a `Map` type that represents `map[string]string`.
The second form of type declaration, and the only kind prior to Go
1.9, does not use an equals sign:
type Celsius float64
This declaration does more than just give a name to a type.
It first defines a new `Named` type
whose underlying type is `float64`; this `Named` type is different
from any other type, including `float64`. The declaration binds the
`TypeName` object to the `Named` type.
Since Go 1.9, the Go language specification has used the term _defined
types_ instead of named types;
the essential property of a defined type is not that it has a name,
but that it is a distinct type with its own method set.
However, the type checker API predates that
change and instead calls defined types "named" types.
type Named struct{ ... }
func (*Named) NumMethods() int
func (*Named) Method(i int) *Func
func (*Named) Obj() *TypeName
func (*Named) Underlying() Type
The `Named` type's `Obj` method returns the `TypeName` object, which
provides the name, position, and other properties of the declaration.
Conversely, the `TypeName` object's `Type` method returns the `Named` type.
A `Named` type may appear as the receiver type in a method declaration.
Methods are associated with the `Named` type, not the name (the
`TypeName` object); it's possible---though cryptic---to declare a
method on a `Named` type using one of its aliases.
The `NumMethods` and `Method` methods enumerate the declared
methods associated with this `Named` type (or a pointer to it),
in the order they were declared.
However, due to the subtleties of anonymous fields and the difference
between value and pointer receivers, a named type may have more or fewer
methods than this list. We'll return to this in [Method Sets](#method-sets).
Every `Type` has an `Underlying` method, but for all of them
except `*Named`, it is simply the identity function.
For a named type, `Underlying` returns its underlying type, which
is always an unnamed type.
Thus `Underlying` returns `int` for both `T` and
`U` below.
type T int
type U T
Clients of the type checker often use type assertions or type switches
with a `Type` operand.
When doing so, it is often necessary to switch on the type that
_underlies_ the type of interest, and failure to do so may be a
bug.
This is a common pattern:
// handle types of composite literal
switch u := t.Underlying().(type) {
case *Struct: // ...
case *Map: // ...
case *Array, *Slice: // ...
default:
panic("impossible")
}
## Interface Types
Interface types are represented by `Interface`.
type Interface struct{ ... }
func (*Interface) Empty() bool
func (*Interface) NumMethods() int
func (*Interface) Method(i int) *Func
func (*Interface) NumEmbeddeds() int
func (*Interface) Embedded(i int) *Named
func (*Interface) NumExplicitMethods() int
func (*Interface) ExplicitMethod(i int) *Func
Syntactically, an interface type has a list of explicitly declared
methods (`ExplicitMethod`), and a list of embedded named
interface types (`Embedded`), but many clients care only about
the complete set of methods, which can be enumerated via
`Method`.
All three lists are ordered by name.
Since the empty interface is an important special case, the
`Empty` predicate provides a shorthand for `NumMethods() ==
0`.
As with the fields of structs (see above), the methods of interfaces
may belong equally to more than one interface type.
The `Func` object for method `f` in the code below is shared
by `I` and `J`:
type I interface { f() }
type J I
Because the difference between interface (abstract) and
non-interface (concrete) types is so important in Go, the
`IsInterface` predicate is provided for convenience.
func IsInterface(Type) bool
The type checker provides three utility methods relating to interface
satisfaction:
func Implements(V Type, T *Interface) bool
func AssertableTo(V *Interface, T Type) bool
func MissingMethod(V Type, T *Interface, static bool) (method *Func, wrongType bool)
The `Implements` predicate reports whether a type satisfies an
interface type.
`MissingMethod` is like `Implements`, but instead of
returning false, it explains why a type does not satisfy the
interface, for use in diagnostics.
`AssertableTo` reports whether a type assertion `v.(T)` is legal.
If `T` is a concrete type that doesn't have all the methods of
interface `v`, then the type assertion is not legal, as in this example:
// error: io.Writer is not assertible to int
func f(w io.Writer) int { return w.(int) }
## TypeAndValue
The type checker records the type of each expression in another field
of the `Info` struct, namely `Types`:
type Info struct {
...
Types map[ast.Expr]TypeAndValue
}
No entries are recorded for identifiers since the `Defs` and
`Uses` maps provide more information about them.
Also, no entries are recorded for pseudo-expressions like
`*ast.KeyValuePair` or `*ast.Ellipsis`.
The value of the `Types` map is a `TypeAndValue`, which
(unsurprisingly) holds the type and value of the expression, and in
addition, its _mode_.
The mode is opaque, but has predicates to answer questions such as:
Does this expression denote a value or a type? Does this value have an
address? Does this expression appear on the left-hand side of an
assignment? Does this expression appear in a context that expects two
results?
The comments in the code below give examples of expressions that
satisfy each predicate.
type TypeAndValue struct {
Type Type
Value constant.Value // for constant expressions only
...
}
func (TypeAndValue) IsVoid() bool // e.g. "main()"
func (TypeAndValue) IsType() bool // e.g. "*os.File"
func (TypeAndValue) IsBuiltin() bool // e.g. "len(x)"
func (TypeAndValue) IsValue() bool // e.g. "*os.Stdout"
func (TypeAndValue) IsNil() bool // e.g. "nil"
func (TypeAndValue) Addressable() bool // e.g. "a[i]" but not "f()", "m[key]"
func (TypeAndValue) Assignable() bool // e.g. "a[i]", "m[key]"
func (TypeAndValue) HasOk() bool // e.g. "<-ch", "m[key]"
The statement below inspects every expression within the AST of a single
type-checked file and prints its type, value, and mode:
// go get golang.org/x/example/gotypes/typeandvalue
```
// f is a parsed, type-checked *ast.File.
ast.Inspect(f, func(n ast.Node) bool {
if expr, ok := n.(ast.Expr); ok {
if tv, ok := info.Types[expr]; ok {
fmt.Printf("%-24s\tmode: %s\n", nodeString(expr), mode(tv))
fmt.Printf("\t\t\t\ttype: %v\n", tv.Type)
if tv.Value != nil {
fmt.Printf("\t\t\t\tvalue: %v\n", tv.Value)
}
}
}
return true
})
```
It makes use of these two helper functions, which are not shown:
// nodeString formats a syntax tree in the style of gofmt.
func nodeString(n ast.Node) string
// mode returns a string describing the mode of an expression.
func mode(tv types.TypeAndValue) string
Given this input:
```
const input = `
package main
var m = make(map[string]int)
func main() {
v, ok := m["hello, " + "world"]
print(rune(v), ok)
}
`
```
the program prints:
```
$ go build golang.org/x/example/gotypes/typeandvalue
$ ./typeandvalue
make(map[string]int) mode: value
type: map[string]int
make mode: builtin
type: func(map[string]int) map[string]int
map[string]int mode: type
type: map[string]int
string mode: type
type: string
int mode: type
type: int
m["hello, "+"world"] mode: value,assignable,ok
type: (int, bool)
m mode: value,addressable,assignable
type: map[string]int
"hello, " + "world" mode: value
type: string
value: "hello, world"
"hello, " mode: value
type: untyped string
value: "hello, "
"world" mode: value
type: untyped string
value: "world"
print(rune(v), ok) mode: void
type: ()
print mode: builtin
type: func(rune, bool)
rune(v) mode: value
type: rune
rune mode: type
type: rune
...more not shown...
```
Notice that the identifiers for the built-ins `make` and
`print` have types that are specific to the particular calls in
which they appear.
Also notice `m["hello"]` has a 2-tuple type `(int, bool)`
and that it is assignable, but unlike the variable `m`, it is not
addressable.
Download the example and vary the inputs and see what the program prints.
Here's another example, adapted from the `govet` static checking tool.
It checks for accidental uses of a method value `x.f` when a
call `x.f()` was intended;
comparing a method `x.f` against nil is a common mistake.
// go get golang.org/x/example/gotypes/nilfunc
```
// CheckNilFuncComparison reports unintended comparisons
// of functions against nil, e.g., "if x.Method == nil {".
func CheckNilFuncComparison(info *types.Info, n ast.Node) {
e, ok := n.(*ast.BinaryExpr)
if !ok {
return // not a binary operation
}
if e.Op != token.EQL && e.Op != token.NEQ {
return // not a comparison
}
// If this is a comparison against nil, find the other operand.
var other ast.Expr
if info.Types[e.X].IsNil() {
other = e.Y
} else if info.Types[e.Y].IsNil() {
other = e.X
} else {
return // not a comparison against nil
}
// Find the object.
var obj types.Object
switch v := other.(type) {
case *ast.Ident:
obj = info.Uses[v]
case *ast.SelectorExpr:
obj = info.Uses[v.Sel]
default:
return // not an identifier or selection
}
if _, ok := obj.(*types.Func); !ok {
return // not a function or method
}
fmt.Printf("%s: comparison of function %v %v nil is always %v\n",
fset.Position(e.Pos()), obj.Name(), e.Op, e.Op == token.NEQ)
}
```
Given this input,
```
const input = `package main
import "bytes"
func main() {
var buf bytes.Buffer
if buf.Bytes == nil && bytes.Repeat != nil && main == nil {
// ...
}
}
`
```
the program reports these errors:
```
$ go build golang.org/x/example/gotypes/nilfunc
$ ./nilfunc
input.go:7:5: comparison of function Bytes == nil is always false
input.go:7:25: comparison of function Repeat != nil is always true
input.go:7:48: comparison of function main == nil is always false
```
# Selections
A _selection_ is an expression _`expr`_`.f` in which
`f` denotes either a struct field or a method.
A selection is resolved not by looking for a name in the lexical
environment, but by looking within a _type_.
The type checker ascertains the meaning of each selection in the
package---a surprisingly tricky business---and records it in the
`Selections` mapping of the `Info` struct, whose values are
of type `Selection`:
type Selection struct{ ... }
func (s *Selection) Kind() SelectionKind // = FieldVal | MethodVal | MethodExpr
func (s *Selection) Recv() Type
func (s *Selection) Obj() Object
func (s *Selection) Type() Type
func (s *Selection) Index() []int
func (s *Selection) Indirect() bool
The `Kind` method discriminates between the three (legal) kinds
of selections, as indicated by the comments below.
type T struct{Field int}
func (T) Method() {}
var v T
// Kind Type
var _ = v.Field // FieldVal int
var _ = v.Method // MethodVal func()
var _ = T.Method // MethodExpr func(T)
Because of embedding, a selection may denote more than one field or
method, in which case it is ambiguous, and no `Selection` is
recorded for it.
The `Obj` method returns the `Object` for the selected field
(`*Var`) or method (`*Func`).
Due to embedding, the object may belong to a different type than that
of the receiver expression _`expr`_.
The `Type` method returns the type of the selection. For a field
selection, this is the type of the field, but for method selections,
the result is a function type that is not the same as the type of the
method.
For a `MethodVal`, the receiver parameter is dropped, and
for a `MethodExpr`, the receiver parameter becomes a regular
parameter, as shown in the example above.
The `Index` and `Indirect` methods report information about
implicit operations occurring during the selection that a compiler
would need to know about.
Because of embedding, a selection _`expr`_`.f` may be
shorthand for a sequence containing several implicit field selections,
_`expr`_`.d.e.f`, and `Index` reports the complete
sequence.
And because of automatic pointer dereferencing during struct field
accesses and method calls, a selection may imply one or more indirect
loads from memory; `Indirect` reports whether this occurs.
Clients of the type checker can call `LookupFieldOrMethod` to
look up a name within a type, as if by a selection.
This function has an intimidating signature, but conceptually it
accepts just a `Type` and a name, and returns a `Selection`:
func LookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) \
(obj Object, index []int, indirect bool)
The result is not actually a `Selection`, but it contains the
three main components of one: `Obj`, `Index`,
and `Indirect`.
The `addressable` flag should be set if the receiver is a
_variable_ of type `T`, since in a method selection on a
variable, an implicit address-of operation (`&`) may occur.
The flag indicates whether the methods of type `*T` should be
considered during the lookup.
(You may wonder why this parameter is necessary. Couldn't clients
instead call `LookupFieldOrMethod` on the pointer type `*T`
if the receiver is a `T` variable? The answer is that if
`T` is an interface type, the type `*T` has no methods at
all.)
The final two parameters of `LookupFieldOrMethod` are `(pkg
*Package, name string)`.
Together they specify the name of the field or method to look up.
This brings us to `Id`s.
# Ids
`LookupFieldOrMethod`'s need for a `Package` parameter
is a subtle consequence of the
[_Uniqueness of identifiers_](https://golang.org/ref/spec#Uniqueness_of_identifiers)
section in the Go spec: "Two
identifiers are different if they are spelled differently, or if they
appear in different packages and are not exported."
In practical terms, this means that a type may have two methods
(or two fields, or one of each) both named `f` so long as those
methods are defined in different packages, as in this example:
package a
type A int
func (A) f()
package b
type B int
func (B) f()
package c
import ( "a"; "b" )
type C struct{a.A; b.B} // C has two methods called f
The type `c.C` has two methods named `f`, but there is
no ambiguity because the two `f`s are distinct
identifiers---think of them as `fᵃ` and `fᵇ`.
For an exported method, this situation _would_ be ambiguous
because there is no distinction between `Fᵃ` and `Fᵇ`; there
is only `F`.
Despite having two methods called `f`, neither of them can be
called from within package `c` because `c` has no way to
identify them.
Within `c`, `f` is the identifier `fᶜ`, and
type `C` has no method of that name.
But if we pass an instance of `C` to code in package `a`
and call its `f` method via an interface, `fᵃ` is called.
The practical consequence for tool builders is that any time you need
to look up a field or method by name, or construct a map of fields and/or
methods keyed by name, it is not sufficient to use the object's name
as a key.
Instead, you must call the `Object.Id` method, which returns
a string that incorporates the object name, and for unexported
objects, the package path too.
There is also a standalone function `Id` that combines a name and
the package path in the same way:
func Id(pkg *Package, name string) string
This distinction applies to selections _`expr`_`.f`, but not
to lexical references `x` because for unexported identifiers,
declarations and references always appear in the same package.
Fun fact: the `reflect.StructField` type records both the
`Name` and the `PkgPath` strings for the same reason.
The `FieldByName` methods of `reflect.Value` and
`reflect.Type` match field names without regard to the package.
If there is more than one match, they return an invalid value.
# Method Sets
The _method set_ of a type is the set of methods that can be
called on any value of that type.
(A variable of type `T` has access to all the methods of type
`*T` as well, due to the implicit address-of operation during
method calls, but those extra methods are not part of the method set
of `T`.)
Clients can request the method set of a type `T` by calling
`NewMethodSet(T)`:
type MethodSet struct{ ... }
func NewMethodSet(T Type) *MethodSet
func (s *MethodSet) Len() int
func (s *MethodSet) At(i int) *Selection
func (s *MethodSet) Lookup(pkg *Package, name string) *Selection
The `Len` and `At` methods access a list of
`Selections`, all of kind `MethodVal`, ordered by `Id`.
The `Lookup` function allows lookup of a single method by
name (and package path, as explained in the previous section).
`NewMethodSet` can be expensive, so for applications that compute
method sets repeatedly, `golang.org/x/tools/go/types/typeutil`
provides a `MethodSetCache` type that records previous results.
If you only need a single method, don't construct the
`MethodSet` at all; it's cheaper to use
`LookupFieldOrMethod`.
The next program generates a boilerplate
declaration of a new concrete type that satisfies an existing
interface.
Here's an example:
```
$ ./skeleton io ReadWriteCloser buffer
// *buffer implements io.ReadWriteCloser.
type buffer struct{}
func (b *buffer) Close() error {
panic("unimplemented")
}
func (b *buffer) Read(p []byte) (n int, err error) {
panic("unimplemented")
}
func (b *buffer) Write(p []byte) (n int, err error) {
panic("unimplemented")
}
```
The three arguments are the package and the name of the existing
interface type, and the name of the new concrete type.
The `main` function (not shown) loads the specified package and
calls `PrintSkeleton` with the remaining two arguments:
// go get golang.org/x/example/gotypes/skeleton
```
func PrintSkeleton(pkg *types.Package, ifacename, concname string) error {
obj := pkg.Scope().Lookup(ifacename)
if obj == nil {
return fmt.Errorf("%s.%s not found", pkg.Path(), ifacename)
}
if _, ok := obj.(*types.TypeName); !ok {
return fmt.Errorf("%v is not a named type", obj)
}
iface, ok := obj.Type().Underlying().(*types.Interface)
if !ok {
return fmt.Errorf("type %v is a %T, not an interface",
obj, obj.Type().Underlying())
}
// Use first letter of type name as receiver parameter.
if !isValidIdentifier(concname) {
return fmt.Errorf("invalid concrete type name: %q", concname)
}
r, _ := utf8.DecodeRuneInString(concname)
fmt.Printf("// *%s implements %s.%s.\n", concname, pkg.Path(), ifacename)
fmt.Printf("type %s struct{}\n", concname)
mset := types.NewMethodSet(iface)
for i := 0; i < mset.Len(); i++ {
meth := mset.At(i).Obj()
sig := types.TypeString(meth.Type(), (*types.Package).Name)
fmt.Printf("func (%c *%s) %s%s {\n\tpanic(\"unimplemented\")\n}\n",
r, concname, meth.Name(),
strings.TrimPrefix(sig, "func"))
}
return nil
}
```
First, `PrintSkeleton` locates the package-level named interface
type, handling various error cases.
Then it chooses the name for the receiver of the new methods: the
first letter of the concrete type.
Finally, it iterates over the method set of the interface, printing
the corresponding concrete method declarations.
There's a subtlety in the declaration of `sig`, which is the
string form of the method signature.
We could have obtained this string from `meth.Type().String()`,
but this would cause any named types within it to be formatted with
the complete package path, for instance
`net/http.ResponseWriter`, which is informative in diagnostics
but not legal Go syntax.
The `TypeString` function (explained in [Formatting Values](#formatting-values)) allows the
caller to control how packages are printed.
Passing `(*types.Package).Name` causes only the package name
`http` to be printed, not the complete path.
Here's another example that illustrates it:
```
$ ./skeleton net/http Handler myHandler
// *myHandler implements net/http.Handler.
type myHandler struct{}
func (m *myHandler) ServeHTTP(http.ResponseWriter, *http.Request) {
panic("unimplemented")
}
```
The following program inspects all pairs of package-level named types
in `pkg`, and reports the types that satisfy each interface type.
// go get golang.org/x/example/gotypes/implements
```
// Find all named types at package level.
var allNamed []*types.Named
for _, name := range pkg.Scope().Names() {
if obj, ok := pkg.Scope().Lookup(name).(*types.TypeName); ok {
allNamed = append(allNamed, obj.Type().(*types.Named))
}
}
// Test assignability of all distinct pairs of
// named types (T, U) where U is an interface.
for _, T := range allNamed {
for _, U := range allNamed {
if T == U || !types.IsInterface(U) {
continue
}
if types.AssignableTo(T, U) {
fmt.Printf("%s satisfies %s\n", T, U)
} else if !types.IsInterface(T) &&
types.AssignableTo(types.NewPointer(T), U) {
fmt.Printf("%s satisfies %s\n", types.NewPointer(T), U)
}
}
}
```
Given this input,
// go get golang.org/x/example/gotypes/implements
```
const input = `package main
type A struct{}
func (*A) f()
type B int
func (B) f()
func (*B) g()
type I interface { f() }
type J interface { g() }
`
```
the program prints:
```
$ go build golang.org/x/example/gotypes/implements
$ ./implements
*hello.A satisfies hello.I
hello.B satisfies hello.I
*hello.B satisfies hello.J
```
Notice that the method set of `B` does not include `g`, but
the method set of `*B` does.
That's why we needed the second assignability check, using the pointer
type `types.NewPointer(T)`.
# Constants
A constant expression is one whose value is guaranteed to be computed at
compile time.
Constant expressions may appear in types, specifically as the length
of an array type such as `[16]byte`, so one of the jobs of the
type checker is to compute the value of each constant expression.
As we saw in the `typeandvalue` example, the type checker records
the value of each constant expression like `"Hello, " + "world"`,
storing it in the `Value` field of the `TypeAndValue` struct.
Constants are represented using the `Value` interface from the
`go/constant` package.
package constant // go/constant
type Value interface {
Kind() Kind
}
type Kind int // one of Unknown, Bool, String, Int, Float, Complex
The interface has only one method, for discriminating the various
kinds of constants, but the package provides many functions for
inspecting a value of a known kind,
// Accessors
func BoolVal(x Value) bool
func Float32Val(x Value) (float32, bool)
func Float64Val(x Value) (float64, bool)
func Int64Val(x Value) (int64, bool)
func StringVal(x Value) string
func Uint64Val(x Value) (uint64, bool)
func Bytes(x Value) []byte
func BitLen(x Value) int
func Sign(x Value) int
for performing arithmetic on values,
// Operations
func Compare(x Value, op token.Token, y Value) bool
func UnaryOp(op token.Token, y Value, prec uint) Value
func BinaryOp(x Value, op token.Token, y Value) Value
func Shift(x Value, op token.Token, s uint) Value
func Denom(x Value) Value
func Num(x Value) Value
func Real(x Value) Value
func Imag(x Value) Value
and for constructing new values:
// Constructors
func MakeBool(b bool) Value
func MakeFloat64(x float64) Value
func MakeFromBytes(bytes []byte) Value
func MakeFromLiteral(lit string, tok token.Token, prec uint) Value
func MakeImag(x Value) Value
func MakeInt64(x int64) Value
func MakeString(s string) Value
func MakeUint64(x uint64) Value
func MakeUnknown() Value
All numeric `Value`s, whether integer or floating-point, signed or
unsigned, or real or complex, are represented more precisely than
ordinary Go types like `int64` and `float64`.
Internally, the `go/constant` package uses multi-precision data types
like `Int`, `Rat`, and `Float` from the `math/big` package so that
`Values` and their arithmetic operations are accurate to at least 256
bits, as required by the Go specification.
<!-- TODO example -->
# Size and Alignment
Because the calls `unsafe.Sizeof(v)`, `unsafe.Alignof(v)`,
and `unsafe.Offsetof(v.f)` are all constant expressions, the type
checker must be able to compute the memory layout of any value
`v`.
By default, the type checker uses the same layout algorithm as the Go
1.5 `gc` compiler targeting `amd64`.
Clients can configure the type checker to use a different algorithm by
providing an instance of the `types.Sizes` interface in the
`types.Config` struct:
package types
type Sizes interface {
Alignof(T Type) int64
Offsetsof(fields []*Var) []int64
Sizeof(T Type) int64
}
For common changes, like reducing the word size to 32 bits, clients
can use an instance of `StdSizes`:
type StdSizes struct {
WordSize int64
MaxAlign int64
}
This type has two basic size and alignment parameters from which it
derives all the other values using common assumptions.
For example, pointers, functions, maps, and channels fit in one word,
strings and interfaces require two words, and slices need three.
The default behaviour is equivalent to `StdSizes{8, 8}`.
For more esoteric layout changes, you'll need to write a new
implementation of the `Sizes` interface.
The `hugeparam` program below prints all function parameters and
results whose size exceeds a threshold.
By default, the threshold is 48 bytes, but you can set it via the
`-bytes` command-line flag.
Such a tool could help identify inefficient parameter passing in your
programs.
// go get golang.org/x/example/gotypes/hugeparam
```
var bytesFlag = flag.Int("bytes", 48, "maximum parameter size in bytes")
var sizeof = (&types.StdSizes{8, 8}).Sizeof // the sizeof function
func PrintHugeParams(fset *token.FileSet, info *types.Info, files []*ast.File) {
checkTuple := func(descr string, tuple *types.Tuple) {
for i := 0; i < tuple.Len(); i++ {
v := tuple.At(i)
if sz := sizeof(v.Type()); sz > int64(*bytesFlag) {
fmt.Printf("%s: %q %s: %s = %d bytes\n",
fset.Position(v.Pos()),
v.Name(), descr, v.Type(), sz)
}
}
}
checkSig := func(sig *types.Signature) {
checkTuple("parameter", sig.Params())
checkTuple("result", sig.Results())
}
for _, file := range files {
ast.Inspect(file, func(n ast.Node) bool {
switch n := n.(type) {
case *ast.FuncDecl:
checkSig(info.Defs[n.Name].Type().(*types.Signature))
case *ast.FuncLit:
checkSig(info.Types[n.Type].Type.(*types.Signature))
}
return true
})
}
}
```
As before, `Inspect` applies a function to every node in the AST.
The function cares about two kinds of nodes: declarations of named
functions and methods (`*ast.FuncDecl`) and function literals
(`*ast.FuncLit`).
Observe the two cases' different logic to obtain the type of each
function.
Here's a typical invocation on the standard `encoding/xml` package.
It reports a number of places where the 7-word
[`StartElement` type](https://pkg.go.dev/encoding/xml#StartElement)
is copied.
```
% ./hugeparam encoding/xml
/go/src/encoding/xml/marshal.go:167:50: "start" parameter: encoding/xml.StartElement = 56 bytes
/go/src/encoding/xml/marshal.go:734:97: "" result: encoding/xml.StartElement = 56 bytes
/go/src/encoding/xml/marshal.go:761:51: "start" parameter: encoding/xml.StartElement = 56 bytes
/go/src/encoding/xml/marshal.go:781:68: "start" parameter: encoding/xml.StartElement = 56 bytes
/go/src/encoding/xml/xml.go:72:30: "" result: encoding/xml.StartElement = 56 bytes
```
# Imports
The type checker's `Check` function processes a slice of parsed
files (`[]*ast.File`) that make up one package.
When the type checker encounters an import declaration, it needs the
type information for the objects in the imported package.
It gets it by calling the `Import` method of the `Importer`
interface shown below, an instance of which must be provided by the
`Config`.
This separation of concerns relieves the type checker from having to
know any of the details of Go workspace organization, `GOPATH`,
compiler file formats, and so on.
type Importer interface {
Import(path string) (*Package, error)
}
Most of our examples used the simplest `Importer` implementation,
`importer.Default()`, provided by the `go/importer` package.
This importer looks in `$GOROOT` and `$GOPATH` for `.a`
files written by the compiler (`gc` or `gccgo`)
that was used to build the program.
In addition to object code, these files contain _export data_,
that is, a description of all the objects declared by the package, and
also of any objects from other packages that were referred to indirectly.
Because export data includes information about dependencies, the type
checker need load at most one file per import, instead of one per
transitive dependency.
Compiler export data is compact and efficient to locate, load, and
parse, but it has several shortcomings.
First, it does not contain position information for imported
objects, reducing the quality of certain diagnostic messages.
Second, it does not contain complete syntax trees nor semantic information
about the contents of function bodies, so it is not suitable for
interprocedural analyses.
Third, compiler object data may be stale. Nothing detects or ensures
that the object files are more recent than the source files from which
they were derived.
Generally, object data for standard packages is likely to be
up-to-date, but for user packages, it depends on how recently the user
ran a `go install` or `go build -i` command.
The [`golang.org/tools/x/go/loader` package](https://pkg.go.dev/golang.org/x/tools/go/loader)
provides an alternative `Importer` that addresses
some of these problems.
It loads a complete program from source, performing
[`cgo`](https://golang.org/cmd/cgo/cgo) preprocessing if
necessary, followed by parsing and type-checking.
It loads independent packages in parallel to hide I/O latency, and
detects and reports import cycles.
For each package, it provides the `types.Package` containing the
package's lexical environment, the list of `ast.File` syntax
trees for each file in the package, the `types.Info` containing
type information for each syntax node, and a list of type errors
associated with that package.
(Please be aware that the `go/loader` package's API is likely to
change before it finally stabilizes.)
The `doc` program below demonstrates a simple use of the loader.
It is a rudimentary implementation of `go doc` that prints the type,
methods, and documentation of the package-level object specified on
the command line.
Here's an example:
```
$ ./doc net/http File
type net/http.File interface{Readdir(count int) ([]os.FileInfo, error); Seek(offset int64, whence int) (int64, error); Stat() (os.FileInfo, error); io.Closer; io.Reader}
/go/src/io/io.go:92:2: method (net/http.File) Close() error
/go/src/io/io.go:71:2: method (net/http.File) Read(p []byte) (n int, err error)
/go/src/net/http/fs.go:65:2: method (net/http.File) Readdir(count int) ([]os.FileInfo, error)
/go/src/net/http/fs.go:66:2: method (net/http.File) Seek(offset int64, whence int) (int64, error)
/go/src/net/http/fs.go:67:2: method (net/http.File) Stat() (os.FileInfo, error)
A File is returned by a FileSystem's Open method and can be
served by the FileServer implementation.
The methods should behave the same as those on an *os.File.
```
Observe that it prints the correct location of each method
declaration, even though, due to embedding, some of
`http.File`'s methods were declared in another package.
Here's the first part of the program, showing how to load an entire
program starting from the single package, `pkgpath`:
// go get golang.org/x/example/gotypes/doc
```
pkgpath, name := os.Args[1], os.Args[2]
// The loader loads a complete Go program from source code.
conf := loader.Config{ParserMode: parser.ParseComments}
conf.Import(pkgpath)
lprog, err := conf.Load()
if err != nil {
log.Fatal(err) // load error
}
// Find the package and package-level object.
pkg := lprog.Package(pkgpath).Pkg
obj := pkg.Scope().Lookup(name)
if obj == nil {
log.Fatalf("%s.%s not found", pkg.Path(), name)
}
```
Notice that we instructed the parser to retain comments during parsing.
The rest of the program prints the output:
// go get golang.org/x/example/gotypes/doc
```
// Print the object and its methods (incl. location of definition).
fmt.Println(obj)
for _, sel := range typeutil.IntuitiveMethodSet(obj.Type(), nil) {
fmt.Printf("%s: %s\n", lprog.Fset.Position(sel.Obj().Pos()), sel)
}
// Find the path from the root of the AST to the object's position.
// Walk up to the enclosing ast.Decl for the doc comment.
_, path, _ := lprog.PathEnclosingInterval(obj.Pos(), obj.Pos())
for _, n := range path {
switch n := n.(type) {
case *ast.GenDecl:
fmt.Println("\n", n.Doc.Text())
return
case *ast.FuncDecl:
fmt.Println("\n", n.Doc.Text())
return
}
}
```
We used `IntuitiveMethodSet` to compute the method set, instead
of `NewMethodSet`.
The result of this convenience function, which is intended for use in
user interfaces, includes methods of `*T` as well as those of
`T`, since that matches most users' intuition about the method
set of a type.
(Our example, `http.File`, didn't illustrate the difference, but try
running it on a type with both value and pointer methods.)
Also notice `PathEnclosingInterval`, which finds the set of AST
nodes that enclose a particular point, in this case, the object's
declaring identifier.
By walking up with path, we find the enclosing declaration, to which
the documentation is attached.
# Formatting support
All types that satisfy `Type` or `Object` define a
`String` method that formats the type or object in a readable
notation. `Selection` also provides a `String` method.
All package-level objects within these data structures are
printed with the complete package path, as in these examples:
[]encoding/json.Marshaler // a *Slice type
encoding/json.Marshal // a *Func object
(*encoding/json.Encoder).Encode // a *Func object (method)
func (enc *encoding/json.Encoder) Encode(v interface{}) error // a method *Signature
func NewEncoder(w io.Writer) *encoding/json.Encoder // a function *Signature
This notation is unambiguous, but it is not legal Go syntax.
Also, package paths may be long, and the same package path may appear
many times in a single string, for instance, when formatting a
function of several parameters.
Because these strings often form part of a tool's user interface---as
with the diagnostic messages of `hugeparam` or the code generated
by `skeleton`---many clients want more control over the
formatting of package names.
The `go/types` package provides these alternatives to the
`String` methods:
func ObjectString(obj Object, qf Qualifier) string
func TypeString(typ Type, qf Qualifier) string
func SelectionString(s *Selection, qf Qualifier) string
type Qualifier func(*Package) string
The `TypeString`, `ObjectString`, and `SelectionString`
functions are like the `String` methods of the respective types,
but they accept an additional argument, a `Qualifier`.
A `Qualifier` is a client-provided function that determines how a
package name is rendered as a string.
If it is nil, the default behavior is to print the package's
path, just like the `String` methods do.
If a caller passes `(*Package).Name` as the qualifier, that is, a
function that accepts a package and returns its `Name`, then
objects are qualified only by the package name.
The above examples would look like this:
[]json.Marshaler
json.Marshal
(*json.Encoder).Encode
func (enc *json.Encoder) Encode(v interface{}) error
func NewEncoder(w io.Writer) *json.Encoder
Often when a tool prints some output, it is implicitly in the
context of a particular package, perhaps one specified by the
command line or HTTP request.
In that case, it is more natural to omit the package qualification
altogether for objects belonging to that package, but to qualify all
other objects by their package's path.
That's what the `RelativeTo(pkg)` qualifier does:
func RelativeTo(pkg *Package) Qualifier
The examples below show how `json.NewEncoder` would be printed
using three qualifiers, each relative to a different package:
// RelativeTo "encoding/json":
func NewEncoder(w io.Writer) *Encoder
// RelativeTo "io":
func NewEncoder(w Writer) *encoding/json.Encoder
// RelativeTo any other package:
func NewEncoder(w io.Writer) *encoding/json.Encoder
Another qualifier that may be relevant to refactoring tools (but is
not currently provided by the type checker) is one that renders each
package name using the locally appropriate name within a given source
file.
Its behavior would depend on the set of import declarations, including
renaming imports, within that source file.
# Getting from A to B
The type checker and its related packages represent many aspects of a
Go program in many different ways, and analysis tools must often map
between them.
For instance, a named entity may be identified by its `Object`;
by its declaring identifier (`ast.Ident`) or by any referring
identifier; by its declaring `ast.Node`; by the position
(`token.Pos`) of any those nodes; or by the filename and
line/column number (or byte offset) of those `token.Pos` values.
In this section, we'll list solutions to a number of common problems
of the form "I have an A; I need the corresponding B".
To map **from a `token.Pos` to an `ast.Node`**, call the
helper function
[`astutil.PathEnclosingInterval`](https://pkg.go.dev/golang.org/x/tools/go/ast/astutil#PathEnclosingInterval).
It returns the enclosing `ast.Node`, and all its ancestors up to
the root of the file.
You must know which file `*ast.File` the `token.Pos` belongs to.
Alternatively, you can search an entire program loaded by the
`loader` package, using
[`(*loader.Program).PathEnclosingInterval`](https://pkg.go.dev/golang.org/x/tools/go/loader#Program.PathEnclosingInterval).
To map **from an `Object` to its declaring syntax**, call
`Pos` to get its position, then use `PathEnclosingInterval` as before.
This approach is suitable for a one-off query. For repeated use, it
may be more efficient to visit the syntax tree and construct the
mapping between declarations and objects.
To map **from an `ast.Ident` to the `Object`** it refers to (or
declares), consult the `Uses` or `Defs` map for the
package, as shown in [Identifier Resolution](#identifier-resolution).
To map **from an `Object` to its documentation**, find the
object's declaration, and look at the attached `Doc` field.
You must have set the parser's `ParseComments` flag.
See the `doc` example in [Imports](#imports).