Permalink
Browse files

Correctly detect functions that belong to a receiver

Previously functions that had a name starting with "New" followed by the
name of a type that was part of its return value was considered a
constructor. The way we handle it now is more like the way go doc
handles these cases. A function belongs to a receiver based on its first
return type. If it's a named return type, it must not have multiple
names. In this case a function is not a constructor any more, but a
function with its receiver set.
  • Loading branch information...
1 parent e1ca5f4 commit 00892686e67d2964e65bb0d9ffb58d45f18d0bda @jstemmer committed Aug 17, 2012
Showing with 74 additions and 35 deletions.
  1. +53 −30 parser.go
  2. +9 −5 parser_test.go
  3. +12 −0 tests/struct.go
View
@@ -10,8 +10,9 @@ import (
// tagParser contains the data needed while parsing.
type tagParser struct {
- fset *token.FileSet
- tags []Tag
+ fset *token.FileSet
+ tags []Tag // list of created tags
+ types []string // all types we encounter, used to determine the constructors
}
// PrintTree prints the ast of the source in filename.
@@ -29,8 +30,9 @@ func PrintTree(filename string) error {
// Parse parses the source in filename and returns a list of tags.
func Parse(filename string) ([]Tag, error) {
p := &tagParser{
- fset: token.NewFileSet(),
- tags: []Tag{},
+ fset: token.NewFileSet(),
+ tags: []Tag{},
+ types: make([]string, 0),
}
f, err := parser.ParseFile(p.fset, filename, nil, 0)
@@ -65,11 +67,10 @@ func (p *tagParser) parseImports(f *ast.File) {
// parseDeclarations creates a tag for each function, type or value declaration.
func (p *tagParser) parseDeclarations(f *ast.File) {
+ // first parse the type and value declarations, so that we have a list of all
+ // known types before parsing the functions.
for _, d := range f.Decls {
- switch decl := d.(type) {
- case *ast.FuncDecl:
- p.parseFunction(decl)
- case *ast.GenDecl:
+ if decl, ok := d.(*ast.GenDecl); ok {
for _, s := range decl.Specs {
switch ts := s.(type) {
case *ast.TypeSpec:
@@ -80,6 +81,13 @@ func (p *tagParser) parseDeclarations(f *ast.File) {
}
}
}
+
+ // now parse all the functions
+ for _, d := range f.Decls {
+ if decl, ok := d.(*ast.FuncDecl); ok {
+ p.parseFunction(decl)
+ }
+ }
}
// parseFunction creates a tag for function declaration f.
@@ -90,16 +98,15 @@ func (p *tagParser) parseFunction(f *ast.FuncDecl) {
tag.Fields[Signature] = fmt.Sprintf("(%s)", getTypes(f.Type.Params, true))
tag.Fields[TypeField] = getTypes(f.Type.Results, false)
- // receiver
if f.Recv != nil && len(f.Recv.List) > 0 {
+ // this function has a receiver, set the type to Method
tag.Fields[ReceiverType] = getType(f.Recv.List[0].Type, false)
tag.Type = Method
- }
-
- // check if this is a constructor, in that case it belongs to that type
- if strings.HasPrefix(tag.Name, "New") && containsType(tag.Name[3:], f.Type.Results) {
- tag.Fields[ReceiverType] = tag.Name[3:]
- tag.Type = Constructor
+ } else if name, ok := p.belongsToReceiver(f.Type.Results); ok {
+ // this function does not have a receiver, but it belongs to one based
+ // on its return values; its type will be Function instead of Method.
+ tag.Fields[ReceiverType] = name
+ tag.Type = Function
}
p.tags = append(p.tags, tag)
@@ -116,6 +123,7 @@ func (p *tagParser) parseTypeDeclaration(ts *ast.TypeSpec) {
case *ast.StructType:
tag.Fields[TypeField] = "struct"
p.parseStructFields(tag.Name, s)
+ p.types = append(p.types, tag.Name)
case *ast.InterfaceType:
tag.Fields[TypeField] = "interface"
tag.Type = Interface
@@ -206,6 +214,36 @@ func (p *tagParser) createTag(name string, pos token.Pos, tagType TagType) Tag {
return NewTag(name, p.fset.File(pos).Name(), p.fset.Position(pos).Line, tagType)
}
+// belongsToReceiver checks if a function with these return types belongs to
+// a receiver. If it belongs to a receiver, the name of that receiver will be
+// returned with ok set to true. Otherwise ok will be false.
+// Behavior should be similar to how go doc decides when a function belongs to
+// a receiver (gosrc/pkg/go/doc/reader.go).
+func (p *tagParser) belongsToReceiver(types *ast.FieldList) (name string, ok bool) {
+ if types == nil || types.NumFields() == 0 {
+ return "", false
+ }
+
+ // If the first return type has more than 1 result associated with
+ // it, it should not belong to that receiver.
+ // Similar behavior as go doc (go source/.
+ if len(types.List[0].Names) > 1 {
+ return "", false
+ }
+
+ // get name of the first return type
+ t := getType(types.List[0].Type, false)
+
+ // check if it exists in the current list of known types
+ for _, knownType := range p.types {
+ if t == knownType {
+ return knownType, true
+ }
+ }
+
+ return "", false
+}
+
// getTypes returns a comma separated list of types in fields. If includeNames is
// true each type is preceded by a comma separated list of parameter names.
func getTypes(fields *ast.FieldList, includeNames bool) string {
@@ -246,21 +284,6 @@ func getTypes(fields *ast.FieldList, includeNames bool) string {
return strings.Join(types, ", ")
}
-// containsType checks if t occurs in any of the types in fields
-func containsType(t string, fields *ast.FieldList) bool {
- if len(t) == 0 || fields == nil {
- return false
- }
-
- for _, param := range fields.List {
- if getType(param.Type, false) == t {
- return true
- }
- }
-
- return false
-}
-
// getType returns a string representation of the type of node. If star is true and the
// type is a pointer, a * will be prepended to the string.
func getType(node ast.Node, star bool) (paramType string) {
View
@@ -48,15 +48,19 @@ var testCases = []struct {
tag("field3", 5, "w", F{"access": "private", "ctype": "Struct", "type": "string"}),
tag("field4", 6, "w", F{"access": "private", "ctype": "Struct", "type": "*bool"}),
tag("Struct", 3, "t", F{"access": "public", "type": "struct"}),
- tag("NewStruct", 9, "r", F{"access": "public", "ctype": "Struct", "signature": "()", "type": "*Struct"}),
- tag("F1", 13, "m", F{"access": "public", "ctype": "Struct", "signature": "()", "type": "[]bool, [2]*string"}),
- tag("F2", 16, "m", F{"access": "public", "ctype": "Struct", "signature": "()", "type": "bool"}),
tag("Struct", 20, "e", F{"access": "public", "ctype": "TestEmbed", "type": "Struct"}),
tag("*io.Writer", 21, "e", F{"access": "public", "ctype": "TestEmbed", "type": "*io.Writer"}),
tag("TestEmbed", 19, "t", F{"access": "public", "type": "struct"}),
- tag("NewTestEmbed", 24, "r", F{"access": "public", "ctype": "TestEmbed", "signature": "()", "type": "TestEmbed"}),
tag("Struct2", 27, "t", F{"access": "public", "type": "struct"}),
- tag("NewStruct2", 30, "r", F{"access": "public", "ctype": "Struct2", "signature": "()", "type": "*Struct2, error"}),
+ tag("Connection", 36, "t", F{"access": "public", "type": "struct"}),
+ tag("NewStruct", 9, "f", F{"access": "public", "ctype": "Struct", "signature": "()", "type": "*Struct"}),
+ tag("F1", 13, "m", F{"access": "public", "ctype": "Struct", "signature": "()", "type": "[]bool, [2]*string"}),
+ tag("F2", 16, "m", F{"access": "public", "ctype": "Struct", "signature": "()", "type": "bool"}),
+ tag("NewTestEmbed", 24, "f", F{"access": "public", "ctype": "TestEmbed", "signature": "()", "type": "TestEmbed"}),
+ tag("NewStruct2", 30, "f", F{"access": "public", "ctype": "Struct2", "signature": "()", "type": "*Struct2, error"}),
+ tag("Dial", 33, "f", F{"access": "public", "ctype": "Connection", "signature": "()", "type": "*Connection, error"}),
+ tag("Dial2", 39, "f", F{"access": "public", "ctype": "Connection", "signature": "()", "type": "*Connection, *Struct2"}),
+ tag("Dial3", 42, "f", F{"access": "public", "signature": "()", "type": "*Connection, *Connection"}),
}},
{filename: "tests/type.go", tags: []Tag{
tag("Test", 1, "p", F{}),
View
@@ -29,3 +29,15 @@ type Struct2 struct {
func NewStruct2() (*Struct2, error) {
}
+
+func Dial() (*Connection, error) {
+}
+
+type Connection struct {
+}
+
+func Dial2() (*Connection, *Struct2) {
+}
+
+func Dial3() (a, b *Connection) {
+}

0 comments on commit 0089268

Please sign in to comment.