| <!-- 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). |