go/types
adonovan@google.com

Contents

Introduction

go/typesgo get

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
go/tokengo/scannergo/astgo/parsergo/constant
golang.org/x/tools/go/loaderx/tools

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.

unsafe.Sizeof
kT{k: 0}TkTk

Nonetheless, the three processes of identifier resolution, type deduction, and constant evaluation can be separated for the purpose of explanation.

An Example

go get github.com/golang/example/gotypes/...
// go get github.com/golang/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())
}
token.FileSetgo/tokenFileSetFileSettoken.PosFileSettoken.PosFileSet
Configimporter.Default()
CheckPackage"cmd/hello"InfoCheckPackagePackage
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 github.com/golang/example/gotypes/pkginfo
$ ./pkginfo
Package  "cmd/hello"
Name:    main
Imports: [package fmt ("fmt")]
Scope:   package "cmd/hello" scope 0x820533590 {
.  func cmd/hello.main()
}
Path"encoding/json"$GOPATH
Namepackagejson
ScopeImports

Objects

ast.Identvartypefunc
Object
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)
}
NameExportedNameast.IsExported(obj.Name())Type
Postoken.Pos(*token.FileSet).PositionString
fmt.Println(fset.Position(obj.Pos())) // "hello.go:10:6"
Postoken.NoPos
jsonimport "encoding/json"appendlennilObjectObject
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
ObjectObjectxyx==y
ParentScopeParent
PkgPackageId
TypeObject
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(*Var).IsField(*Var).Anonymousstruct{T T}struct{T}(*Const).Val
(*TypeName).IsAliastype I = intNamedtype Celsius float64
(*PkgName).Importedencoding/jsonjsonPkgNamePackagePkgNamePackage
ast.NodeObjectTypego/astObjectObjectast.ObjectT{k: 0}ast.Object

Identifier Resolution

InfoCheck
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
	...
}
Check
map[*ast.Ident]ObjectDefsUses
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)
DefsUsesstruct{U}UUTypeNameVar

The function below prints the location of each referring and defining identifier in the input program, and the object it refers to.

// go get github.com/golang/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 github.com/golang/example/gotypes/hello
package main

import "fmt"

func main() {
	fmt.Println("Hello, 世界")
}

This is what it prints:

$ go build github.com/golang/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)
DefsmainDefs
ImplicitsObjectast.Ident
yy
switch y := x.(type) {
case int:
	fmt.Printf("%d", y)
case string:
	fmt.Printf("%q", y)
default:
	fmt.Print(y)
}
VaryImplicitsast.CaseClauseVardefaultnilVaryDefs
jsonast.Ident
import "encoding/json"
Implicitsast.ImportSpecPkgNamejson
Selectionsmap[*ast.SelectorExpr]*Selectionexpr.fexprfast.SelectorExprSelection
ast.SelectorExprfmt.PrintlnSelectionsfmtPrintlnUses
ast.SelectorExpr

Scopes

Scope
type Scope struct{ ... }

func (s *Scope) Names() []string
func (s *Scope) Lookup(name string) Object
NamesLookup 
for _, name := range scope.Names() {
	fmt.Println(scope.Lookup(name))
}
go/typesScope
package main

import "fmt"

func main() {
	const message = "hello, world"
	fmt.Println(message)
}
inttrueappendTypeNameConstBuiltinUniverse*Scope
"main"main"fmt"PkgNamefmtmainmessageConstmainifforswitchcaseselect
Scope
func (s *Scope) Parent() *Scope
func (s *Scope) NumChildren() int
func (s *Scope) Child(i int) *Scope
ParentChildParentScopeUniverseUniverseUniverse
UniversePackageScope*ast.File*ast.IfStmtScopesInfo(*Func).Scope
ScopeLookupParentScope
func (s *Scope) LookupParent(name string, pos token.Pos) (*Scope, Object)
pos
Scope
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
PosEndScopeContainsInnermost
ParseComments
// go get github.com/golang/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")
		}
	}
}
pkg.Scope().Innermost(pos)ScopeLookupParent(name, pos)
"append""fmt"main
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 github.com/golang/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
mainmainx

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.

InitOrderInfo[]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()
(*Package).Imports

Types

ObjectTypeObjectType
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
NamedTypet1==t2
func Identical(t1, t2 Type) bool
Typegolang.org/x/tools/go/types/typeutil
x==yT(v)
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

Basicunsafe.Pointer
type Basic struct{...}
func (*Basic) Kind() BasicKind
func (*Basic) Name() string
func (*Basic) Info() BasicInfo
KindBoolStringInt16ByteUint8RuneInt32UnsafePointerunsafe.PointerUntypedBoolUntypedIntUntypedNilnilInvalidLabelBuiltinPkgName
Name"float64"Info
TypTyp[String]*BasicstringUniverseTyp
intNamedunsafe.PointerPointer
x==ybool

Simple Composite Types

PointerArraySliceMapChanElemT*T[n]T[]Tmap[K]Tchan Treflect.Value
*Map*Chan*Array
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
VarIsFieldParent
new(S).fnew(S).d.e.fStructSfAnonymous

One subtlety is relevant to tools that generate documentation. When analyzing a declaration such as this,

type T struct{x int}
Varx"T.x""T"Varx
type T struct{x int}
type U T
VarxTUxT
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.

*Tuple

Function and Method Types

