使用go/ast来解析go文件II

在进行go文件解析经常需要对Ident对象的类型/值进行比较

但go/ast只对单文件进行解析,并不适合用来比较类型,所以这个时候就需要用到另一个库go/types

先来看看全部代码

代码也可以通过这里下载,建议在看教程的同时运行这个demo代码

// implicitly import import “fmt”

import . “unsafe”

type Product struct { Name string }

func (p Product) String() string { return “Product:” + p.Name }

func ImplicitlyNode() { var d interface{} = 5 switch x := d.(type) { case int: fmt.Println(x) default: fmt.Println(x) } var e func(int) fmt.Println(e) }

func SelectionNode() { p := Product{Name: “t011”} fmt.Println(p.Name) fmt.Println(p.String()) fmt.Println(Product.String(p)) fmt.Println(Offsetof(p.Name)) }

const MaxRoutines = 100

var CurrentRoutines = 1

func main() { //test1 fmt.Println(“Hello, World!") //test2 a := []int{1, 2, 3} fmt.Println(a) b := map[int]string{ 1: “a”, 2: “b”, } fmt.Println(b)

d := make(chan int, 5)
fmt.Println(d)

fmt.Println(MaxRoutines)

}

</details>

<details>
<summary>
使用types分析库并打印其中各种信息
</summary>
```golang
package main

import (
	"fmt"
	"go/ast"
	"go/importer"
	"go/parser"
	"go/token"
	"go/types"
	"reflect"
	"sort"
)

// 排序规则order by Pos(), End()
func sortNodes(nodes []ast.Node) {
	sort.Slice(nodes, func(i, j int) bool {
		if nodes[i].Pos() == nodes[j].Pos() {
			return nodes[i].End() < nodes[j].End()
		}
		return nodes[i].Pos() < nodes[j].Pos()
	})
}

// map中的元素是无序的,对key排序打印更好查看
func getSortedKeys(m interface{}) []ast.Node {
	mValue := reflect.ValueOf(m)
	nodes := make([]ast.Node, mValue.Len())
	keys := mValue.MapKeys()
	for i := range keys {
		nodes[i] = keys[i].Interface().(ast.Node)
	}
	sortNodes(nodes)
	return nodes
}

func main() {
	fset := token.NewFileSet() // 位置是相对于节点
	// 用ParseFile把文件解析成*ast.File节点
	f, err := parser.ParseFile(fset, "data/src.go", nil, 0)
	if err != nil {
		panic(err)
	}

	// 使用types check
	// 构造config
	config := types.Config{
		// 加载包的方式,可以通过源码或编译好的包,其中编译好的包分为gc和gccgo,前者应该是
		Importer: importer.For("source", nil),
		// 表示允许包里面加载c库 import "c"
		FakeImportC: true,
	}

	info := &types.Info{
		// 表达式对应的类型
		Types: make(map[ast.Expr]types.TypeAndValue),
		// 被定义的标示符
		Defs: make(map[*ast.Ident]types.Object),
		// 被使用的标示符
		Uses: make(map[*ast.Ident]types.Object),
		// 隐藏节点,匿名import包,type-specific时的case对应的当前类型,声明函数的匿名参数如var func(int)
		Implicits: make(map[ast.Node]types.Object),
		// 选择器,只能针对类型/对象.字段/method的选择,package.API这种不会记录在这里
		Selections: make(map[*ast.SelectorExpr]*types.Selection),
		// scope 记录当前库scope下的所有域,*ast.File/*ast.FuncType/... 都属于scope,详情看Scopes说明
		// scope关系: 最外层Universe scope,之后Package scope,其他子scope
		Scopes: make(map[ast.Node]*types.Scope),
		// 记录所有package级的初始化值
		InitOrder: make([]*types.Initializer, 0, 0),
	}
	// 这里第一个path参数觉得当前pkg前缀,和FileSet的文件路径是无关的
	pkg, err := config.Check("", fset, []*ast.File{f}, info)

	if err != nil {
		panic(err)
	}
	// 打印types
	fmt.Println("------------ types -----------")
	for _, node := range getSortedKeys(info.Types) {
		expr := node.(ast.Expr)
		typeValue := info.Types[expr]
		fmt.Printf("%s - %s %T it's value: %v type: %s\n",
			fset.Position(expr.Pos()),
			fset.Position(expr.End()),
			expr,
			typeValue.Value,
			typeValue.Type.String(),
		)
		if typeValue.Assignable() {
			fmt.Print("assignable ")
		}
		if typeValue.Addressable() {
			fmt.Print("addressable ")
		}
		if typeValue.IsNil() {
			fmt.Print("nil ")
		}
		if typeValue.HasOk() {
			fmt.Print("has ok ")
		}
		if typeValue.IsBuiltin() {
			fmt.Print("builtin ")
		}
		if typeValue.IsType() {
			fmt.Print("is type ")
		}
		if typeValue.IsValue() {
			fmt.Print("is value ")
		}
		if typeValue.IsVoid() {
			fmt.Print("void ")
		}
		fmt.Println()
	}
	// 打印defs
	fmt.Println("------------ def -----------")
	for _, node := range getSortedKeys(info.Defs) {
		ident := node.(*ast.Ident)
		object := info.Defs[ident]
		fmt.Printf("%s - %s %T",
			fset.Position(ident.Pos()),
			fset.Position(ident.End()),
			object,
		)
		if object != nil {
			fmt.Printf(" it's object: %s type: %s",
				object,
				object.Type().String(),
			)

		}
		fmt.Println()
	}
	// 打印Uses
	fmt.Println("------------ uses -----------")
	for _, node := range getSortedKeys(info.Uses) {
		ident := node.(*ast.Ident)
		object := info.Uses[ident]
		fmt.Printf("%s - %s %T",
			fset.Position(ident.Pos()),
			fset.Position(ident.End()),
			object,
		)
		if object != nil {
			fmt.Printf(" it's object: %s type: %s",
				object,
				object.Type().String(),
			)

		}
		fmt.Println()
	}
	// 打印Implicits
	fmt.Println("------------ implicits -----------")
	for _, node := range getSortedKeys(info.Implicits) {
		object := info.Implicits[node]
		fmt.Printf("%s - %s %T it's object: %s\n",
			fset.Position(node.Pos()),
			fset.Position(node.End()),
			node,
			object,
		)
	}
	// 打印Selections
	fmt.Println("------------ selections -----------")
	for _, node := range getSortedKeys(info.Selections) {
		sel := node.(*ast.SelectorExpr)
		typeSel := info.Selections[sel]
		fmt.Printf("%s - %s it's selection: %s\n",
			fset.Position(sel.Pos()),
			fset.Position(sel.End()),
			typeSel.String(),
		)
		fmt.Printf("receive: %s index: %v obj: %s\n", typeSel.Recv(), typeSel.Index(), typeSel.Obj())
	}
	// 打印Scopes
	fmt.Println("------------ scopes -----------")
	//打印package scope
	fmt.Printf("package level scope %s\n",
		pkg.Scope().String(),
	)
	// 打印宇宙级scope
	fmt.Printf("universe level scope %s\n",
		pkg.Scope().Parent().String(),
	)
	for _, node := range getSortedKeys(info.Scopes) {
		scope := info.Scopes[node]
		fmt.Printf("%s - %s %T it's scope %s\n",
			fset.Position(node.Pos()),
			fset.Position(node.End()),
			node,
			scope.String(),
		)
	}
	// 打印InitOrder
	fmt.Println("------------ init -----------")
	for _, init := range info.InitOrder {
		fmt.Printf("init %s\n",
			init.String(),
		)
	}
}

构建types.Config

types.Config决定如何去解析go代码,这里**importer.For(“source”, nil)**表示通过源码的方式解析go库,FakeImportC表示允许加载C库

创建types.Info

types.Info决定要解析哪些信息,给字段赋值后在Config.Check是会对这部分信息进行解析

Types保存表达式对应的类型,
Defs保存所有被定义的标示符,包括package name(包名)和带名字的加载库(import _ "package" / import . "package")
Uses保存所有被使用的标示符
Implicits保存三种隐藏节点,匿名import 的库(import "package"), type-specific时的case对应类型(switch t := x.(type){case int:}中case节点映射的t类型)
Selections保存所有类选择器,只能针对类型/对象.字段/method的选择,package.API这种不会记录在这里
Scopes保存当前库scope下的所有域,*ast.File/*ast.FuncType/... 都属于scope,最外层Universe scope,之后Package scope,其他子scope
InitOrder保存所有最外部初始化的值

config.Check

使用config.Check会填充types.Info的内容

这里第一个path参数决定当前pkg前缀,和FileSet的文件路径是无关的,[]*ast.File{f}是要解析的go文件,这些go文件必须是同一个pkg的文件

pkg, err := config.Check("", fset, []*ast.File{f}, info)

pkg包含顶级scope和包名信息,err则是解析文件的语法错误

config.Check过后我们就可以通过types.Info来读取go文件信息了

types.Info.Types

types.Info.Types 映射ast.Expr(不包括在types.Info.Defs和types.Info.Users中的*ast.Ident)到types.TypeAndValue类型

types.TypeAndValue解析expr的类型和它的额外属性,如果expr是一个常量值,TypeAndValue也会解析它的Value

我们来打印一下types.Info.Types的内容

fmt.Println("------------ types -----------")
for _, node := range getSortedKeys(info.Types) {
	expr := node.(ast.Expr)
	typeValue := info.Types[expr]
	fmt.Printf("%s - %s %T it's value: %v type: %s\n",
		fset.Position(expr.Pos()),
		fset.Position(expr.End()),
		expr,
		typeValue.Value,
		typeValue.Type.String(),
	)
	if typeValue.Assignable() {
		fmt.Print("assignable ")
	}
	if typeValue.Addressable() {
		fmt.Print("addressable ")
	}
	if typeValue.IsNil() {
		fmt.Print("nil ")
	}
	if typeValue.HasOk() {
		fmt.Print("has ok ")
	}
	if typeValue.IsBuiltin() {
		fmt.Print("builtin ")
	}
	if typeValue.IsType() {
		fmt.Print("is type ")
	}
	if typeValue.IsValue() {
		fmt.Print("is value ")
	}
	if typeValue.IsVoid() {
		fmt.Print("void ")
	}
	fmt.Println()
}

types.Info.Defs

Defs保存所有定义的*ast.Ident,并映射为types.Object

types.Object可以是一个包名,函数,常量,变量,或者标签的接口

当前包的package name也保存在Defs表中,不过它的Object是nil,不知道是不是bug

打印一下Defs的内容

fmt.Println("------------ def -----------")
for _, node := range getSortedKeys(info.Defs) {
	ident := node.(*ast.Ident)
	object := info.Defs[ident]
	fmt.Printf("%s - %s %T",
		fset.Position(ident.Pos()),
		fset.Position(ident.End()),
		object,
	)
	if object != nil {
		fmt.Printf(" it's object: %s type: %s",
			object,
			object.Type().String(),
		)

	}
	fmt.Println()
}

types.Info.Uses

Uses保存所有使用的*ast.Ident,并映射为types.Object

基本在Uses中的Ident也会出现在Types里,这部分逻辑很迷

打印Uses

fmt.Println("------------ uses -----------")
for _, node := range getSortedKeys(info.Uses) {
	ident := node.(*ast.Ident)
	object := info.Uses[ident]
	fmt.Printf("%s - %s %T",
		fset.Position(ident.Pos()),
		fset.Position(ident.End()),
		object,
	)
	if object != nil {
		fmt.Printf(" it's object: %s type: %s",
			object,
			object.Type().String(),
		)

	}
	fmt.Println()
}

types.Info.Implicits

Implicits保存各种隐藏声明的节点,以下节点可能出现在Implicits表中

没有包名的import

type-specific的swtich语句

声明函数时的匿名参数

这个example已经把实现了所有隐藏声明代码,来看看哪些节点会出现在这里

fmt.Println("------------ implicits -----------")
for _, node := range getSortedKeys(info.Implicits) {
	object := info.Implicits[node]
	fmt.Printf("%s - %s %T it's object: %s\n",
		fset.Position(node.Pos()),
		fset.Position(node.End()),
		node,
		object,
	)
}

types.Info.Selections

Selections保存所有结构体选择表达式像 x.f,它会把*ast.SelectorExpr映射成*types.Selection,只有类成员的使用表达式会被映射,库成员的使用则不会

types.Selection有3中类型,对应3种调用方式(这里参考了types源码)

//	type T struct{ x int; E }
//	type E struct{}
//	func (e E) m() {}
//	var p *T
//
// the following relations exist:
//
//	Selector    Kind          Recv    Obj    Type               Index     Indirect
//
//	p.x         FieldVal      T       x      int                {0}       true
//	p.m         MethodVal     *T      m      func (e *T) m()    {1, 0}    true
//	T.m         MethodExpr    T       m      func m(_ T)        {1, 0}    false
//
fmt.Println("------------ selections -----------")
for _, node := range getSortedKeys(info.Selections) {
	sel := node.(*ast.SelectorExpr)
	typeSel := info.Selections[sel]
	fmt.Printf("%s - %s it's selection: %s\n",
		fset.Position(sel.Pos()),
		fset.Position(sel.End()),
		typeSel.String(),
	)
	fmt.Printf("receive: %s index: %v obj: %s\n", typeSel.Recv(), typeSel.Index(), typeSel.Obj())
}

types.Info.Scope

scope 记录当前库scope下的所有域,*ast.File/*ast.FuncType/… 都属于scope,详情看Scopes说明

scope关系: 最外层Universe scope,之后Package scope,其他子scope

打印Universe scope的内容和Package scope的内容

// 打印宇宙级scope
//打印package scope
fmt.Printf("package level scope %s\n",
	pkg.Scope().String(),
)
// 打印宇宙级scope
fmt.Printf("universe level scope %s\n",
	pkg.Scope().Parent().String(),
)

打印所有scope(types.Info.Scope不会包含Package scope和Universe scope)

for _, node := range getSortedKeys(info.Scopes) {
	scope := info.Scopes[node]
	fmt.Printf("%s - %s %T it's scope: %s\n",
		fset.Position(node.Pos()),
		fset.Position(node.End()),
		node,
		scope.String(),
	)
}

data/src.go:19:2 - data/src.go:20:17 *ast.CaseClause it’s scope: case scope 0xc421389360 { . var x int } data/src.go:21:2 - data/src.go:22:17 *ast.CaseClause it’s scope: case scope 0xc421389400 { . var x interface{} } data/src.go:24:8 - data/src.go:24:17 *ast.FuncType it’s scope: function scope 0xc4213894f0 {}

data/src.go:28:1 - data/src.go:28:21 *ast.FuncType it’s scope: function scope 0xc421389180 { . var p Product } data/src.go:40:1 - data/src.go:40:12 *ast.FuncType it’s scope: function scope 0xc4213891d0 { . var a []int . var b map[int]string . var d chan int }

</details>


#### types.Info.InitOrder

InitOrder保存当前库最外层的变量


打印当前所有InitOrder内容

```golang
fmt.Println("------------ init -----------")
for _, init := range info.InitOrder {
	fmt.Printf("init %s\n",
		init.String(),
	)
}

types.Object比较

可以使用Object.Pos()模块查找这个Object的声明时的位置,从而比较哪些Object属于同一个对象

types.Type比较

可以通过Type.String()来比较这2个Type是不是相同的类型

总结

以上就是就是go/types的全部内容了,下一章继续介绍go/types中实现的Type类型