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