Signature
type Signature struct{ ... }
func (*Signature) Recv() *Var
func (*Signature) Params() *Tuple
func (*Signature) Results() *Tuple
func (*Signature) Variadic() bool
fmt.PrintlnVariadicappend
SignatureRecvFuncIdentical
BuiltinappendSignatureBuiltinInvalid

Named Types

Dictionarymap[string]string
type Dictionary = map[string]string
TypeNameDictionaryIsAliasTypeMapmap[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
Namedfloat64Namedfloat64TypeNameNamed

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
NamedObjTypeNameTypeNameTypeNamed
NamedNamedTypeNameNamedNumMethodsMethodNamed
TypeUnderlying*NamedUnderlyingUnderlyingintTU
type T int
type U T
Type

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
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
ExplicitMethodEmbeddedMethodEmptyNumMethods() == 0
FuncfIJ
type I interface { f() }
type J I
IsInterface
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)
ImplementsMissingMethodImplements
AssertableTov.(T)Tv
// error: io.Writer is not assertible to int
func f(w io.Writer) int { return w.(int) } 

TypeAndValue

InfoTypes
type Info struct {
	...
	Types map[ast.Expr]TypeAndValue
}
DefsUses*ast.KeyValuePair*ast.Ellipsis
TypesTypeAndValue
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 github.com/golang/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 github.com/golang/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...
makeprintm["hello"](int, bool)m

Download the example and vary the inputs and see what the program prints.

govetx.fx.f()x.f
// go get github.com/golang/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 github.com/golang/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

expr.ffSelectionsInfoSelection
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
Kind
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)
Selection
ObjObject*Var*FuncexprTypeMethodValMethodExpr
IndexIndirectexpr.fexpr.d.e.fIndexIndirect
LookupFieldOrMethodTypeSelection
func LookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) \
    (obj Object, index []int, indirect bool)
SelectionObjIndexIndirect
addressableT&*TLookupFieldOrMethod*TTT*T
LookupFieldOrMethod(pkg *Package, name string)Id

Ids

LookupFieldOrMethodPackagef
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
c.CfffᵃfᵇFᵃFᵇF
fcccffᶜCCaffᵃ
Object.IdId
func Id(pkg *Package, name string) string
expr.fx
reflect.StructFieldNamePkgPathFieldByNamereflect.Valuereflect.Type

Method Sets

T*TT
TNewMethodSet(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
LenAtSelectionsMethodValIdLookup
NewMethodSetgolang.org/x/tools/go/types/typeutilMethodSetCacheMethodSetLookupFieldOrMethod

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")
}
mainPrintSkeleton
// go get github.com/golang/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
}
PrintSkeleton
sigmeth.Type().String()net/http.ResponseWriterTypeString(*types.Package).Namehttp
$ ./skeleton net/http Handler myHandler
// *myHandler implements net/http.Handler.
type myHandler struct{}
func (m *myHandler) ServeHTTP(http.ResponseWriter, *http.Request) {
	panic("unimplemented")
}
pkg
// go get github.com/golang/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 github.com/golang/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 github.com/golang/example/gotypes/implements
$ ./implements
*hello.A satisfies hello.I
hello.B satisfies hello.I
*hello.B satisfies hello.J
Bg*Btypes.NewPointer(T)

Constants

[16]byte
typeandvalue"Hello, " + "world"ValueTypeAndValueValuego/constant
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
Valueint64float64go/constantIntRatFloatmath/bigValues

Size and Alignment

unsafe.Sizeof(v)unsafe.Alignof(v)unsafe.Offsetof(v.f)v
gcamd64types.Sizestypes.Config
package types

type Sizes interface {
	Alignof(T Type) int64
	Offsetsof(fields []*Var) []int64
	Sizeof(T Type) int64
}
StdSizes
type StdSizes struct {
	WordSize int64
	MaxAlign int64
}
StdSizes{8, 8}Sizes
hugeparam-bytes
// go get github.com/golang/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
		})
	}
}
Inspect*ast.FuncDecl*ast.FuncLit
encoding/xmlStartElement
% ./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

Check[]*ast.FileImportImporterConfigGOPATH
type Importer interface {
	Import(path string) (*Package, error)
}
Importerimporter.Default()go/importer$GOROOT$GOPATH.agcgccgo
go installgo build -i
golang.org/tools/x/go/loaderImportercgotypes.Packageast.Filetypes.Infogo/loader
docgo doc
$ ./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.
http.Filepkgpath
// go get github.com/golang/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 github.com/golang/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
	}
}
IntuitiveMethodSetNewMethodSet*TThttp.File
PathEnclosingInterval

Formatting support

TypeObjectStringSelectionString
[]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
hugeparamskeleton
go/typesString
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
TypeStringObjectStringSelectionStringStringQualifier
QualifierString(*Package).NameName
[]json.Marshaler
json.Marshal
(*json.Encoder).Encode
func (enc *json.Encoder) Encode(v interface{}) error
func NewEncoder(w io.Writer) *json.Encoder
RelativeTo(pkg)
func RelativeTo(pkg *Package) Qualifier
json.NewEncoder
// 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

Objectast.Identast.Nodetoken.Postoken.Pos

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".

token.Posast.Nodeastutil.PathEnclosingIntervalast.Node*ast.Filetoken.Posloader(*loader.Program).PathEnclosingInterval
ObjectPosPathEnclosingInterval
ast.IdentObjectUsesDefs
ObjectDocParseCommentsdoc