1// Copyright 2015 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// Only run where builders (build.golang.org) have
6// access to compiled packages for import.
7//
8//go:build !arm && !arm64
9
10package types_test
11
12// This file shows examples of basic usage of the go/types API.
13//
14// To locate a Go package, use (*go/build.Context).Import.
15// To load, parse, and type-check a complete Go program
16// from source, use golang.org/x/tools/go/loader.
17
18import (
19	"bytes"
20	"fmt"
21	"go/ast"
22	"go/format"
23	"go/importer"
24	"go/parser"
25	"go/token"
26	"go/types"
27	"log"
28	"regexp"
29	"sort"
30	"strings"
31)
32
33// ExampleScope prints the tree of Scopes of a package created from a
34// set of parsed files.
35func ExampleScope() {
36	// Parse the source files for a package.
37	fset := token.NewFileSet()
38	var files []*ast.File
39	for _, file := range []struct{ name, input string }{
40		{"main.go", `
41package main
42import "fmt"
43func main() {
44	freezing := FToC(-18)
45	fmt.Println(freezing, Boiling) }
46`},
47		{"celsius.go", `
48package main
49import "fmt"
50type Celsius float64
51func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) }
52func FToC(f float64) Celsius { return Celsius(f - 32 / 9 * 5) }
53const Boiling Celsius = 100
54func Unused() { {}; {{ var x int; _ = x }} } // make sure empty block scopes get printed
55`},
56	} {
57		f, err := parser.ParseFile(fset, file.name, file.input, 0)
58		if err != nil {
59			log.Fatal(err)
60		}
61		files = append(files, f)
62	}
63
64	// Type-check a package consisting of these files.
65	// Type information for the imported "fmt" package
66	// comes from $GOROOT/pkg/$GOOS_$GOOARCH/fmt.a.
67	conf := types.Config{Importer: importer.Default()}
68	pkg, err := conf.Check("temperature", fset, files, nil)
69	if err != nil {
70		log.Fatal(err)
71	}
72
73	// Print the tree of scopes.
74	// For determinism, we redact addresses.
75	var buf bytes.Buffer
76	pkg.Scope().WriteTo(&buf, 0, true)
77	rx := regexp.MustCompile(` 0x[a-fA-F0-9]*`)
78	fmt.Println(rx.ReplaceAllString(buf.String(), ""))
79
80	// Output:
81	// package "temperature" scope {
82	// .  const temperature.Boiling temperature.Celsius
83	// .  type temperature.Celsius float64
84	// .  func temperature.FToC(f float64) temperature.Celsius
85	// .  func temperature.Unused()
86	// .  func temperature.main()
87	// .  main.go scope {
88	// .  .  package fmt
89	// .  .  function scope {
90	// .  .  .  var freezing temperature.Celsius
91	// .  .  }
92	// .  }
93	// .  celsius.go scope {
94	// .  .  package fmt
95	// .  .  function scope {
96	// .  .  .  var c temperature.Celsius
97	// .  .  }
98	// .  .  function scope {
99	// .  .  .  var f float64
100	// .  .  }
101	// .  .  function scope {
102	// .  .  .  block scope {
103	// .  .  .  }
104	// .  .  .  block scope {
105	// .  .  .  .  block scope {
106	// .  .  .  .  .  var x int
107	// .  .  .  .  }
108	// .  .  .  }
109	// .  .  }
110	// .  }
111	// }
112}
113
114// ExampleMethodSet prints the method sets of various types.
115func ExampleMethodSet() {
116	// Parse a single source file.
117	const input = `
118package temperature
119import "fmt"
120type Celsius float64
121func (c Celsius) String() string  { return fmt.Sprintf("%g°C", c) }
122func (c *Celsius) SetF(f float64) { *c = Celsius(f - 32 / 9 * 5) }
123
124type S struct { I; m int }
125type I interface { m() byte }
126`
127	fset := token.NewFileSet()
128	f, err := parser.ParseFile(fset, "celsius.go", input, 0)
129	if err != nil {
130		log.Fatal(err)
131	}
132
133	// Type-check a package consisting of this file.
134	// Type information for the imported packages
135	// comes from $GOROOT/pkg/$GOOS_$GOOARCH/fmt.a.
136	conf := types.Config{Importer: importer.Default()}
137	pkg, err := conf.Check("temperature", fset, []*ast.File{f}, nil)
138	if err != nil {
139		log.Fatal(err)
140	}
141
142	// Print the method sets of Celsius and *Celsius.
143	celsius := pkg.Scope().Lookup("Celsius").Type()
144	for _, t := range []types.Type{celsius, types.NewPointer(celsius)} {
145		fmt.Printf("Method set of %s:\n", t)
146		mset := types.NewMethodSet(t)
147		for i := 0; i < mset.Len(); i++ {
148			fmt.Println(mset.At(i))
149		}
150		fmt.Println()
151	}
152
153	// Print the method set of S.
154	styp := pkg.Scope().Lookup("S").Type()
155	fmt.Printf("Method set of %s:\n", styp)
156	fmt.Println(types.NewMethodSet(styp))
157
158	// Output:
159	// Method set of temperature.Celsius:
160	// method (temperature.Celsius) String() string
161	//
162	// Method set of *temperature.Celsius:
163	// method (*temperature.Celsius) SetF(f float64)
164	// method (*temperature.Celsius) String() string
165	//
166	// Method set of temperature.S:
167	// MethodSet {}
168}
169
170// ExampleInfo prints various facts recorded by the type checker in a
171// types.Info struct: definitions of and references to each named object,
172// and the type, value, and mode of every expression in the package.
173func ExampleInfo() {
174	// Parse a single source file.
175	const input = `
176package fib
177
178type S string
179
180var a, b, c = len(b), S(c), "hello"
181
182func fib(x int) int {
183	if x < 2 {
184		return x
185	}
186	return fib(x-1) - fib(x-2)
187}`
188	fset := token.NewFileSet()
189	f, err := parser.ParseFile(fset, "fib.go", input, 0)
190	if err != nil {
191		log.Fatal(err)
192	}
193
194	// Type-check the package.
195	// We create an empty map for each kind of input
196	// we're interested in, and Check populates them.
197	info := types.Info{
198		Types: make(map[ast.Expr]types.TypeAndValue),
199		Defs:  make(map[*ast.Ident]types.Object),
200		Uses:  make(map[*ast.Ident]types.Object),
201	}
202	var conf types.Config
203	pkg, err := conf.Check("fib", fset, []*ast.File{f}, &info)
204	if err != nil {
205		log.Fatal(err)
206	}
207
208	// Print package-level variables in initialization order.
209	fmt.Printf("InitOrder: %v\n\n", info.InitOrder)
210
211	// For each named object, print the line and
212	// column of its definition and each of its uses.
213	fmt.Println("Defs and Uses of each named object:")
214	usesByObj := make(map[types.Object][]string)
215	for id, obj := range info.Uses {
216		posn := fset.Position(id.Pos())
217		lineCol := fmt.Sprintf("%d:%d", posn.Line, posn.Column)
218		usesByObj[obj] = append(usesByObj[obj], lineCol)
219	}
220	var items []string
221	for obj, uses := range usesByObj {
222		sort.Strings(uses)
223		item := fmt.Sprintf("%s:\n  defined at %s\n  used at %s",
224			types.ObjectString(obj, types.RelativeTo(pkg)),
225			fset.Position(obj.Pos()),
226			strings.Join(uses, ", "))
227		items = append(items, item)
228	}
229	sort.Strings(items) // sort by line:col, in effect
230	fmt.Println(strings.Join(items, "\n"))
231	fmt.Println()
232
233	fmt.Println("Types and Values of each expression:")
234	items = nil
235	for expr, tv := range info.Types {
236		var buf bytes.Buffer
237		posn := fset.Position(expr.Pos())
238		tvstr := tv.Type.String()
239		if tv.Value != nil {
240			tvstr += " = " + tv.Value.String()
241		}
242		// line:col | expr | mode : type = value
243		fmt.Fprintf(&buf, "%2d:%2d | %-19s | %-7s : %s",
244			posn.Line, posn.Column, exprString(fset, expr),
245			mode(tv), tvstr)
246		items = append(items, buf.String())
247	}
248	sort.Strings(items)
249	fmt.Println(strings.Join(items, "\n"))
250
251	// Output:
252	// InitOrder: [c = "hello" b = S(c) a = len(b)]
253	//
254	// Defs and Uses of each named object:
255	// builtin len:
256	//   defined at -
257	//   used at 6:15
258	// func fib(x int) int:
259	//   defined at fib.go:8:6
260	//   used at 12:20, 12:9
261	// type S string:
262	//   defined at fib.go:4:6
263	//   used at 6:23
264	// type int:
265	//   defined at -
266	//   used at 8:12, 8:17
267	// type string:
268	//   defined at -
269	//   used at 4:8
270	// var b S:
271	//   defined at fib.go:6:8
272	//   used at 6:19
273	// var c string:
274	//   defined at fib.go:6:11
275	//   used at 6:25
276	// var x int:
277	//   defined at fib.go:8:10
278	//   used at 10:10, 12:13, 12:24, 9:5
279	//
280	// Types and Values of each expression:
281	//  4: 8 | string              | type    : string
282	//  6:15 | len                 | builtin : func(string) int
283	//  6:15 | len(b)              | value   : int
284	//  6:19 | b                   | var     : fib.S
285	//  6:23 | S                   | type    : fib.S
286	//  6:23 | S(c)                | value   : fib.S
287	//  6:25 | c                   | var     : string
288	//  6:29 | "hello"             | value   : string = "hello"
289	//  8:12 | int                 | type    : int
290	//  8:17 | int                 | type    : int
291	//  9: 5 | x                   | var     : int
292	//  9: 5 | x < 2               | value   : untyped bool
293	//  9: 9 | 2                   | value   : int = 2
294	// 10:10 | x                   | var     : int
295	// 12: 9 | fib                 | value   : func(x int) int
296	// 12: 9 | fib(x - 1)          | value   : int
297	// 12: 9 | fib(x-1) - fib(x-2) | value   : int
298	// 12:13 | x                   | var     : int
299	// 12:13 | x - 1               | value   : int
300	// 12:15 | 1                   | value   : int = 1
301	// 12:20 | fib                 | value   : func(x int) int
302	// 12:20 | fib(x - 2)          | value   : int
303	// 12:24 | x                   | var     : int
304	// 12:24 | x - 2               | value   : int
305	// 12:26 | 2                   | value   : int = 2
306}
307
308func mode(tv types.TypeAndValue) string {
309	switch {
310	case tv.IsVoid():
311		return "void"
312	case tv.IsType():
313		return "type"
314	case tv.IsBuiltin():
315		return "builtin"
316	case tv.IsNil():
317		return "nil"
318	case tv.Assignable():
319		if tv.Addressable() {
320			return "var"
321		}
322		return "mapindex"
323	case tv.IsValue():
324		return "value"
325	default:
326		return "unknown"
327	}
328}
329
330func exprString(fset *token.FileSet, expr ast.Expr) string {
331	var buf bytes.Buffer
332	format.Node(&buf, fset, expr)
333	return buf.String()
334}
335