Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
go
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
go
Commits
a94bd4d7
Commit
a94bd4d7
authored
Jan 29, 2012
by
Brad Fitzpatrick
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
cmd/goapi: expand embedded interfaces
Fixes #2801 R=rsc CC=golang-dev
https://golang.org/cl/5576068
parent
0f2659a3
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
221 additions
and
40 deletions
+221
-40
src/cmd/goapi/goapi.go
src/cmd/goapi/goapi.go
+186
-32
src/cmd/goapi/goapi_test.go
src/cmd/goapi/goapi_test.go
+6
-3
src/cmd/goapi/testdata/src/pkg/p1/golden.txt
src/cmd/goapi/testdata/src/pkg/p1/golden.txt
+8
-1
src/cmd/goapi/testdata/src/pkg/p1/p1.go
src/cmd/goapi/testdata/src/pkg/p1/p1.go
+14
-4
src/cmd/goapi/testdata/src/pkg/p2/golden.txt
src/cmd/goapi/testdata/src/pkg/p2/golden.txt
+2
-0
src/cmd/goapi/testdata/src/pkg/p2/p2.go
src/cmd/goapi/testdata/src/pkg/p2/p2.go
+5
-0
No files found.
src/cmd/goapi/goapi.go
View file @
a94bd4d7
...
...
@@ -21,8 +21,10 @@ import (
"log"
"os"
"os/exec"
"path"
"path/filepath"
"sort"
"strconv"
"strings"
)
...
...
@@ -51,6 +53,11 @@ func main() {
if
err
!=
nil
{
log
.
Fatalf
(
"failed to find tree: %v"
,
err
)
}
w
.
tree
=
tree
for
_
,
pkg
:=
range
pkgs
{
w
.
wantedPkg
[
pkg
]
=
true
}
for
_
,
pkg
:=
range
pkgs
{
if
strings
.
HasPrefix
(
pkg
,
"cmd/"
)
||
...
...
@@ -61,8 +68,7 @@ func main() {
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
)
w
.
WalkPackage
(
pkg
)
}
bw
:=
bufio
.
NewWriter
(
os
.
Stdout
)
...
...
@@ -99,23 +105,47 @@ func main() {
}
}
// pkgSymbol represents a symbol in a package
type
pkgSymbol
struct
{
pkg
string
// "net/http"
symbol
string
// "RoundTripper"
}
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"
tree
*
build
.
Tree
fset
*
token
.
FileSet
scope
[]
string
features
map
[
string
]
bool
// set
lastConstType
string
curPackageName
string
curPackage
*
ast
.
Package
prevConstType
map
[
string
]
string
// identifer -> "ideal-int"
packageState
map
[
string
]
loadState
interfaces
map
[
pkgSymbol
]
*
ast
.
InterfaceType
selectorFullPkg
map
[
string
]
string
// "http" => "net/http", updated by imports
wantedPkg
map
[
string
]
bool
// packages requested on the command line
}
func
NewWalker
()
*
Walker
{
return
&
Walker
{
fset
:
token
.
NewFileSet
(),
features
:
make
(
map
[
string
]
bool
),
fset
:
token
.
NewFileSet
(),
features
:
make
(
map
[
string
]
bool
),
packageState
:
make
(
map
[
string
]
loadState
),
interfaces
:
make
(
map
[
pkgSymbol
]
*
ast
.
InterfaceType
),
selectorFullPkg
:
make
(
map
[
string
]
string
),
wantedPkg
:
make
(
map
[
string
]
bool
),
}
}
// loadState is the state of a package's parsing.
type
loadState
int
const
(
notLoaded
loadState
=
iota
loading
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.
...
...
@@ -162,10 +192,34 @@ func (w *Walker) Features() (fs []string) {
return
}
func
(
w
*
Walker
)
WalkPackage
(
name
,
dir
string
)
{
log
.
Printf
(
"package %s"
,
name
)
pop
:=
w
.
pushScope
(
"pkg "
+
name
)
defer
pop
()
// fileDeps returns the imports in a file.
func
fileDeps
(
f
*
ast
.
File
)
(
pkgs
[]
string
)
{
for
_
,
is
:=
range
f
.
Imports
{
fpkg
,
err
:=
strconv
.
Unquote
(
is
.
Path
.
Value
)
if
err
!=
nil
{
log
.
Fatalf
(
"error unquoting import string %q: %v"
,
is
.
Path
.
Value
,
err
)
}
if
fpkg
!=
"C"
{
pkgs
=
append
(
pkgs
,
fpkg
)
}
}
return
}
// WalkPackage walks all files in package `name'.
// WalkPackage does nothing if the package has already been loaded.
func
(
w
*
Walker
)
WalkPackage
(
name
string
)
{
switch
w
.
packageState
[
name
]
{
case
loading
:
log
.
Fatalf
(
"import cycle loading package %q?"
,
name
)
case
loaded
:
return
}
w
.
packageState
[
name
]
=
loading
defer
func
()
{
w
.
packageState
[
name
]
=
loaded
}()
dir
:=
filepath
.
Join
(
w
.
tree
.
SrcDir
(),
filepath
.
FromSlash
(
name
))
info
,
err
:=
build
.
ScanDir
(
dir
)
if
err
!=
nil
{
...
...
@@ -183,13 +237,26 @@ func (w *Walker) WalkPackage(name, dir string) {
log
.
Fatalf
(
"error parsing package %s, file %s: %v"
,
name
,
file
,
err
)
}
apkg
.
Files
[
file
]
=
f
for
_
,
dep
:=
range
fileDeps
(
f
)
{
w
.
WalkPackage
(
dep
)
}
}
log
.
Printf
(
"package %s"
,
name
)
pop
:=
w
.
pushScope
(
"pkg "
+
name
)
defer
pop
()
w
.
curPackageName
=
name
w
.
curPackage
=
apkg
w
.
prevConstType
=
map
[
string
]
string
{}
for
name
,
afile
:=
range
apkg
.
Files
{
w
.
walkFile
(
filepath
.
Join
(
dir
,
name
),
afile
)
for
_
,
afile
:=
range
apkg
.
Files
{
w
.
recordTypes
(
afile
)
}
for
_
,
afile
:=
range
apkg
.
Files
{
w
.
walkFile
(
afile
)
}
// Now that we're done walking types, vars and consts
...
...
@@ -229,7 +296,27 @@ func (w *Walker) pushScope(name string) (popFunc func()) {
}
}
func
(
w
*
Walker
)
walkFile
(
name
string
,
file
*
ast
.
File
)
{
func
(
w
*
Walker
)
recordTypes
(
file
*
ast
.
File
)
{
for
_
,
di
:=
range
file
.
Decls
{
switch
d
:=
di
.
(
type
)
{
case
*
ast
.
GenDecl
:
switch
d
.
Tok
{
case
token
.
TYPE
:
for
_
,
sp
:=
range
d
.
Specs
{
ts
:=
sp
.
(
*
ast
.
TypeSpec
)
name
:=
ts
.
Name
.
Name
if
ast
.
IsExported
(
name
)
{
if
it
,
ok
:=
ts
.
Type
.
(
*
ast
.
InterfaceType
);
ok
{
w
.
noteInterface
(
name
,
it
)
}
}
}
}
}
}
}
func
(
w
*
Walker
)
walkFile
(
file
*
ast
.
File
)
{
// Not entering a scope here; file boundaries aren't interesting.
for
_
,
di
:=
range
file
.
Decls
{
...
...
@@ -237,7 +324,18 @@ func (w *Walker) walkFile(name string, file *ast.File) {
case
*
ast
.
GenDecl
:
switch
d
.
Tok
{
case
token
.
IMPORT
:
continue
for
_
,
sp
:=
range
d
.
Specs
{
is
:=
sp
.
(
*
ast
.
ImportSpec
)
fpath
,
err
:=
strconv
.
Unquote
(
is
.
Path
.
Value
)
if
err
!=
nil
{
log
.
Fatal
(
err
)
}
name
:=
path
.
Base
(
fpath
)
if
is
.
Name
!=
nil
{
name
=
is
.
Name
.
Name
}
w
.
selectorFullPkg
[
name
]
=
fpath
}
case
token
.
CONST
:
for
_
,
sp
:=
range
d
.
Specs
{
w
.
walkConst
(
sp
.
(
*
ast
.
ValueSpec
))
...
...
@@ -527,12 +625,15 @@ func (w *Walker) nodeDebug(node interface{}) string {
return
b
.
String
()
}
func
(
w
*
Walker
)
noteInterface
(
name
string
,
it
*
ast
.
InterfaceType
)
{
w
.
interfaces
[
pkgSymbol
{
w
.
curPackageName
,
name
}]
=
it
}
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
)
...
...
@@ -540,7 +641,6 @@ func (w *Walker) walkTypeSpec(ts *ast.TypeSpec) {
w
.
walkInterfaceType
(
name
,
t
)
default
:
w
.
emitFeature
(
fmt
.
Sprintf
(
"type %s %s"
,
name
,
w
.
nodeString
(
ts
.
Type
)))
//log.Fatalf("unknown typespec %T", ts.Type)
}
}
...
...
@@ -582,27 +682,78 @@ func (w *Walker) walkStructType(name string, t *ast.StructType) {
}
}
func
(
w
*
Walker
)
walkInterfaceType
(
name
string
,
t
*
ast
.
InterfaceType
)
{
methods
:=
[]
string
{}
// method is a method of an interface.
type
method
struct
{
name
string
// "Read"
sig
string
// "([]byte) (int, error)", from funcSigString
}
// interfaceMethods returns the expanded list of methods for an interface.
// pkg is the complete package name ("net/http")
// iname is the interface name.
func
(
w
*
Walker
)
interfaceMethods
(
pkg
,
iname
string
)
(
methods
[]
method
)
{
t
,
ok
:=
w
.
interfaces
[
pkgSymbol
{
pkg
,
iname
}]
if
!
ok
{
log
.
Fatalf
(
"failed to find interface %s.%s"
,
pkg
,
iname
)
}
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
)
switch
tv
:=
typ
.
(
type
)
{
case
*
ast
.
FuncType
:
for
_
,
mname
:=
range
f
.
Names
{
if
ast
.
IsExported
(
mname
.
Name
)
{
ft
:=
typ
.
(
*
ast
.
FuncType
)
methods
=
append
(
methods
,
method
{
name
:
mname
.
Name
,
sig
:
w
.
funcSigString
(
ft
),
})
}
}
case
*
ast
.
Ident
:
embedded
:=
typ
.
(
*
ast
.
Ident
)
.
Name
if
embedded
==
"error"
{
methods
=
append
(
methods
,
method
{
name
:
"Error"
,
sig
:
"() string"
,
})
continue
}
if
!
ast
.
IsExported
(
embedded
)
{
log
.
Fatalf
(
"unexported embedded interface %q in exported interface %s.%s; confused"
,
embedded
,
pkg
,
iname
)
}
methods
=
append
(
methods
,
w
.
interfaceMethods
(
pkg
,
embedded
)
...
)
case
*
ast
.
SelectorExpr
:
lhs
:=
w
.
nodeString
(
tv
.
X
)
rhs
:=
w
.
nodeString
(
tv
.
Sel
)
fpkg
,
ok
:=
w
.
selectorFullPkg
[
lhs
]
if
!
ok
{
log
.
Fatalf
(
"can't resolve selector %q in interface %s.%s"
,
lhs
,
pkg
,
iname
)
}
methods
=
append
(
methods
,
w
.
interfaceMethods
(
fpkg
,
rhs
)
...
)
default
:
log
.
Fatalf
(
"unknown type %T in interface field"
,
typ
)
}
}
return
}
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
)
{
methNames
=
append
(
methNames
,
m
.
name
)
w
.
emitFeature
(
fmt
.
Sprintf
(
"%s%s"
,
m
.
name
,
m
.
sig
))
}
pop
()
sort
.
Strings
(
meth
od
s
)
if
len
(
meth
od
s
)
==
0
{
sort
.
Strings
(
meth
Name
s
)
if
len
(
meth
Name
s
)
==
0
{
w
.
emitFeature
(
fmt
.
Sprintf
(
"type %s interface {}"
,
name
))
}
else
{
w
.
emitFeature
(
fmt
.
Sprintf
(
"type %s interface { %s }"
,
name
,
strings
.
Join
(
meth
od
s
,
", "
)))
w
.
emitFeature
(
fmt
.
Sprintf
(
"type %s interface { %s }"
,
name
,
strings
.
Join
(
meth
Name
s
,
", "
)))
}
}
...
...
@@ -691,6 +842,9 @@ func (w *Walker) namelessField(f *ast.Field) *ast.Field {
}
func
(
w
*
Walker
)
emitFeature
(
feature
string
)
{
if
!
w
.
wantedPkg
[
w
.
curPackageName
]
{
return
}
f
:=
strings
.
Join
(
w
.
scope
,
", "
)
+
", "
+
feature
if
_
,
dup
:=
w
.
features
[
f
];
dup
{
panic
(
"duplicate feature inserted: "
+
f
)
...
...
src/cmd/goapi/goapi_test.go
View file @
a94bd4d7
...
...
@@ -7,6 +7,7 @@ package main
import
(
"flag"
"fmt"
"go/build"
"io/ioutil"
"os"
"path/filepath"
...
...
@@ -20,7 +21,7 @@ var (
)
func
TestGolden
(
t
*
testing
.
T
)
{
td
,
err
:=
os
.
Open
(
"testdata"
)
td
,
err
:=
os
.
Open
(
"testdata
/src/pkg
"
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
...
...
@@ -33,9 +34,11 @@ func TestGolden(t *testing.T) {
continue
}
w
:=
NewWalker
()
goldenFile
:=
filepath
.
Join
(
"testdata"
,
fi
.
Name
(),
"golden.txt"
)
w
.
wantedPkg
[
fi
.
Name
()]
=
true
w
.
WalkPackage
(
fi
.
Name
(),
filepath
.
Join
(
"testdata"
,
fi
.
Name
()))
w
.
tree
=
&
build
.
Tree
{
Path
:
"testdata"
,
Goroot
:
true
}
goldenFile
:=
filepath
.
Join
(
"testdata"
,
"src"
,
"pkg"
,
fi
.
Name
(),
"golden.txt"
)
w
.
WalkPackage
(
fi
.
Name
())
if
*
updateGolden
{
os
.
Remove
(
goldenFile
)
...
...
src/cmd/goapi/testdata/p1/golden.txt
→
src/cmd/goapi/testdata/
src/pkg/
p1/golden.txt
View file @
a94bd4d7
...
...
@@ -31,11 +31,18 @@ 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 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 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 MyInt int
pkg p1, type Namer interface { Name }
pkg p1, type Namer interface, Name() string
pkg p1, type S struct
pkg p1, type S struct, Public *int
pkg p1, type S struct, PublicTime time.Time
...
...
src/cmd/goapi/testdata/p1/p1.go
→
src/cmd/goapi/testdata/
src/pkg/
p1/p1.go
View file @
a94bd4d7
package
foo
package
p1
import
(
"time"
"url"
ptwo
"p2"
)
const
(
...
...
@@ -44,16 +43,27 @@ var X int64
var
(
Y
int
X
I
// todo: resolve this to foo.I? probably doesn't matter.
X
I
)
type
Namer
interface
{
Name
()
string
}
type
I
interface
{
Namer
ptwo
.
Twoer
Set
(
name
string
,
balance
int64
)
Get
(
string
)
int64
GetNamed
(
string
)
(
balance
int64
)
private
()
}
type
Error
interface
{
error
Temporary
()
bool
}
func
(
myInt
)
privateTypeMethod
()
{}
func
(
myInt
)
CapitalMethodUnexportedType
()
{}
...
...
src/cmd/goapi/testdata/src/pkg/p2/golden.txt
0 → 100644
View file @
a94bd4d7
pkg p2, type Twoer interface { PackageTwoMeth }
pkg p2, type Twoer interface, PackageTwoMeth()
src/cmd/goapi/testdata/src/pkg/p2/p2.go
0 → 100644
View file @
a94bd4d7
package
p2
type
Twoer
interface
{
PackageTwoMeth
()
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment