Commit 5c04272f authored by Brad Fitzpatrick's avatar Brad Fitzpatrick

cmd/goapi: new tool for tracking exported API over time

The idea is that we add files to the api/ directory which
are sets of promises for the future.  Each line in a file
is a stand-alone feature description.

When we do a release, we make sure we haven't broken or changed
any lines from the past (only added them).

We never change old files, only adding new ones. (go-1.1.txt,
etc)

R=dsymonds, adg, r, remyoudompheng, rsc
CC=golang-dev
https://golang.org/cl/5570051
parent c21b3434
// Copyright 2011 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.
// Goapi computes the exported API of a set of Go packages.
package main
import (
"bufio"
"bytes"
"errors"
"flag"
"fmt"
"go/ast"
"go/build"
"go/doc"
"go/parser"
"go/printer"
"go/token"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"sort"
"strings"
)
// Flags
var (
checkFile = flag.String("c", "", "optional filename to check API against")
verbose = flag.Bool("v", false, "Verbose debugging")
)
func main() {
flag.Parse()
var pkgs []string
if flag.NArg() > 0 {
pkgs = flag.Args()
} else {
stds, err := exec.Command("go", "list", "std").Output()
if err != nil {
log.Fatal(err)
}
pkgs = strings.Fields(string(stds))
}
w := NewWalker()
tree, _, err := build.FindTree("os") // some known package
if err != nil {
log.Fatalf("failed to find tree: %v", err)
}
for _, pkg := range pkgs {
if strings.HasPrefix(pkg, "cmd/") ||
strings.HasPrefix(pkg, "exp/") ||
strings.HasPrefix(pkg, "old/") {
continue
}
if !tree.HasSrc(pkg) {
log.Fatalf("no source in tree for package %q", pkg)
}
pkgSrcDir := filepath.Join(tree.SrcDir(), filepath.FromSlash(pkg))
w.WalkPackage(pkg, pkgSrcDir)
}
bw := bufio.NewWriter(os.Stdout)
defer bw.Flush()
if *checkFile != "" {
bs, err := ioutil.ReadFile(*checkFile)
if err != nil {
log.Fatalf("Error reading file %s: %v", *checkFile, err)
}
v1 := strings.Split(string(bs), "\n")
sort.Strings(v1)
v2 := w.Features()
take := func(sl *[]string) string {
s := (*sl)[0]
*sl = (*sl)[1:]
return s
}
for len(v1) > 0 || len(v2) > 0 {
switch {
case len(v2) == 0 || v1[0] < v2[0]:
fmt.Fprintf(bw, "-%s\n", take(&v1))
case len(v1) == 0 || v1[0] > v2[0]:
fmt.Fprintf(bw, "+%s\n", take(&v2))
default:
take(&v1)
take(&v2)
}
}
} else {
for _, f := range w.Features() {
fmt.Fprintf(bw, "%s\n", f)
}
}
}
type Walker struct {
fset *token.FileSet
scope []string
features map[string]bool // set
lastConstType string
curPackageName string
curPackage *ast.Package
prevConstType map[string]string // identifer -> "ideal-int"
}
func NewWalker() *Walker {
return &Walker{
fset: token.NewFileSet(),
features: make(map[string]bool),
}
}
// 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 compress/gzip", "pkg compress/zlib":
switch name {
case "NoCompression", "BestSpeed", "BestCompression", "DefaultCompression":
return "ideal-int", true
}
case "pkg os":
switch name {
case "WNOHANG", "WSTOPPED", "WUNTRACED":
return "ideal-int", true
}
case "pkg path/filepath":
switch name {
case "Separator", "ListSeparator":
return "char", true
}
case "pkg unicode/utf8":
switch name {
case "RuneError":
return "char", true
}
case "pkg text/scanner":
// TODO: currently this tool only resolves const types
// that reference other constant types if they appear
// in the right order. the scanner package has
// ScanIdents and such coming before the Ident/Int/etc
// tokens, hence this hack.
if strings.HasPrefix(name, "Scan") || name == "SkipComments" {
return "ideal-int", true
}
}
return "", false
}
func (w *Walker) Features() (fs []string) {
for f := range w.features {
fs = append(fs, f)
}
sort.Strings(fs)
return
}
func (w *Walker) WalkPackage(name, dir string) {
log.Printf("package %s", name)
pop := w.pushScope("pkg " + name)
defer pop()
info, err := build.ScanDir(dir)
if err != nil {
log.Fatalf("pkg %q, dir %q: ScanDir: %v", name, dir, err)
}
apkg := &ast.Package{
Files: make(map[string]*ast.File),
}
files := append(append([]string{}, info.GoFiles...), info.CgoFiles...)
for _, file := range files {
f, err := parser.ParseFile(w.fset, filepath.Join(dir, file), nil, 0)
if err != nil {
log.Fatalf("error parsing package %s, file %s: %v", name, file, err)
}
apkg.Files[file] = f
}
w.curPackageName = name
w.curPackage = apkg
w.prevConstType = map[string]string{}
for name, afile := range apkg.Files {
w.walkFile(filepath.Join(dir, name), afile)
}
// Now that we're done walking types, vars and consts
// in the *ast.Package, use go/doc to do the rest
// (functions and methods). This is done here because
// go/doc is destructive. We can't use the
// *ast.Package after this.
dpkg := doc.New(apkg, name, 0)
for _, t := range dpkg.Types {
// Move funcs up to the top-level, not hiding in the Types.
dpkg.Funcs = append(dpkg.Funcs, t.Funcs...)
for _, m := range t.Methods {
w.walkFuncDecl(m.Decl)
}
}
for _, f := range dpkg.Funcs {
w.walkFuncDecl(f.Decl)
}
}
// pushScope enters a new scope (walking a package, type, node, etc)
// and returns a function that will leave the scope (with sanity checking
// for mismatched pushes & pops)
func (w *Walker) pushScope(name string) (popFunc func()) {
w.scope = append(w.scope, name)
return func() {
if len(w.scope) == 0 {
log.Fatalf("attempt to leave scope %q with empty scope list", name)
}
if w.scope[len(w.scope)-1] != name {
log.Fatalf("attempt to leave scope %q, but scope is currently %#v", name, w.scope)
}
w.scope = w.scope[:len(w.scope)-1]
}
}
func (w *Walker) walkFile(name string, file *ast.File) {
// Not entering a scope here; file boundaries aren't interesting.
for _, di := range file.Decls {
switch d := di.(type) {
case *ast.GenDecl:
switch d.Tok {
case token.IMPORT:
continue
case token.CONST:
for _, sp := range d.Specs {
w.walkConst(sp.(*ast.ValueSpec))
}
case token.TYPE:
for _, sp := range d.Specs {
w.walkTypeSpec(sp.(*ast.TypeSpec))
}
case token.VAR:
for _, sp := range d.Specs {
w.walkVar(sp.(*ast.ValueSpec))
}
default:
log.Fatalf("unknown token type %d in GenDecl", d.Tok)
}
case *ast.FuncDecl:
// Ignore. Handled in subsequent pass, by go/doc.
default:
log.Printf("unhandled %T, %#v\n", di, di)
printer.Fprint(os.Stderr, w.fset, di)
os.Stderr.Write([]byte("\n"))
}
}
}
var constType = map[token.Token]string{
token.INT: "ideal-int",
token.FLOAT: "ideal-float",
token.STRING: "ideal-string",
token.CHAR: "ideal-char",
token.IMAG: "ideal-imag",
}
var varType = map[token.Token]string{
token.INT: "int",
token.FLOAT: "float64",
token.STRING: "string",
token.CHAR: "rune",
token.IMAG: "complex128",
}
var errTODO = errors.New("TODO")
func (w *Walker) constValueType(vi interface{}) (string, error) {
switch v := vi.(type) {
case *ast.BasicLit:
litType, ok := constType[v.Kind]
if !ok {
return "", fmt.Errorf("unknown basic literal kind %#v", v)
}
return litType, nil
case *ast.UnaryExpr:
return w.constValueType(v.X)
case *ast.SelectorExpr:
// e.g. compress/gzip's BestSpeed == flate.BestSpeed
return "", errTODO
case *ast.Ident:
if v.Name == "iota" {
return "ideal-int", nil // hack.
}
if v.Name == "false" || v.Name == "true" {
return "ideal-bool", nil
}
if v.Name == "intSize" && w.curPackageName == "strconv" {
// Hack.
return "ideal-int", nil
}
if t, ok := w.prevConstType[v.Name]; ok {
return t, nil
}
return "", fmt.Errorf("can't resolve existing constant %q", v.Name)
case *ast.BinaryExpr:
left, err := w.constValueType(v.X)
if err != nil {
return "", err
}
right, err := w.constValueType(v.Y)
if err != nil {
return "", err
}
if left != right {
if left == "ideal-int" && right == "ideal-float" {
return "ideal-float", nil // math.Log2E
}
if left == "ideal-char" && right == "ideal-int" {
return "ideal-int", nil // math/big.MaxBase
}
if left == "ideal-int" && right == "ideal-char" {
return "ideal-int", nil // text/scanner.GoWhitespace
}
if left == "ideal-int" && right == "Duration" {
// Hack, for package time.
return "Duration", nil
}
return "", fmt.Errorf("in BinaryExpr, unhandled type mismatch; left=%q, right=%q", left, right)
}
return left, nil
case *ast.CallExpr:
// Not a call, but a type conversion.
return w.nodeString(v.Fun), nil
case *ast.ParenExpr:
return w.constValueType(v.X)
}
return "", fmt.Errorf("unknown const value type %T", vi)
}
func (w *Walker) varValueType(vi interface{}) (string, error) {
valStr := w.nodeString(vi)
if strings.HasPrefix(valStr, "errors.New(") {
return "error", nil
}
switch v := vi.(type) {
case *ast.BasicLit:
litType, ok := varType[v.Kind]
if !ok {
return "", fmt.Errorf("unknown basic literal kind %#v", v)
}
return litType, nil
case *ast.CompositeLit:
return w.nodeString(v.Type), nil
case *ast.FuncLit:
return w.nodeString(w.namelessType(v.Type)), nil
case *ast.UnaryExpr:
if v.Op == token.AND {
typ, err := w.varValueType(v.X)
return "*" + typ, err
}
return "", fmt.Errorf("unknown unary expr: %#v", v)
case *ast.SelectorExpr:
return "", errTODO
case *ast.Ident:
node, _, ok := w.resolveName(v.Name)
if !ok {
return "", fmt.Errorf("unresolved identifier: %q", v.Name)
}
return w.varValueType(node)
case *ast.BinaryExpr:
left, err := w.varValueType(v.X)
if err != nil {
return "", err
}
right, err := w.varValueType(v.Y)
if err != nil {
return "", err
}
if left != right {
return "", fmt.Errorf("in BinaryExpr, unhandled type mismatch; left=%q, right=%q", left, right)
}
return left, nil
case *ast.ParenExpr:
return w.varValueType(v.X)
case *ast.CallExpr:
funStr := w.nodeString(v.Fun)
node, _, ok := w.resolveName(funStr)
if !ok {
return "", fmt.Errorf("unresolved named %q", funStr)
}
if funcd, ok := node.(*ast.FuncDecl); ok {
// Assume at the top level that all functions have exactly 1 result
return w.nodeString(w.namelessType(funcd.Type.Results.List[0].Type)), nil
}
// maybe a function call; maybe a conversion. Need to lookup type.
return "", fmt.Errorf("resolved name %q to a %T: %#v", funStr, node, node)
default:
return "", fmt.Errorf("unknown const value type %T", vi)
}
panic("unreachable")
}
// resolveName finds a top-level node named name and returns the node
// v and its type t, if known.
func (w *Walker) resolveName(name string) (v interface{}, t interface{}, ok bool) {
for _, file := range w.curPackage.Files {
for _, di := range file.Decls {
switch d := di.(type) {
case *ast.FuncDecl:
if d.Name.Name == name {
return d, d.Type, true
}
case *ast.GenDecl:
switch d.Tok {
case token.TYPE:
for _, sp := range d.Specs {
ts := sp.(*ast.TypeSpec)
if ts.Name.Name == name {
return ts, ts.Type, true
}
}
case token.VAR:
for _, sp := range d.Specs {
vs := sp.(*ast.ValueSpec)
for i, vname := range vs.Names {
if vname.Name == name {
if len(vs.Values) > i {
return vs.Values[i], vs.Type, true
}
return nil, vs.Type, true
}
}
}
}
}
}
}
return nil, nil, false
}
func (w *Walker) walkConst(vs *ast.ValueSpec) {
for _, ident := range vs.Names {
if !ast.IsExported(ident.Name) {
continue
}
litType := ""
if vs.Type != nil {
litType = w.nodeString(vs.Type)
} else {
litType = w.lastConstType
if vs.Values != nil {
if len(vs.Values) != 1 {
log.Fatalf("const %q, values: %#v", ident.Name, vs.Values)
}
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)
}
}
}
}
if litType == "" {
log.Fatalf("unknown kind in const %q", ident.Name)
}
w.lastConstType = litType
w.emitFeature(fmt.Sprintf("const %s %s", ident, litType))
w.prevConstType[ident.Name] = litType
}
}
func (w *Walker) walkVar(vs *ast.ValueSpec) {
for i, ident := range vs.Names {
if !ast.IsExported(ident.Name) {
continue
}
typ := ""
if vs.Type != nil {
typ = w.nodeString(vs.Type)
} else {
if len(vs.Values) == 0 {
log.Fatalf("no values for var %q", ident.Name)
}
if len(vs.Values) > 1 {
log.Fatalf("more than 1 values in ValueSpec not handled, var %q", ident.Name)
}
var err error
typ, err = w.varValueType(vs.Values[i])
if err != nil {
log.Fatalf("unknown type of variable %q, type %T, error = %v\ncode: %s",
ident.Name, vs.Values[i], err, w.nodeString(vs.Values[i]))
}
}
w.emitFeature(fmt.Sprintf("var %s %s", ident, typ))
}
}
func (w *Walker) nodeString(node interface{}) string {
if node == nil {
return ""
}
var b bytes.Buffer
printer.Fprint(&b, w.fset, node)
return b.String()
}
func (w *Walker) nodeDebug(node interface{}) string {
if node == nil {
return ""
}
var b bytes.Buffer
ast.Fprint(&b, w.fset, node, nil)
return b.String()
}
func (w *Walker) walkTypeSpec(ts *ast.TypeSpec) {
name := ts.Name.Name
if !ast.IsExported(name) {
return
}
switch t := ts.Type.(type) {
case *ast.StructType:
w.walkStructType(name, t)
case *ast.InterfaceType:
w.walkInterfaceType(name, t)
default:
w.emitFeature(fmt.Sprintf("type %s %s", name, w.nodeString(ts.Type)))
//log.Fatalf("unknown typespec %T", ts.Type)
}
}
func (w *Walker) walkStructType(name string, t *ast.StructType) {
typeStruct := fmt.Sprintf("type %s struct", name)
w.emitFeature(typeStruct)
pop := w.pushScope(typeStruct)
defer pop()
for _, f := range t.Fields.List {
typ := f.Type
for _, name := range f.Names {
if ast.IsExported(name.Name) {
w.emitFeature(fmt.Sprintf("%s %s", name, w.nodeString(w.namelessType(typ))))
}
}
if f.Names == nil {
switch v := typ.(type) {
case *ast.Ident:
if ast.IsExported(v.Name) {
w.emitFeature(fmt.Sprintf("embedded %s", v.Name))
}
case *ast.StarExpr:
switch vv := v.X.(type) {
case *ast.Ident:
if ast.IsExported(vv.Name) {
w.emitFeature(fmt.Sprintf("embedded *%s", vv.Name))
}
case *ast.SelectorExpr:
w.emitFeature(fmt.Sprintf("embedded %s", w.nodeString(typ)))
default:
log.Fatal("unable to handle embedded starexpr before %T", typ)
}
case *ast.SelectorExpr:
w.emitFeature(fmt.Sprintf("embedded %s", w.nodeString(typ)))
default:
log.Fatalf("unable to handle embedded %T", typ)
}
}
}
}
func (w *Walker) walkInterfaceType(name string, t *ast.InterfaceType) {
methods := []string{}
pop := w.pushScope("type " + name + " interface")
for _, f := range t.Methods.List {
typ := f.Type
for _, name := range f.Names {
if ast.IsExported(name.Name) {
ft := typ.(*ast.FuncType)
w.emitFeature(fmt.Sprintf("%s%s", name, w.funcSigString(ft)))
methods = append(methods, name.Name)
}
}
}
pop()
sort.Strings(methods)
if len(methods) == 0 {
w.emitFeature(fmt.Sprintf("type %s interface {}", name))
} else {
w.emitFeature(fmt.Sprintf("type %s interface { %s }", name, strings.Join(methods, ", ")))
}
}
func (w *Walker) walkFuncDecl(f *ast.FuncDecl) {
if !ast.IsExported(f.Name.Name) {
return
}
if f.Recv != nil {
// Method.
recvType := w.nodeString(f.Recv.List[0].Type)
keep := ast.IsExported(recvType) ||
(strings.HasPrefix(recvType, "*") &&
ast.IsExported(recvType[1:]))
if !keep {
return
}
w.emitFeature(fmt.Sprintf("method (%s) %s%s", recvType, f.Name.Name, w.funcSigString(f.Type)))
return
}
// Else, a function
w.emitFeature(fmt.Sprintf("func %s%s", f.Name.Name, w.funcSigString(f.Type)))
}
func (w *Walker) funcSigString(ft *ast.FuncType) string {
var b bytes.Buffer
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)))
}
}
b.WriteByte(')')
if ft.Results != nil {
if nr := len(ft.Results.List); nr > 0 {
b.WriteByte(' ')
if nr > 1 {
b.WriteByte('(')
}
for i, f := range ft.Results.List {
if i > 0 {
b.WriteString(", ")
}
b.WriteString(w.nodeString(w.namelessType(f.Type)))
}
if nr > 1 {
b.WriteByte(')')
}
}
}
return b.String()
}
// namelessType returns a type node that lacks any variable names.
func (w *Walker) namelessType(t interface{}) interface{} {
ft, ok := t.(*ast.FuncType)
if !ok {
return t
}
return &ast.FuncType{
Params: w.namelessFieldList(ft.Params),
Results: w.namelessFieldList(ft.Results),
}
}
// namelessFieldList returns a deep clone of fl, with the cloned fields
// lacking names.
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))
}
}
return fl2
}
// namelessField clones f, but not preserving the names of fields.
// (comments and tags are also ignored)
func (w *Walker) namelessField(f *ast.Field) *ast.Field {
return &ast.Field{
Type: f.Type,
}
}
func (w *Walker) emitFeature(feature string) {
f := strings.Join(w.scope, ", ") + ", " + feature
if _, dup := w.features[f]; dup {
panic("duplicate feature inserted: " + f)
}
if strings.Contains(f, "\n") {
// TODO: for now, just skip over the
// runtime.MemStatsType.BySize type, which this tool
// doesn't properly handle. It's pretty low-level,
// though, so not super important to protect against.
if strings.HasPrefix(f, "pkg runtime") && strings.Contains(f, "BySize [61]struct") {
return
}
panic("feature contains newlines: " + f)
}
w.features[f] = true
if *verbose {
log.Printf("feature: %s", f)
}
}
func strListContains(l []string, s string) bool {
for _, v := range l {
if v == s {
return true
}
}
return false
}
// Copyright 2011 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 (
"flag"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strings"
"testing"
)
var (
updateGolden = flag.Bool("updategolden", false, "update golden files")
)
func TestGolden(t *testing.T) {
td, err := os.Open("testdata")
if err != nil {
t.Fatal(err)
}
fis, err := td.Readdir(0)
if err != nil {
t.Fatal(err)
}
for _, fi := range fis {
if !fi.IsDir() {
continue
}
w := NewWalker()
goldenFile := filepath.Join("testdata", fi.Name(), "golden.txt")
w.WalkPackage(fi.Name(), filepath.Join("testdata", fi.Name()))
if *updateGolden {
os.Remove(goldenFile)
f, err := os.Create(goldenFile)
if err != nil {
t.Fatal(err)
}
for _, feat := range w.Features() {
fmt.Fprintf(f, "%s\n", feat)
}
f.Close()
}
bs, err := ioutil.ReadFile(goldenFile)
if err != nil {
t.Fatalf("opening golden.txt for package %q: %v", fi.Name(), err)
}
wanted := strings.Split(string(bs), "\n")
sort.Strings(wanted)
for _, feature := range wanted {
if feature == "" {
continue
}
_, ok := w.features[feature]
if !ok {
t.Errorf("package %s: missing feature %q", fi.Name(), feature)
}
delete(w.features, feature)
}
for _, feature := range w.Features() {
t.Errorf("package %s: extra feature not in golden file: %q", fi.Name(), feature)
}
}
}
pkg p1, const A ideal-int
pkg p1, const A64 int64
pkg p1, const B ideal-int
pkg p1, const ConversionConst MyInt
pkg p1, const FloatConst ideal-float
pkg p1, const StrConst ideal-string
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 TakesFunc(func(int) int)
pkg p1, method (*B) JustOnB()
pkg p1, method (*B) OnBothTandBPtr()
pkg p1, method (*Embedded) OnEmbedded()
pkg p1, method (*S2) SMethod(int8, int16, int64)
pkg p1, method (*T) JustOnT()
pkg p1, method (*T) OnBothTandBPtr()
pkg p1, method (B) OnBothTandBVal()
pkg p1, method (S) StructValueMethod()
pkg p1, method (S) StructValueMethodNamedRecv()
pkg p1, method (S2) StructValueMethod()
pkg p1, method (S2) StructValueMethodNamedRecv()
pkg p1, method (T) OnBothTandBVal()
pkg p1, method (TPtrExported) OnEmbedded()
pkg p1, method (TPtrUnexported) OnBothTandBPtr()
pkg p1, method (TPtrUnexported) OnBothTandBVal()
pkg p1, type B struct
pkg p1, type Codec struct
pkg p1, type Codec struct, Func func(int, int) int
pkg p1, type EmbedSelector struct
pkg p1, type EmbedSelector struct, embedded time.Time
pkg p1, type EmbedURLPtr struct
pkg p1, type EmbedURLPtr struct, embedded *url.URL
pkg p1, type Embedded struct
pkg p1, type I interface { Get, GetNamed, Set }
pkg p1, type I interface, Get(string) int64
pkg p1, type I interface, GetNamed(string) int64
pkg p1, type I interface, Set(string, int64)
pkg p1, type MyInt int
pkg p1, type S struct
pkg p1, type S struct, Public *int
pkg p1, type S struct, PublicTime time.Time
pkg p1, type S2 struct
pkg p1, type S2 struct, Extra bool
pkg p1, type S2 struct, embedded S
pkg p1, type SI struct
pkg p1, type SI struct, I int
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 ChecksumError error
pkg p1, var SIPtr *SI
pkg p1, var SIPtr2 *SI
pkg p1, var SIVal SI
pkg p1, var X I
pkg p1, var X int64
pkg p1, var Y int
package foo
import (
"time"
"url"
)
const (
A = 1
a = 11
A64 int64 = 1
)
const (
ConversionConst = MyInt(5)
)
var ChecksumError = errors.New("gzip checksum error")
const B = 2
const StrConst = "foo"
const FloatConst = 1.5
type myInt int
type MyInt int
type S struct {
Public *int
private *int
PublicTime time.Time
}
type EmbedURLPtr struct {
*url.URL
}
type S2 struct {
S
Extra bool
}
var X int64
var (
Y int
X I // todo: resolve this to foo.I? probably doesn't matter.
)
type I interface {
Set(name string, balance int64)
Get(string) int64
GetNamed(string) (balance int64)
private()
}
func (myInt) privateTypeMethod() {}
func (myInt) CapitalMethodUnexportedType() {}
func (s *S2) SMethod(x int8, y int16, z int64) {}
type s struct{}
func (s) method()
func (s) Method()
func (S) StructValueMethod()
func (ignored S) StructValueMethodNamedRecv()
func (s *S2) unexported(x int8, y int16, z int64) {}
func Bar(x int8, y int16, z int64) {}
func Bar1(x int8, y int16, z int64) uint64 {}
func Bar2(x int8, y int16, z int64) (uint8, uint64) {}
func unexported(x int8, y int16, z int64) {}
func TakesFunc(f func(dontWantName int) int)
type Codec struct {
Func func(x int, y int) (z int)
}
type SI struct {
I int
}
var SIVal = SI{}
var SIPtr = &SI{}
var SIPtr2 *SI
type T struct {
common
}
type B struct {
common
}
type common struct {
i int
}
type TPtrUnexported struct {
*common
}
type TPtrExported struct {
*Embedded
}
type Embedded struct{}
func (*Embedded) OnEmbedded() {}
func (*T) JustOnT() {}
func (*B) JustOnB() {}
func (*common) OnBothTandBPtr() {}
func (common) OnBothTandBVal() {}
type EmbedSelector struct {
time.Time
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment