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