x/tools: support Go 1.9 type aliases
For #18130.
Change-Id: Ice695602619dbbf851af970e790f07ff2ac2c141
Reviewed-on: https://go-review.googlesource.com/36623
Reviewed-by: Robert Griesemer <gri@golang.org>
diff --git a/cmd/guru/describe.go b/cmd/guru/describe.go
index 16f37c6..dd32911 100644
--- a/cmd/guru/describe.go
+++ b/cmd/guru/describe.go
@@ -327,9 +327,9 @@
return nil, fmt.Errorf("unexpected AST for expr: %T", n)
}
- t := qpos.info.TypeOf(expr)
- if t == nil {
- t = types.Typ[types.Invalid]
+ typ := qpos.info.TypeOf(expr)
+ if typ == nil {
+ typ = types.Typ[types.Invalid]
}
constVal := qpos.info.Types[expr].Value
if c, ok := obj.(*types.Const); ok {
@@ -339,11 +339,11 @@
return &describeValueResult{
qpos: qpos,
expr: expr,
- typ: t,
+ typ: typ,
constVal: constVal,
obj: obj,
- methods: accessibleMethods(t, qpos.info.Pkg),
- fields: accessibleFields(t, qpos.info.Pkg),
+ methods: accessibleMethods(typ, qpos.info.Pkg),
+ fields: accessibleFields(typ, qpos.info.Pkg),
}, nil
}
@@ -425,48 +425,46 @@
func describeType(qpos *queryPos, path []ast.Node) (*describeTypeResult, error) {
var description string
- var t types.Type
+ var typ types.Type
switch n := path[0].(type) {
case *ast.Ident:
- t = qpos.info.TypeOf(n)
- switch t := t.(type) {
- case *types.Basic:
+ obj := qpos.info.ObjectOf(n).(*types.TypeName)
+ typ = obj.Type()
+ if isAlias(obj) {
+ description = "alias of "
+ } else if obj.Pos() == n.Pos() {
+ description = "definition of " // (Named type)
+ } else if _, ok := typ.(*types.Basic); ok {
description = "reference to built-in "
-
- case *types.Named:
- isDef := t.Obj().Pos() == n.Pos() // see caveats at isDef above
- if isDef {
- description = "definition of "
- } else {
- description = "reference to "
- }
+ } else {
+ description = "reference to " // (Named type)
}
case ast.Expr:
- t = qpos.info.TypeOf(n)
+ typ = qpos.info.TypeOf(n)
default:
// Unreachable?
return nil, fmt.Errorf("unexpected AST for type: %T", n)
}
- description = description + "type " + qpos.typeString(t)
+ description = description + "type " + qpos.typeString(typ)
// Show sizes for structs and named types (it's fairly obvious for others).
- switch t.(type) {
+ switch typ.(type) {
case *types.Named, *types.Struct:
szs := types.StdSizes{WordSize: 8, MaxAlign: 8} // assume amd64
description = fmt.Sprintf("%s (size %d, align %d)", description,
- szs.Sizeof(t), szs.Alignof(t))
+ szs.Sizeof(typ), szs.Alignof(typ))
}
return &describeTypeResult{
qpos: qpos,
node: path[0],
description: description,
- typ: t,
- methods: accessibleMethods(t, qpos.info.Pkg),
- fields: accessibleFields(t, qpos.info.Pkg),
+ typ: typ,
+ methods: accessibleMethods(typ, qpos.info.Pkg),
+ fields: accessibleFields(typ, qpos.info.Pkg),
}, nil
}
@@ -672,23 +670,29 @@
fmt.Fprintf(&buf, " %s", types.TypeString(obj.Type(), qualifier))
case *types.TypeName:
+ typ := obj.Type()
+ if isAlias(obj) {
+ buf.WriteString(" = ")
+ } else {
+ buf.WriteByte(' ')
+ typ = typ.Underlying()
+ }
+ var typestr string
// Abbreviate long aggregate type names.
- var abbrev string
- switch t := obj.Type().Underlying().(type) {
+ switch typ := typ.(type) {
case *types.Interface:
- if t.NumMethods() > 1 {
- abbrev = "interface{...}"
+ if typ.NumMethods() > 1 {
+ typestr = "interface{...}"
}
case *types.Struct:
- if t.NumFields() > 1 {
- abbrev = "struct{...}"
+ if typ.NumFields() > 1 {
+ typestr = "struct{...}"
}
}
- if abbrev == "" {
- fmt.Fprintf(&buf, " %s", types.TypeString(obj.Type().Underlying(), qualifier))
- } else {
- fmt.Fprintf(&buf, " %s", abbrev)
+ if typestr == "" {
+ typestr = types.TypeString(typ, qualifier)
}
+ buf.WriteString(typestr)
case *types.Var:
fmt.Fprintf(&buf, " %s", types.TypeString(obj.Type(), qualifier))
@@ -699,20 +703,26 @@
func (r *describePackageResult) JSON(fset *token.FileSet) []byte {
var members []*serial.DescribeMember
for _, mem := range r.members {
- typ := mem.obj.Type()
+ obj := mem.obj
+ typ := obj.Type()
var val string
- switch mem := mem.obj.(type) {
+ var alias string
+ switch obj := obj.(type) {
case *types.Const:
- val = mem.Val().String()
+ val = obj.Val().String()
case *types.TypeName:
- typ = typ.Underlying()
+ if isAlias(obj) {
+ alias = "= " // kludgy
+ } else {
+ typ = typ.Underlying()
+ }
}
members = append(members, &serial.DescribeMember{
- Name: mem.obj.Name(),
- Type: typ.String(),
+ Name: obj.Name(),
+ Type: alias + typ.String(),
Value: val,
- Pos: fset.Position(mem.obj.Pos()).String(),
- Kind: tokenOf(mem.obj),
+ Pos: fset.Position(obj.Pos()).String(),
+ Kind: tokenOf(obj),
Methods: methodsToSerial(r.pkg, mem.methods, fset),
})
}
diff --git a/cmd/guru/guru_test.go b/cmd/guru/guru_test.go
index 003183a..ac185fd 100644
--- a/cmd/guru/guru_test.go
+++ b/cmd/guru/guru_test.go
@@ -218,6 +218,7 @@
}
for _, filename := range []string{
+ "testdata/src/alias/alias.go", // iff guru.HasAlias (go1.9)
"testdata/src/calls/main.go",
"testdata/src/describe/main.go",
"testdata/src/freevars/main.go",
@@ -248,6 +249,9 @@
// wording for a "no such file or directory" error.
continue
}
+ if filename == "testdata/src/alias/alias.go" && !guru.HasAlias {
+ continue
+ }
json := strings.Contains(filename, "-json/")
queries := parseQueries(t, filename)
diff --git a/cmd/guru/implements.go b/cmd/guru/implements.go
index b7c2962..2d89b2d 100644
--- a/cmd/guru/implements.go
+++ b/cmd/guru/implements.go
@@ -102,16 +102,20 @@
}
// Find all named types, even local types (which can have
- // methods via promotion) and the built-in "error".
- var allNamed []types.Type
+ // methods due to promotion) and the built-in "error".
+ // We ignore aliases 'type M = N' to avoid duplicate
+ // reporting of the Named type N.
+ var allNamed []*types.Named
for _, info := range lprog.AllPackages {
for _, obj := range info.Defs {
- if obj, ok := obj.(*types.TypeName); ok {
- allNamed = append(allNamed, obj.Type())
+ if obj, ok := obj.(*types.TypeName); ok && !isAlias(obj) {
+ if named, ok := obj.Type().(*types.Named); ok {
+ allNamed = append(allNamed, named)
+ }
}
}
}
- allNamed = append(allNamed, types.Universe.Lookup("error").Type())
+ allNamed = append(allNamed, types.Universe.Lookup("error").Type().(*types.Named))
var msets typeutil.MethodSetCache
diff --git a/cmd/guru/isAlias18.go b/cmd/guru/isAlias18.go
new file mode 100644
index 0000000..6a2a4c0
--- /dev/null
+++ b/cmd/guru/isAlias18.go
@@ -0,0 +1,15 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !go1.9
+
+package main
+
+import "go/types"
+
+func isAlias(obj *types.TypeName) bool {
+ return false // there are no type aliases before Go 1.9
+}
+
+const HasAlias = false
diff --git a/cmd/guru/isAlias19.go b/cmd/guru/isAlias19.go
new file mode 100644
index 0000000..25096ab
--- /dev/null
+++ b/cmd/guru/isAlias19.go
@@ -0,0 +1,15 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build go1.9
+
+package main
+
+import "go/types"
+
+func isAlias(obj *types.TypeName) bool {
+ return obj.IsAlias()
+}
+
+const HasAlias = true
diff --git a/cmd/guru/testdata/src/alias/alias.go b/cmd/guru/testdata/src/alias/alias.go
new file mode 100644
index 0000000..18487c0
--- /dev/null
+++ b/cmd/guru/testdata/src/alias/alias.go
@@ -0,0 +1,20 @@
+// Tests of Go 1.9 type aliases.
+// See go.tools/guru/guru_test.go for explanation.
+// See alias.golden for expected query results.
+
+package alias // @describe describe-pkg "alias"
+
+type I interface{ f() } // @implements implements-I "I"
+
+type N int
+func (N) f() {}
+
+type M = N // @describe describe-def-M "M"
+var m M // @describe describe-ref-M "M"
+
+type O N // @describe describe-O "O"
+
+type P = struct{N} // @describe describe-P "N"
+
+type U = undefined // @describe describe-U "U"
+type _ = undefined // @describe describe-undefined "undefined"
diff --git a/cmd/guru/testdata/src/alias/alias.golden b/cmd/guru/testdata/src/alias/alias.golden
new file mode 100644
index 0000000..b5ba46e
--- /dev/null
+++ b/cmd/guru/testdata/src/alias/alias.golden
@@ -0,0 +1,47 @@
+-------- @describe describe-pkg --------
+definition of package "alias"
+ type I interface{f()}
+ method (I) f()
+ type M = N
+ method (N) f()
+ type N int
+ method (N) f()
+ type O int
+ type P = struct{N}
+ method (struct{N}) f()
+ type U = invalid type
+ var m N
+
+-------- @implements implements-I --------
+interface type I
+ is implemented by basic type N
+
+-------- @describe describe-def-M --------
+alias of type N (size 8, align 8)
+defined as int
+Methods:
+ method (N) f()
+
+-------- @describe describe-ref-M --------
+alias of type N (size 8, align 8)
+defined as int
+Methods:
+ method (N) f()
+
+-------- @describe describe-O --------
+definition of type O (size 8, align 8)
+No methods.
+
+-------- @describe describe-P --------
+type struct{N} (size 8, align 8)
+Methods:
+ method (struct{N}) f()
+Fields:
+ N N
+
+-------- @describe describe-U --------
+alias of type invalid type
+
+-------- @describe describe-undefined --------
+identifier
+
diff --git a/cmd/guru/testdata/src/alias/main.golden b/cmd/guru/testdata/src/alias/main.golden
deleted file mode 100644
index fcd7c39..0000000
--- a/cmd/guru/testdata/src/alias/main.golden
+++ /dev/null
@@ -1,54 +0,0 @@
--------- @describe pkg --------
-definition of package "alias"
- type S1 struct{aliaslib.T}
- method (S1) Method(x *int) *int
- type S2 struct{aliaslib.T}
- method (S2) Method(x *int) *int
- alias bad1 => ?
- alias bad2 => ?
- const c_ => aliaslib.C
- func f_ => aliaslib.F
- type t_ => aliaslib.T
- var v_ => aliaslib.V
- var x aliaslib.T
-
--------- @describe bad1 --------
-identifier
-
--------- @describe bad2 --------
-identifier
-
--------- @describe v --------
-definition of var alias v_ int
-
--------- @describe t --------
-alias of type aliaslib.T (size 8, align 8)
-defined as int
-Methods:
- method (T) Method(x *int) *int
-
--------- @describe c --------
-definition of const alias c_ untyped int of value 3
-
--------- @describe f --------
-definition of func alias f_ func()
-
--------- @describe s1-field --------
-reference to field T aliaslib.T
-defined here
-Methods:
- method (T) Method(x *int) *int
-
--------- @describe s2-field --------
-type struct{aliaslib.T} (size 8, align 8)
-Methods:
- method (struct{T}) Method(x *int) *int
-Fields:
- t_ aliaslib.T
-
--------- @describe var-x --------
-alias of type aliaslib.T (size 8, align 8)
-defined as int
-Methods:
- method (T) Method(x *int) *int
-
diff --git a/cmd/guru/testdata/src/aliaslib/aliaslib.go b/cmd/guru/testdata/src/aliaslib/aliaslib.go
deleted file mode 100644
index 4f07a42..0000000
--- a/cmd/guru/testdata/src/aliaslib/aliaslib.go
+++ /dev/null
@@ -1,11 +0,0 @@
-package aliaslib
-
-type T int
-
-func (T) Method(x *int) *int
-
-func F()
-
-const C = 3
-
-var V = 0
diff --git a/cmd/guru/whicherrs.go b/cmd/guru/whicherrs.go
index 37073c1..3a81bf5 100644
--- a/cmd/guru/whicherrs.go
+++ b/cmd/guru/whicherrs.go
@@ -181,11 +181,9 @@
// typeswitch or assert to. This means finding out
// if the type pointed to can be seen by us.
//
- // For the purposes of this analysis, the type is always
- // either a Named type or a pointer to one.
- // There are cases where error can be implemented
- // by unnamed types, but in that case, we can't assert to
- // it, so we don't care about it for this analysis.
+ // For the purposes of this analysis, we care only about
+ // TypeNames of Named or pointer-to-Named types.
+ // We ignore other types (e.g. structs) that implement error.
var name *types.TypeName
switch t := conc.(type) {
case *types.Pointer: