summaryrefslogtreecommitdiff
path: root/src/cmd/api
diff options
context:
space:
mode:
authorMichael Stapelberg <stapelberg@debian.org>2013-03-04 21:27:36 +0100
committerMichael Stapelberg <michael@stapelberg.de>2013-03-04 21:27:36 +0100
commit04b08da9af0c450d645ab7389d1467308cfc2db8 (patch)
treedb247935fa4f2f94408edc3acd5d0d4f997aa0d8 /src/cmd/api
parent917c5fb8ec48e22459d77e3849e6d388f93d3260 (diff)
downloadgolang-upstream/1.1_hg20130304.tar.gz
Imported Upstream version 1.1~hg20130304upstream/1.1_hg20130304
Diffstat (limited to 'src/cmd/api')
-rw-r--r--src/cmd/api/clone.go251
-rw-r--r--src/cmd/api/goapi.go225
-rw-r--r--src/cmd/api/goapi_test.go66
-rw-r--r--src/cmd/api/testdata/src/pkg/p1/golden.txt16
-rw-r--r--src/cmd/api/testdata/src/pkg/p1/p1.go50
-rw-r--r--src/cmd/api/testdata/src/pkg/p3/golden.txt3
-rw-r--r--src/cmd/api/testdata/src/pkg/p3/p3.go6
7 files changed, 550 insertions, 67 deletions
diff --git a/src/cmd/api/clone.go b/src/cmd/api/clone.go
new file mode 100644
index 000000000..180215f4b
--- /dev/null
+++ b/src/cmd/api/clone.go
@@ -0,0 +1,251 @@
+// Copyright 2012 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.
+
+package main
+
+import (
+ "fmt"
+ "go/ast"
+ "log"
+ "reflect"
+)
+
+const debugClone = false
+
+// TODO(bradfitz): delete this function (and whole file) once
+// http://golang.org/issue/4380 is fixed.
+func clone(i interface{}) (cloned interface{}) {
+ if debugClone {
+ defer func() {
+ if !reflect.DeepEqual(i, cloned) {
+ log.Printf("cloned %T doesn't match: in=%#v out=%#v", i, i, cloned)
+ }
+ }()
+ }
+ switch v := i.(type) {
+ case nil:
+ return nil
+ case *ast.File:
+ o := &ast.File{
+ Doc: v.Doc, // shallow
+ Package: v.Package,
+ Comments: v.Comments, // shallow
+ Name: v.Name,
+ Scope: v.Scope,
+ }
+ for _, x := range v.Decls {
+ o.Decls = append(o.Decls, clone(x).(ast.Decl))
+ }
+ for _, x := range v.Imports {
+ o.Imports = append(o.Imports, clone(x).(*ast.ImportSpec))
+ }
+ for _, x := range v.Unresolved {
+ o.Unresolved = append(o.Unresolved, x)
+ }
+ return o
+ case *ast.GenDecl:
+ o := new(ast.GenDecl)
+ *o = *v
+ o.Specs = nil
+ for _, x := range v.Specs {
+ o.Specs = append(o.Specs, clone(x).(ast.Spec))
+ }
+ return o
+ case *ast.TypeSpec:
+ o := new(ast.TypeSpec)
+ *o = *v
+ o.Type = cloneExpr(v.Type)
+ return o
+ case *ast.InterfaceType:
+ o := new(ast.InterfaceType)
+ *o = *v
+ o.Methods = clone(v.Methods).(*ast.FieldList)
+ return o
+ case *ast.FieldList:
+ if v == nil {
+ return v
+ }
+ o := new(ast.FieldList)
+ *o = *v
+ o.List = nil
+ for _, x := range v.List {
+ o.List = append(o.List, clone(x).(*ast.Field))
+ }
+ return o
+ case *ast.Field:
+ o := &ast.Field{
+ Doc: v.Doc, // shallow
+ Type: cloneExpr(v.Type),
+ Tag: clone(v.Tag).(*ast.BasicLit),
+ Comment: v.Comment, // shallow
+ }
+ for _, x := range v.Names {
+ o.Names = append(o.Names, clone(x).(*ast.Ident))
+ }
+ return o
+ case *ast.FuncType:
+ if v == nil {
+ return v
+ }
+ return &ast.FuncType{
+ Func: v.Func,
+ Params: clone(v.Params).(*ast.FieldList),
+ Results: clone(v.Results).(*ast.FieldList),
+ }
+ case *ast.FuncDecl:
+ if v == nil {
+ return v
+ }
+ return &ast.FuncDecl{
+ Recv: clone(v.Recv).(*ast.FieldList),
+ Name: v.Name,
+ Type: clone(v.Type).(*ast.FuncType),
+ Body: v.Body, // shallow
+ }
+ case *ast.ValueSpec:
+ if v == nil {
+ return v
+ }
+ o := &ast.ValueSpec{
+ Type: cloneExpr(v.Type),
+ }
+ for _, x := range v.Names {
+ o.Names = append(o.Names, x)
+ }
+ for _, x := range v.Values {
+ o.Values = append(o.Values, cloneExpr(x))
+ }
+ return o
+ case *ast.CallExpr:
+ if v == nil {
+ return v
+ }
+ o := &ast.CallExpr{}
+ *o = *v
+ o.Args = cloneExprs(v.Args)
+ o.Fun = cloneExpr(v.Fun)
+ return o
+ case *ast.SelectorExpr:
+ if v == nil {
+ return nil
+ }
+ return &ast.SelectorExpr{
+ X: cloneExpr(v.X),
+ Sel: v.Sel,
+ }
+ case *ast.ArrayType:
+ return &ast.ArrayType{
+ Lbrack: v.Lbrack,
+ Len: cloneExpr(v.Len),
+ Elt: cloneExpr(v.Elt),
+ }
+ case *ast.StructType:
+ return &ast.StructType{
+ Struct: v.Struct,
+ Fields: clone(v.Fields).(*ast.FieldList),
+ Incomplete: v.Incomplete,
+ }
+ case *ast.StarExpr:
+ return &ast.StarExpr{
+ Star: v.Star,
+ X: cloneExpr(v.X),
+ }
+ case *ast.CompositeLit:
+ return &ast.CompositeLit{
+ Type: cloneExpr(v.Type),
+ Lbrace: v.Lbrace,
+ Elts: cloneExprs(v.Elts),
+ Rbrace: v.Rbrace,
+ }
+ case *ast.UnaryExpr:
+ return &ast.UnaryExpr{
+ OpPos: v.OpPos,
+ Op: v.Op,
+ X: cloneExpr(v.X),
+ }
+ case *ast.BinaryExpr:
+ return &ast.BinaryExpr{
+ OpPos: v.OpPos,
+ Op: v.Op,
+ X: cloneExpr(v.X),
+ Y: cloneExpr(v.Y),
+ }
+ case *ast.Ellipsis:
+ return &ast.Ellipsis{
+ Ellipsis: v.Ellipsis,
+ Elt: cloneExpr(v.Elt),
+ }
+ case *ast.KeyValueExpr:
+ return &ast.KeyValueExpr{
+ Key: cloneExpr(v.Key),
+ Colon: v.Colon,
+ Value: cloneExpr(v.Value),
+ }
+ case *ast.FuncLit:
+ return &ast.FuncLit{
+ Type: clone(v.Type).(*ast.FuncType),
+ Body: v.Body, // shallow
+ }
+ case *ast.MapType:
+ return &ast.MapType{
+ Map: v.Map,
+ Key: cloneExpr(v.Key),
+ Value: cloneExpr(v.Value),
+ }
+ case *ast.ParenExpr:
+ return &ast.ParenExpr{
+ Lparen: v.Lparen,
+ X: cloneExpr(v.X),
+ Rparen: v.Rparen,
+ }
+ case *ast.Ident, *ast.BasicLit:
+ return v
+ case *ast.ImportSpec:
+ return &ast.ImportSpec{
+ Doc: v.Doc, // shallow
+ Name: v.Name,
+ Path: clone(v.Path).(*ast.BasicLit),
+ Comment: v.Comment, // shallow
+ EndPos: v.EndPos,
+ }
+ case *ast.ChanType:
+ return &ast.ChanType{
+ Begin: v.Begin,
+ Arrow: v.Arrow,
+ Dir: v.Dir,
+ Value: cloneExpr(v.Value),
+ }
+ case *ast.TypeAssertExpr:
+ return &ast.TypeAssertExpr{
+ X: cloneExpr(v.X),
+ Type: cloneExpr(v.Type),
+ }
+ case *ast.IndexExpr:
+ return &ast.IndexExpr{
+ X: cloneExpr(v.X),
+ Index: cloneExpr(v.Index),
+ Lbrack: v.Lbrack,
+ Rbrack: v.Rbrack,
+ }
+ }
+ panic(fmt.Sprintf("Uncloneable type %T", i))
+}
+
+func cloneExpr(x ast.Expr) ast.Expr {
+ if x == nil {
+ return nil
+ }
+ return clone(x).(ast.Expr)
+}
+
+func cloneExprs(x []ast.Expr) []ast.Expr {
+ if x == nil {
+ return nil
+ }
+ o := make([]ast.Expr, len(x))
+ for i, x := range x {
+ o[i] = cloneExpr(x)
+ }
+ return o
+}
diff --git a/src/cmd/api/goapi.go b/src/cmd/api/goapi.go
index ad1c6bb8c..0d76b0cdb 100644
--- a/src/cmd/api/goapi.go
+++ b/src/cmd/api/goapi.go
@@ -22,12 +22,14 @@ import (
"go/parser"
"go/printer"
"go/token"
+ "io"
"io/ioutil"
"log"
"os"
"os/exec"
"path"
"path/filepath"
+ "regexp"
"runtime"
"sort"
"strconv"
@@ -38,11 +40,12 @@ import (
var (
// TODO(bradfitz): once Go 1.1 comes out, allow the -c flag to take a comma-separated
// list of files, rather than just one.
- checkFile = flag.String("c", "", "optional filename to check API against")
- allowNew = flag.Bool("allow_new", true, "allow API additions")
- nextFile = flag.String("next", "", "optional filename of tentative upcoming API features for the next release. This file can be lazily maintained. It only affects the delta warnings from the -c file printed on success.")
- verbose = flag.Bool("v", false, "verbose debugging")
- forceCtx = flag.String("contexts", "", "optional comma-separated list of <goos>-<goarch>[-cgo] to override default contexts.")
+ checkFile = flag.String("c", "", "optional filename to check API against")
+ allowNew = flag.Bool("allow_new", true, "allow API additions")
+ exceptFile = flag.String("except", "", "optional filename of packages that are allowed to change without triggering a failure in the tool")
+ nextFile = flag.String("next", "", "optional filename of tentative upcoming API features for the next release. This file can be lazily maintained. It only affects the delta warnings from the -c file printed on success.")
+ verbose = flag.Bool("v", false, "verbose debugging")
+ forceCtx = flag.String("contexts", "", "optional comma-separated list of <goos>-<goarch>[-cgo] to override default contexts.")
)
// contexts are the default contexts which are scanned, unless
@@ -100,7 +103,7 @@ func setContexts() {
func main() {
flag.Parse()
- if !strings.Contains(runtime.Version(), "weekly") {
+ if !strings.Contains(runtime.Version(), "weekly") && !strings.Contains(runtime.Version(), "devel") {
if *nextFile != "" {
fmt.Printf("Go version is %q, ignoring -next %s\n", runtime.Version(), *nextFile)
*nextFile = ""
@@ -166,7 +169,6 @@ func main() {
features = append(features, f2)
}
}
- sort.Strings(features)
fail := false
defer func() {
@@ -179,24 +181,45 @@ func main() {
defer bw.Flush()
if *checkFile == "" {
+ sort.Strings(features)
for _, f := range features {
fmt.Fprintf(bw, "%s\n", f)
}
return
}
- var required []string
- for _, filename := range []string{*checkFile} {
- required = append(required, fileFeatures(filename)...)
+ required := fileFeatures(*checkFile)
+ optional := fileFeatures(*nextFile)
+ exception := fileFeatures(*exceptFile)
+ fail = !compareAPI(bw, features, required, optional, exception)
+}
+
+func set(items []string) map[string]bool {
+ s := make(map[string]bool)
+ for _, v := range items {
+ s[v] = true
}
- sort.Strings(required)
+ return s
+}
- var optional = make(map[string]bool) // feature => true
- if *nextFile != "" {
- for _, feature := range fileFeatures(*nextFile) {
- optional[feature] = true
- }
+var spaceParensRx = regexp.MustCompile(` \(\S+?\)`)
+
+func featureWithoutContext(f string) string {
+ if !strings.Contains(f, "(") {
+ return f
}
+ return spaceParensRx.ReplaceAllString(f, "")
+}
+
+func compareAPI(w io.Writer, features, required, optional, exception []string) (ok bool) {
+ ok = true
+
+ optionalSet := set(optional)
+ exceptionSet := set(exception)
+ featureSet := set(features)
+
+ sort.Strings(features)
+ sort.Strings(required)
take := func(sl *[]string) string {
s := (*sl)[0]
@@ -206,20 +229,27 @@ func main() {
for len(required) > 0 || len(features) > 0 {
switch {
- case len(features) == 0 || required[0] < features[0]:
- fmt.Fprintf(bw, "-%s\n", take(&required))
- fail = true // broke compatibility
- case len(required) == 0 || required[0] > features[0]:
+ case len(features) == 0 || (len(required) > 0 && required[0] < features[0]):
+ feature := take(&required)
+ if exceptionSet[feature] {
+ fmt.Fprintf(w, "~%s\n", feature)
+ } else if featureSet[featureWithoutContext(feature)] {
+ // okay.
+ } else {
+ fmt.Fprintf(w, "-%s\n", feature)
+ ok = false // broke compatibility
+ }
+ case len(required) == 0 || (len(features) > 0 && required[0] > features[0]):
newFeature := take(&features)
- if optional[newFeature] {
+ if optionalSet[newFeature] {
// Known added feature to the upcoming release.
// Delete it from the map so we can detect any upcoming features
// which were never seen. (so we can clean up the nextFile)
- delete(optional, newFeature)
+ delete(optionalSet, newFeature)
} else {
- fmt.Fprintf(bw, "+%s\n", newFeature)
+ fmt.Fprintf(w, "+%s\n", newFeature)
if !*allowNew {
- fail = true // we're in lock-down mode for next release
+ ok = false // we're in lock-down mode for next release
}
}
default:
@@ -228,17 +258,22 @@ func main() {
}
}
+ // In next file, but not in API.
var missing []string
- for feature := range optional {
+ for feature := range optionalSet {
missing = append(missing, feature)
}
sort.Strings(missing)
for _, feature := range missing {
- fmt.Fprintf(bw, "(in next file, but not in API) -%s\n", feature)
+ fmt.Fprintf(w, "±%s\n", feature)
}
+ return
}
func fileFeatures(filename string) []string {
+ if filename == "" {
+ return nil
+ }
bs, err := ioutil.ReadFile(filename)
if err != nil {
log.Fatalf("Error reading file %s: %v", filename, err)
@@ -256,10 +291,11 @@ type pkgSymbol struct {
symbol string // "RoundTripper"
}
+var fset = token.NewFileSet()
+
type Walker struct {
context *build.Context
root string
- fset *token.FileSet
scope []string
features map[string]bool // set
lastConstType string
@@ -276,7 +312,6 @@ type Walker struct {
func NewWalker() *Walker {
return &Walker{
- fset: token.NewFileSet(),
features: make(map[string]bool),
packageState: make(map[string]loadState),
interfaces: make(map[pkgSymbol]*ast.InterfaceType),
@@ -297,20 +332,6 @@ const (
loaded
)
-// hardCodedConstantType is a hack until the type checker is sufficient for our needs.
-// Rather than litter the code with unnecessary type annotations, we'll hard-code
-// the cases we can't handle yet.
-func (w *Walker) hardCodedConstantType(name string) (typ string, ok bool) {
- switch w.scope[0] {
- case "pkg syscall":
- switch name {
- case "darwinAMD64":
- return "bool", true
- }
- }
- return "", false
-}
-
func (w *Walker) Features() (fs []string) {
for f := range w.features {
fs = append(fs, f)
@@ -333,6 +354,21 @@ func fileDeps(f *ast.File) (pkgs []string) {
return
}
+var parsedFileCache = make(map[string]*ast.File)
+
+func parseFile(filename string) (*ast.File, error) {
+ f, ok := parsedFileCache[filename]
+ if !ok {
+ var err error
+ f, err = parser.ParseFile(fset, filename, nil, 0)
+ if err != nil {
+ return nil, err
+ }
+ parsedFileCache[filename] = f
+ }
+ return clone(f).(*ast.File), nil
+}
+
// WalkPackage walks all files in package `name'.
// WalkPackage does nothing if the package has already been loaded.
func (w *Walker) WalkPackage(name string) {
@@ -366,7 +402,7 @@ func (w *Walker) WalkPackage(name string) {
files := append(append([]string{}, info.GoFiles...), info.CgoFiles...)
for _, file := range files {
- f, err := parser.ParseFile(w.fset, filepath.Join(dir, file), nil, 0)
+ f, err := parseFile(filepath.Join(dir, file))
if err != nil {
log.Fatalf("error parsing package %s, file %s: %v", name, file, err)
}
@@ -501,7 +537,7 @@ func (w *Walker) walkFile(file *ast.File) {
// Ignore. Handled in subsequent pass, by go/doc.
default:
log.Printf("unhandled %T, %#v\n", di, di)
- printer.Fprint(os.Stderr, w.fset, di)
+ printer.Fprint(os.Stderr, fset, di)
os.Stderr.Write([]byte("\n"))
}
}
@@ -562,6 +598,10 @@ func (w *Walker) constValueType(vi interface{}) (string, error) {
}
return constDepPrefix + v.Name, nil
case *ast.BinaryExpr:
+ switch v.Op {
+ case token.EQL, token.LSS, token.GTR, token.NOT, token.NEQ, token.LEQ, token.GEQ:
+ return "bool", nil
+ }
left, err := w.constValueType(v.X)
if err != nil {
return "", err
@@ -734,17 +774,11 @@ func (w *Walker) walkConst(vs *ast.ValueSpec) {
var err error
litType, err = w.constValueType(vs.Values[0])
if err != nil {
- if t, ok := w.hardCodedConstantType(ident.Name); ok {
- litType = t
- err = nil
- } else {
- log.Fatalf("unknown kind in const %q (%T): %v", ident.Name, vs.Values[0], err)
- }
+ log.Fatalf("unknown kind in const %q (%T): %v", ident.Name, vs.Values[0], err)
}
}
}
- if strings.HasPrefix(litType, constDepPrefix) {
- dep := litType[len(constDepPrefix):]
+ if dep := strings.TrimPrefix(litType, constDepPrefix); dep != litType {
w.constDep[ident.Name] = dep
continue
}
@@ -816,7 +850,7 @@ func (w *Walker) nodeString(node interface{}) string {
return ""
}
var b bytes.Buffer
- printer.Fprint(&b, w.fset, node)
+ printer.Fprint(&b, fset, node)
return b.String()
}
@@ -825,7 +859,7 @@ func (w *Walker) nodeDebug(node interface{}) string {
return ""
}
var b bytes.Buffer
- ast.Fprint(&b, w.fset, node, nil)
+ ast.Fprint(&b, fset, node, nil)
return b.String()
}
@@ -844,7 +878,7 @@ func (w *Walker) walkTypeSpec(ts *ast.TypeSpec) {
case *ast.InterfaceType:
w.walkInterfaceType(name, t)
default:
- w.emitFeature(fmt.Sprintf("type %s %s", name, w.nodeString(ts.Type)))
+ w.emitFeature(fmt.Sprintf("type %s %s", name, w.nodeString(w.namelessType(ts.Type))))
}
}
@@ -892,15 +926,18 @@ type method struct {
sig string // "([]byte) (int, error)", from funcSigString
}
-// interfaceMethods returns the expanded list of methods for an interface.
+// interfaceMethods returns the expanded list of exported methods for an interface.
+// The boolean complete reports whether the list contains all methods (that is, the
+// interface has no unexported methods).
// pkg is the complete package name ("net/http")
// iname is the interface name.
-func (w *Walker) interfaceMethods(pkg, iname string) (methods []method) {
+func (w *Walker) interfaceMethods(pkg, iname string) (methods []method, complete bool) {
t, ok := w.interfaces[pkgSymbol{pkg, iname}]
if !ok {
log.Fatalf("failed to find interface %s.%s", pkg, iname)
}
+ complete = true
for _, f := range t.Methods.List {
typ := f.Type
switch tv := typ.(type) {
@@ -912,6 +949,8 @@ func (w *Walker) interfaceMethods(pkg, iname string) (methods []method) {
name: mname.Name,
sig: w.funcSigString(ft),
})
+ } else {
+ complete = false
}
}
case *ast.Ident:
@@ -927,7 +966,9 @@ func (w *Walker) interfaceMethods(pkg, iname string) (methods []method) {
log.Fatalf("unexported embedded interface %q in exported interface %s.%s; confused",
embedded, pkg, iname)
}
- methods = append(methods, w.interfaceMethods(pkg, embedded)...)
+ m, c := w.interfaceMethods(pkg, embedded)
+ methods = append(methods, m...)
+ complete = complete && c
case *ast.SelectorExpr:
lhs := w.nodeString(tv.X)
rhs := w.nodeString(tv.Sel)
@@ -935,7 +976,9 @@ func (w *Walker) interfaceMethods(pkg, iname string) (methods []method) {
if !ok {
log.Fatalf("can't resolve selector %q in interface %s.%s", lhs, pkg, iname)
}
- methods = append(methods, w.interfaceMethods(fpkg, rhs)...)
+ m, c := w.interfaceMethods(fpkg, rhs)
+ methods = append(methods, m...)
+ complete = complete && c
default:
log.Fatalf("unknown type %T in interface field", typ)
}
@@ -945,14 +988,28 @@ func (w *Walker) interfaceMethods(pkg, iname string) (methods []method) {
func (w *Walker) walkInterfaceType(name string, t *ast.InterfaceType) {
methNames := []string{}
-
pop := w.pushScope("type " + name + " interface")
- for _, m := range w.interfaceMethods(w.curPackageName, name) {
+ methods, complete := w.interfaceMethods(w.curPackageName, name)
+ for _, m := range methods {
methNames = append(methNames, m.name)
w.emitFeature(fmt.Sprintf("%s%s", m.name, m.sig))
}
+ if !complete {
+ // The method set has unexported methods, so all the
+ // implementations are provided by the same package,
+ // so the method set can be extended. Instead of recording
+ // the full set of names (below), record only that there were
+ // unexported methods. (If the interface shrinks, we will notice
+ // because a method signature emitted during the last loop,
+ // will disappear.)
+ w.emitFeature("unexported methods")
+ }
pop()
+ if !complete {
+ return
+ }
+
sort.Strings(methNames)
if len(methNames) == 0 {
w.emitFeature(fmt.Sprintf("type %s interface {}", name))
@@ -994,18 +1051,38 @@ func (w *Walker) walkFuncDecl(f *ast.FuncDecl) {
func (w *Walker) funcSigString(ft *ast.FuncType) string {
var b bytes.Buffer
+ writeField := func(b *bytes.Buffer, f *ast.Field) {
+ if n := len(f.Names); n > 1 {
+ for i := 0; i < n; i++ {
+ if i > 0 {
+ b.WriteString(", ")
+ }
+ b.WriteString(w.nodeString(w.namelessType(f.Type)))
+ }
+ } else {
+ b.WriteString(w.nodeString(w.namelessType(f.Type)))
+ }
+ }
b.WriteByte('(')
if ft.Params != nil {
for i, f := range ft.Params.List {
if i > 0 {
b.WriteString(", ")
}
- b.WriteString(w.nodeString(w.namelessType(f.Type)))
+ writeField(&b, f)
}
}
b.WriteByte(')')
if ft.Results != nil {
- if nr := len(ft.Results.List); nr > 0 {
+ nr := 0
+ for _, f := range ft.Results.List {
+ if n := len(f.Names); n > 1 {
+ nr += n
+ } else {
+ nr++
+ }
+ }
+ if nr > 0 {
b.WriteByte(' ')
if nr > 1 {
b.WriteByte('(')
@@ -1014,7 +1091,7 @@ func (w *Walker) funcSigString(ft *ast.FuncType) string {
if i > 0 {
b.WriteString(", ")
}
- b.WriteString(w.nodeString(w.namelessType(f.Type)))
+ writeField(&b, f)
}
if nr > 1 {
b.WriteByte(')')
@@ -1042,7 +1119,13 @@ func (w *Walker) namelessFieldList(fl *ast.FieldList) *ast.FieldList {
fl2 := &ast.FieldList{}
if fl != nil {
for _, f := range fl.List {
- fl2.List = append(fl2.List, w.namelessField(f))
+ repeats := 1
+ if len(f.Names) > 1 {
+ repeats = len(f.Names)
+ }
+ for i := 0; i < repeats; i++ {
+ fl2.List = append(fl2.List, w.namelessField(f))
+ }
}
}
return fl2
@@ -1056,10 +1139,21 @@ func (w *Walker) namelessField(f *ast.Field) *ast.Field {
}
}
+var (
+ byteRx = regexp.MustCompile(`\bbyte\b`)
+ runeRx = regexp.MustCompile(`\brune\b`)
+)
+
func (w *Walker) emitFeature(feature string) {
if !w.wantedPkg[w.curPackageName] {
return
}
+ if strings.Contains(feature, "byte") {
+ feature = byteRx.ReplaceAllString(feature, "uint8")
+ }
+ if strings.Contains(feature, "rune") {
+ feature = runeRx.ReplaceAllString(feature, "int32")
+ }
f := strings.Join(w.scope, ", ") + ", " + feature
if _, dup := w.features[f]; dup {
panic("duplicate feature inserted: " + f)
@@ -1075,6 +1169,7 @@ func (w *Walker) emitFeature(feature string) {
}
panic("feature contains newlines: " + f)
}
+
w.features[f] = true
if *verbose {
log.Printf("feature: %s", f)
diff --git a/src/cmd/api/goapi_test.go b/src/cmd/api/goapi_test.go
index c7cc601b1..1a86c0ec7 100644
--- a/src/cmd/api/goapi_test.go
+++ b/src/cmd/api/goapi_test.go
@@ -5,6 +5,7 @@
package main
import (
+ "bytes"
"flag"
"fmt"
"io/ioutil"
@@ -73,3 +74,68 @@ func TestGolden(t *testing.T) {
}
}
}
+
+func TestCompareAPI(t *testing.T) {
+ tests := []struct {
+ name string
+ features, required, optional, exception []string
+ ok bool // want
+ out string // want
+ }{
+ {
+ name: "feature added",
+ features: []string{"A", "B", "C", "D", "E", "F"},
+ required: []string{"B", "D"},
+ ok: true,
+ out: "+A\n+C\n+E\n+F\n",
+ },
+ {
+ name: "feature removed",
+ features: []string{"C", "A"},
+ required: []string{"A", "B", "C"},
+ ok: false,
+ out: "-B\n",
+ },
+ {
+ name: "feature added then removed",
+ features: []string{"A", "C"},
+ optional: []string{"B"},
+ required: []string{"A", "C"},
+ ok: true,
+ out: "±B\n",
+ },
+ {
+ name: "exception removal",
+ required: []string{"A", "B", "C"},
+ features: []string{"A", "C"},
+ exception: []string{"B"},
+ ok: true,
+ out: "~B\n",
+ },
+ {
+ // http://golang.org/issue/4303
+ name: "contexts reconverging",
+ required: []string{
+ "A",
+ "pkg syscall (darwin-386), type RawSockaddrInet6 struct",
+ "pkg syscall (darwin-amd64), type RawSockaddrInet6 struct",
+ },
+ features: []string{
+ "A",
+ "pkg syscall, type RawSockaddrInet6 struct",
+ },
+ ok: true,
+ out: "+pkg syscall, type RawSockaddrInet6 struct\n",
+ },
+ }
+ for _, tt := range tests {
+ buf := new(bytes.Buffer)
+ gotok := compareAPI(buf, tt.features, tt.required, tt.optional, tt.exception)
+ if gotok != tt.ok {
+ t.Errorf("%s: ok = %v; want %v", tt.name, gotok, tt.ok)
+ }
+ if got := buf.String(); got != tt.out {
+ t.Errorf("%s: output differs\nGOT:\n%s\nWANT:\n%s", tt.name, got, tt.out)
+ }
+ }
+}
diff --git a/src/cmd/api/testdata/src/pkg/p1/golden.txt b/src/cmd/api/testdata/src/pkg/p1/golden.txt
index e334e5776..abcc0ce6c 100644
--- a/src/cmd/api/testdata/src/pkg/p1/golden.txt
+++ b/src/cmd/api/testdata/src/pkg/p1/golden.txt
@@ -10,6 +10,7 @@ pkg p1, func Bar(int8, int16, int64)
pkg p1, func Bar1(int8, int16, int64) uint64
pkg p1, func Bar2(int8, int16, int64) (uint8, uint64)
pkg p1, func BarE() Error
+pkg p1, func PlainFunc(int, int, string) (*B, error)
pkg p1, func TakesFunc(func(int) int)
pkg p1, method (*B) JustOnB()
pkg p1, method (*B) OnBothTandBPtr()
@@ -27,6 +28,9 @@ pkg p1, method (TPtrExported) OnEmbedded()
pkg p1, method (TPtrUnexported) OnBothTandBPtr()
pkg p1, method (TPtrUnexported) OnBothTandBVal()
pkg p1, type B struct
+pkg p1, type ByteStruct struct
+pkg p1, type ByteStruct struct, B uint8
+pkg p1, type ByteStruct struct, R int32
pkg p1, type Codec struct
pkg p1, type Codec struct, Func func(int, int) int
pkg p1, type EmbedSelector struct
@@ -37,15 +41,21 @@ pkg p1, type Embedded struct
pkg p1, type Error interface { Error, Temporary }
pkg p1, type Error interface, Error() string
pkg p1, type Error interface, Temporary() bool
-pkg p1, type I interface { Get, GetNamed, Name, PackageTwoMeth, Set }
+pkg p1, type FuncType func(int, int, string) (*B, error)
pkg p1, type I interface, Get(string) int64
pkg p1, type I interface, GetNamed(string) int64
pkg p1, type I interface, Name() string
pkg p1, type I interface, PackageTwoMeth()
pkg p1, type I interface, Set(string, int64)
+pkg p1, type I interface, unexported methods
pkg p1, type MyInt int
pkg p1, type Namer interface { Name }
pkg p1, type Namer interface, Name() string
+pkg p1, type Private interface, X()
+pkg p1, type Private interface, unexported methods
+pkg p1, type Public interface { X, Y }
+pkg p1, type Public interface, X()
+pkg p1, type Public interface, Y()
pkg p1, type S struct
pkg p1, type S struct, Public *int
pkg p1, type S struct, PublicTime time.Time
@@ -58,7 +68,9 @@ pkg p1, type T struct
pkg p1, type TPtrExported struct
pkg p1, type TPtrExported struct, embedded *Embedded
pkg p1, type TPtrUnexported struct
-pkg p1, var ByteConv []byte
+pkg p1, var Byte uint8
+pkg p1, var ByteConv []uint8
+pkg p1, var ByteFunc func(uint8) int32
pkg p1, var ChecksumError error
pkg p1, var SIPtr *SI
pkg p1, var SIPtr2 *SI
diff --git a/src/cmd/api/testdata/src/pkg/p1/p1.go b/src/cmd/api/testdata/src/pkg/p1/p1.go
index d965bb75e..f94c9ceeb 100644
--- a/src/cmd/api/testdata/src/pkg/p1/p1.go
+++ b/src/cmd/api/testdata/src/pkg/p1/p1.go
@@ -78,6 +78,16 @@ type I interface {
private()
}
+type Public interface {
+ X()
+ Y()
+}
+
+type Private interface {
+ X()
+ y()
+}
+
type Error interface {
error
Temporary() bool
@@ -139,8 +149,12 @@ type TPtrExported struct {
*Embedded
}
+type FuncType func(x, y int, s string) (b *B, err error)
+
type Embedded struct{}
+func PlainFunc(x, y int, s string) (b *B, err error)
+
func (*Embedded) OnEmbedded() {}
func (*T) JustOnT() {}
@@ -151,3 +165,39 @@ func (common) OnBothTandBVal() {}
type EmbedSelector struct {
time.Time
}
+
+const (
+ foo = "foo"
+ foo2 string = "foo2"
+ truth = foo == "foo" || foo2 == "foo2"
+)
+
+func ellipsis(...string) {}
+
+var x = &S{
+ Public: nil,
+ private: nil,
+ publicTime: time.Now(),
+}
+
+var parenExpr = (1 + 5)
+
+var funcLit = func() {}
+
+var m map[string]int
+
+var chanVar chan int
+
+var ifaceVar interface{} = 5
+
+var assertVar = ifaceVar.(int)
+
+var indexVar = m["foo"]
+
+var Byte byte
+var ByteFunc func(byte) rune
+
+type ByteStruct struct {
+ B byte
+ R rune
+}
diff --git a/src/cmd/api/testdata/src/pkg/p3/golden.txt b/src/cmd/api/testdata/src/pkg/p3/golden.txt
new file mode 100644
index 000000000..a7dcccd1b
--- /dev/null
+++ b/src/cmd/api/testdata/src/pkg/p3/golden.txt
@@ -0,0 +1,3 @@
+pkg p3, func BadHop(int, int, int) (bool, bool, *ThirdBase, *ThirdBase, error)
+pkg p3, method (*ThirdBase) GoodPlayer() (int, int, int)
+pkg p3, type ThirdBase struct
diff --git a/src/cmd/api/testdata/src/pkg/p3/p3.go b/src/cmd/api/testdata/src/pkg/p3/p3.go
new file mode 100644
index 000000000..1b2b1a426
--- /dev/null
+++ b/src/cmd/api/testdata/src/pkg/p3/p3.go
@@ -0,0 +1,6 @@
+package p3
+
+type ThirdBase struct{}
+
+func (tb *ThirdBase) GoodPlayer() (i, j, k int)
+func BadHop(i, j, k int) (l, m bool, n, o *ThirdBase, err error)