这里讨论的复合类型是指无法用一个标识符表示的类型,它们包含其它包中的基础类型(需要通过点号选择操作符)、指针类型、 数组类型、切片类型、结构体类型、map类型、管道类型、函数类型和接口类型,以及它们之间再次组合产生的更复杂的类型。
类型定义由*ast.TypeSpec结构体表示,复合类型也是如此
type TypeSpec struct {
Doc *CommentGroup // associated documentation; or nil
Name *Ident // type name
Assign token.Pos // position of '=', if any; added in Go 1.9
Type Expr // *Ident, *ParenExpr, *SelectorExpr, *StarExpr, or any of th *XxxTypes
Comment *CommentGroup // line comments; or nil
}
基础类型
基础类型是最简单的类型,就是基于已有的命名类型再次定义新类型,或者是为已有类型定义新的别名。
func main() {
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "hello.go", src, parser.AllErrors)
if err != nil {
log.Fatal(err)
}
for _, decl := range f.Decls {
ast.Print(nil, decl.(*ast.GenDecl).Specs[0])
}
}
const src = `package foo
type Int1 int
type Int2 pkg.int
`
第一个类型的输出结果如下:
0 *ast.TypeSpec {
1 . Name: *ast.Ident {
2 . . NamePos: 18
3 . . Name: "Int1"
4 . . Obj: *ast.Object {
5 . . . Kind: type
6 . . . Name: "Int1"
7 . . . Decl: *(obj @ 0)
8 . . }
9 . }
10 . Assign: 0
11 . Type: *ast.Ident {
12 . . NamePos: 23
13 . . Name: "int"
14 . }
15 }
第二个类型的输出结果如下:
0 *ast.TypeSpec {
1 . Name: *ast.Ident {
2 . . NamePos: 32
3 . . Name: "Int2"
4 . . Obj: *ast.Object {
5 . . . Kind: type
6 . . . Name: "Int2"
7 . . . Decl: *(obj @ 0)
8 . . }
9 . }
10 . Assign: 0
11 . Type: *ast.SelectorExpr {
12 . . X: *ast.Ident {
13 . . . NamePos: 37
14 . . . Name: "pkg"
15 . . }
16 . . Sel: *ast.Ident {
17 . . . NamePos: 41
18 . . . Name: "int"
19 . . }
20 . }
21 }
对比两个结果可以发现,Int1的Type定义对应的是ast.Ident表示一个标识符,而Int2的Type定义对应的是ast.SelectorExpr表示是其它包的命名类型。*ast.SelectorExp结构体定义如下:
type SelectorExpr struct {
X Expr // expression
Sel *Ident // field selector
}
指针类型
func main() {
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "hello.go", src, parser.AllErrors)
if err != nil {
log.Fatal(err)
}
for _, decl := range f.Decls {
ast.Print(nil, decl.(*ast.GenDecl).Specs[0])
}
}
const src = `package foo
type IntPtr *int
`
--------------------------output-----------------------------
0 *ast.TypeSpec {
1 . Name: *ast.Ident {
2 . . NamePos: 18
3 . . Name: "IntPtr"
4 . . Obj: *ast.Object {
5 . . . Kind: type
6 . . . Name: "IntPtr"
7 . . . Decl: *(obj @ 0)
8 . . }
9 . }
10 . Assign: 0
11 . Type: *ast.StarExpr {
12 . . Star: 25
13 . . X: *ast.Ident {
14 . . . NamePos: 26
15 . . . Name: "int"
16 . . }
17 . }
18 }
新类型的名字依然是普通的ast.Ident标识符类型,其值是新类型的名字“IntPtr”。而ast.TypeSpec.Type成员则是新的ast.StarExpr类型,其结构体定义如下:
type StarExpr struct {
Star token.Pos // position of "*"
X Expr // operand
}
数组类型
type IntArray [1]int
解析结果如下:
0 *ast.TypeSpec {
1 . Name: *ast.Ident {
2 . . NamePos: 18
3 . . Name: "IntArray"
4 . . Obj: *ast.Object {
5 . . . Kind: type
6 . . . Name: "IntArray"
7 . . . Decl: *(obj @ 0)
8 . . }
9 . }
10 . Assign: 0
11 . Type: *ast.ArrayType {
12 . . Lbrack: 27
13 . . Len: *ast.BasicLit {
14 . . . ValuePos: 28
15 . . . Kind: INT
16 . . . Value: "1"
17 . . }
18 . . Elt: *ast.Ident {
19 . . . NamePos: 30
20 . . . Name: "int"
21 . . }
22 . }
23 }
数组的类型主要由ast.ArrayType类型定义。数组的长度是一个ast.BasicLit类型的表达式,也就是长度为1的数组。数组元素的长度是*ast.Ident类型的标识符表示,数组的元素对应int类型。
type ArrayType struct {
Lbrack token.Pos // position of "["
Len Expr // Ellipsis node for [...]T array types, nil for slice types
Elt Expr // element type
}
其中ast.ArrayType.Len成员是一个表示数组长度的表达式,该表达式必须可以产生常量的整数结果(也可以是三个点省略号表示从元素个数提取)。数组的元素由ast.ArrayType.Elt定义,其值对应一个类型表达式。和指针类型一样,数组类型也是可以递归定义的,数组的元素类型可以数数组、指针等其它任何类型。
切片类型
type IntSlice []int
对其解析语法树的输出如下:
0 *ast.TypeSpec {
1 . Name: *ast.Ident {
2 . . NamePos: 18
3 . . Name: "IntSlice"
4 . . Obj: *ast.Object {
5 . . . Kind: type
6 . . . Name: "IntSlice"
7 . . . Decl: *(obj @ 0)
8 . . }
9 . }
10 . Assign: 0
11 . Type: *ast.ArrayType {
12 . . Lbrack: 27
13 . . Elt: *ast.Ident {
14 . . . NamePos: 29
15 . . . Name: "int"
16 . . }
17 . }
18 }
切片和数组一样,也是通过*ast.ArrayType结构表示切片,不过Len长度成员为nil类型(切片必须是nil,如果是0则表示是数组类型)。
结构体类型
结构体类型是数组类型的再次演进:数组是类型相同的元素的组合,并通过下标索引定位元素;而结构体类型是不同类型元素的组合,可以通过名字来定位元素。结构体类型这种可以组合异构元素类型的抽象能力极大地改进了数据结构编程的体验。
结构体通过struct关键字开始定义,然后在大括弧中包含成员的定义。每一个FieldDecl表示一组有着相同类型和Tag字符串的标识符名字,或者是嵌入的匿名类型或类型指针。以下是结构体的例子:
type MyStruct struct {
a, b int "int value"
string
}
其中a和b成员不仅仅有着相同的int类型,同时还有着相同的Tag字符串,最后的成员是嵌入一个匿名的字符串。
对其解析语法树的输出如下(为了简化省略了一些无关的信息):
11 . Type: *ast.StructType {
12 . . Struct: 27
13 . . Fields: *ast.FieldList {
14 . . . Opening: 34
15 . . . List: []*ast.Field (len = 2) {
16 . . . . 0: *ast.Field {
17 . . . . . Names: []*ast.Ident (len = 2) {
18 . . . . . . 0: *ast.Ident {
19 . . . . . . . NamePos: 37
20 . . . . . . . Name: "a"
21 . . . . . . . Obj: *ast.Object {...}
26 . . . . . . }
27 . . . . . . 1: *ast.Ident {
28 . . . . . . . NamePos: 40
29 . . . . . . . Name: "b"
30 . . . . . . . Obj: *ast.Object {...}
35 . . . . . . }
36 . . . . . }
37 . . . . . Type: *ast.Ident {
38 . . . . . . NamePos: 42
39 . . . . . . Name: "int"
40 . . . . . }
41 . . . . . Tag: *ast.BasicLit {
42 . . . . . . ValuePos: 46
43 . . . . . . Kind: STRING
44 . . . . . . Value: "\"int value\""
45 . . . . . }
46 . . . . }
47 . . . . 1: *ast.Field {
48 . . . . . Type: *ast.Ident {
49 . . . . . . NamePos: 59
50 . . . . . . Name: "string"
51 . . . . . }
52 . . . . }
53 . . . }
54 . . . Closing: 66
55 . . }
56 . . Incomplete: false
57 . }
所有的结构体成员由ast.FieldList表示,其中有三个ast.Field元素。第一个ast.Field对应a, b int “int value”的成员声明,包含了成员名字列表、类型和Tag信息。最后的ast.Field是嵌入的string成员,只有普通的名字而没有类型信息(匿名嵌入成员也可以单独定义Tag字符串)。
其中ast.StructType等和结构体相关的语法树结构定义如下:
type StructType struct {
Struct token.Pos // position of "struct" keyword
Fields *FieldList // list of field declarations
Incomplete bool // true if (source) fields are missing in the Fields list
}
StructType中最重要的信息是FieldList类型的Fields成员声明列表信息。而每一组成员声明又由ast.Field表示,其中包含一组成员的名字,共享的类型和Tag字符串。需要注意的是,ast.Field不仅仅用于表示结构体成员的语法树结点,同时也用于表示接口的方法列表、函数或方法的各种参数列表(接收者参数、输入参数和返回值),因此这是一个异常重要的类型。
Map类型
type IntStringMap map[int]string
解析的语法树输出如下:
11 . Type: *ast.MapType {
12 . . Map: 31
13 . . Key: *ast.Ident {
14 . . . NamePos: 35
15 . . . Name: "int"
16 . . }
17 . . Value: *ast.Ident {
18 . . . NamePos: 39
19 . . . Name: "string"
20 . . }
21 . }
下面是ast.MapType语法树结点的定义:
type MapType struct {
Map token.Pos // position of "map" keyword
Key Expr
Value Expr
}
管道类型
在语法树中管道类型由ast.ChanType结构体定义:
type ChanType struct {
Begin token.Pos // position of "chan" keyword or "<-" (whichever comes first)
Arrow token.Pos // position of "<-" (token.NoPos if there is no "<-"); added in Go 1.1
Dir ChanDir // channel direction
Value Expr // value type
}
type ChanDir int
const (
SEND ChanDir = 1 << iota
RECV
)
其中ast.ChanType.Dir是管道的方向,SEND表示发送、RECV表示接收、SEND|RECV比特位组合表示双向管道。下面的例子是一个双向的int管道:
type IntChan chan int
解析的语法树结果如下:
11 . Type: *ast.ChanType {
12 . . Begin: 26
13 . . Arrow: 0
14 . . Dir: 3
15 . . Value: *ast.Ident {
16 . . . NamePos: 31
17 . . . Name: "int"
18 . . }
19 . }
其中ast.ChanType.Dir值是3,也就是SEND|RECV比特位组合,表示这是一个双向管道。而ast.ChanType.Value部分表示管道值的类型,这里是一个ast.Ident表示的int类型。
函数类型
函数类型基本上是函数签名部分,包含函数的输入参数和返回值类型。与函数声明类似,但是函数类型不包含函数的名字。函数类型的语法规范如下:
type FuncType func(a, b int) bool
函数类型中类型部分由ast.FuncType结构体定义。
type FuncType struct {
Func token.Pos // position of "func" keyword (token.NoPos if there is no "func")
TypeParams *FieldList // type parameters; or nil
Params *FieldList // (incoming) parameters; non-nil
Results *FieldList // (outgoing) results; or nil
}
接口类型
从语法结构角度看,接口和结构体类型很像,不过接口的每个成员都是函数类型。
type IntReader interface {
Read() int
}
对齐分析语法树结果如下:
11 . Type: *ast.InterfaceType {
12 . . Interface: 28
13 . . Methods: *ast.FieldList {
14 . . . Opening: 38
15 . . . List: []*ast.Field (len = 1) {
16 . . . . 0: *ast.Field {
17 . . . . . Names: []*ast.Ident (len = 1) {
18 . . . . . . 0: *ast.Ident {
19 . . . . . . . NamePos: 41
20 . . . . . . . Name: "Read"
21 . . . . . . . Obj: *ast.Object {
22 . . . . . . . . Kind: func
23 . . . . . . . . Name: "Read"
24 . . . . . . . . Decl: *(obj @ 16)
25 . . . . . . . }
26 . . . . . . }
27 . . . . . }
28 . . . . . Type: *ast.FuncType {
29 . . . . . . Func: 0
30 . . . . . . Params: *ast.FieldList {
31 . . . . . . . Opening: 45
32 . . . . . . . Closing: 46
33 . . . . . . }
34 . . . . . . Results: *ast.FieldList {
35 . . . . . . . Opening: 0
36 . . . . . . . List: []*ast.Field (len = 1) {
37 . . . . . . . . 0: *ast.Field {
38 . . . . . . . . . Type: *ast.Ident {
39 . . . . . . . . . . NamePos: 48
40 . . . . . . . . . . Name: "int"
41 . . . . . . . . . }
42 . . . . . . . . }
43 . . . . . . . }
44 . . . . . . . Closing: 0
45 . . . . . . }
46 . . . . . }
47 . . . . }
48 . . . }
49 . . . Closing: 52
50 . . }
51 . . Incomplete: false
52 . }
接口的语法树是ast.InterfaceType类型,其Methods成员列表和结构体成员的*ast.FieldList类型一样。下面是ast.InterfaceType和ast.StructType语法树结构的定义:
type InterfaceType struct {
Interface token.Pos // position of "interface" keyword
Methods *FieldList // list of methods
Incomplete bool // true if (source) methods are missing in the Methods list
}
type StructType struct {
Struct token.Pos // position of "struct" keyword
Fields *FieldList // list of field declarations
Incomplete bool // true if (source) fields are missing in the Fields list
}
对比可以发现,接口和结构体语法树结点中除了方法列表和成员列表的名字不同之外,方法和成员都是由ast.FieldList定义的。因此上述的接口例子和下面的结构体其实非常相似:
type IntReader struct {
Read func() int
}
如果是结构体,那么Read成员就是一个函数类型,函数是func() int类型。总之在语法树层面接口和结构体可以采用相似的代码处理。