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: