Skip to content

Commit 5a73932

Browse files
committed
feat: connect method call with method definition using typeinfo (cloudwego#13)
* feat: connect function-call with method defination * deduplicate
1 parent e666460 commit 5a73932

9 files changed

Lines changed: 264 additions & 111 deletions

File tree

src/compress/golang/go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@ require (
66
github.com/davecgh/go-spew v1.1.1
77
golang.org/x/tools v0.16.1
88
)
9+
10+
require golang.org/x/mod v0.14.0 // indirect

src/compress/golang/go.sum

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
22
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3+
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
4+
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
5+
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
36
golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA=
47
golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=

src/compress/golang/plugin/file.go

Lines changed: 84 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package main
1919
import (
2020
"go/ast"
2121
"go/token"
22+
"go/types"
2223
"path/filepath"
2324
"strings"
2425
)
@@ -66,6 +67,31 @@ func isGoBuiltinFunc(name string) bool {
6667
}
6768
}
6869

70+
func (p *goParser) inspectFile(ctx *fileContext, f *ast.File) (map[string]*Function, map[string]*Struct, error) {
71+
fileStructs := map[string]*Struct{}
72+
fileFuncs := map[string]*Function{}
73+
cont := true
74+
ast.Inspect(f, func(node ast.Node) bool {
75+
if funcDecl, ok := node.(*ast.FuncDecl); ok {
76+
// parse funcs
77+
f, ct := p.parseFunc(ctx, funcDecl)
78+
fileFuncs[f.Name] = f
79+
cont = ct
80+
} else if typDecl, ok := node.(*ast.TypeSpec); ok {
81+
// parse structs
82+
struName := typDecl.Name.Name
83+
struDecl, ok := typDecl.Type.(*ast.StructType)
84+
if ok {
85+
st, ct := p.parseStruct(ctx, struName, struDecl)
86+
fileStructs[struName] = st
87+
cont = ct
88+
}
89+
}
90+
return cont
91+
})
92+
return fileFuncs, fileStructs, nil
93+
}
94+
6995
// getOrSetFunc get a function in the map, or alloc and set a new one if not exists
7096
func (p *goParser) getOrSetFunc(pkg, name string) *Function {
7197
pkgFuncs := p.processedPkgFunctions[pkg]
@@ -127,7 +153,7 @@ func (p *goParser) seprateImports(impts []*ast.ImportSpec) (map[string]string, m
127153

128154
// parseFunc parses all function declaration in one file
129155
func (p *goParser) parseFunc(ctx *fileContext, funcDecl *ast.FuncDecl) (*Function, bool) {
130-
156+
// funcObj := ctx.pkgTypeInfo.Defs[funcDecl.Name]
131157
var associatedStruct *Struct
132158
isMethod := funcDecl.Recv != nil
133159
if isMethod {
@@ -150,6 +176,7 @@ func (p *goParser) parseFunc(ctx *fileContext, funcDecl *ast.FuncDecl) (*Functio
150176
var functionCalls, methodCalls = map[string]*Function{}, map[string]*Function{}
151177

152178
ast.Inspect(funcDecl.Body, func(node ast.Node) bool {
179+
// scope := ctx.pkgTypeInfo.Scopes[node]
153180
call, ok := node.(*ast.CallExpr)
154181
if ok {
155182
var funcName string
@@ -181,20 +208,40 @@ func (p *goParser) parseFunc(ctx *fileContext, funcDecl *ast.FuncDecl) (*Functio
181208
thirdPartyFunctionCalls[funcName] = &ThirdPartyIdentity{PkgPath: impt, Identity: expr.Sel.Name}
182209
return true
183210
}
184-
// WHY: skip sys imports?
185-
if _, ok := ctx.sysImports[x.(*ast.Ident).Name]; ok {
211+
// NOTICE: skip sys imports?
212+
if ctx.IsSysImport(x.(*ast.Ident).Name) {
186213
// internalFunctionCalls[funcName] = p.getOrSetFunc(impt, expr.Sel.Name)
187214
return true
188215
}
216+
// check if it's method calls
217+
sel, ok := ctx.pkgTypeInfo.Selections[expr]
218+
if ok && (sel.Kind() == types.MethodExpr || sel.Kind() == types.MethodVal) {
219+
// builtin or std libs, just ignore
220+
m := sel.Obj()
221+
if m.Pkg() == nil || ctx.IsSysImport(m.Pkg().Name()) {
222+
return true
223+
}
224+
// try assert as named type
225+
obj := getTypeNamed(sel.Recv())
226+
if obj == nil {
227+
return true
228+
}
189229

190-
// Fallback must be method calls
191-
// FIXME: get type info of object
192-
f := p.getOrSetFunc(ctx.pkgPath, funcName)
193-
f.IsMethod = true
194-
f.AssociatedStruct = &Struct{Name: x.(*ast.Ident).Name}
195-
methodCalls[funcName] = f
196-
// TODO: seperate internal method and third-party method
197-
230+
mpkg := m.Pkg().Path()
231+
//NOTICE: use {structName.methodName} as method key
232+
mname := obj.Name() + "." + m.Name()
233+
f := p.getOrSetFunc(mpkg, mname)
234+
f.AssociatedStruct = p.getOrSetStruct(mpkg, obj.Name())
235+
f.IsMethod = true
236+
237+
if strings.HasPrefix(mpkg, p.modName) {
238+
// internal pkg
239+
methodCalls[funcName] = f
240+
} else {
241+
// external pkg
242+
thirdPartyMethodCalls[funcName] = &ThirdPartyIdentity{mpkg, mname}
243+
}
244+
}
198245
return true
199246
case *ast.Ident:
200247
funcName = expr.Name
@@ -227,6 +274,22 @@ func (p *goParser) parseFunc(ctx *fileContext, funcDecl *ast.FuncDecl) (*Functio
227274
return f, true
228275
}
229276

277+
func getTypeNamed(typ types.Type) types.Object {
278+
if pt, ok := typ.(*types.Pointer); ok {
279+
typ = pt.Elem()
280+
}
281+
name, ok := typ.(*types.Named)
282+
if ok {
283+
return name.Obj()
284+
}
285+
return nil
286+
}
287+
288+
func (ctx *fileContext) IsSysImport(alias string) bool {
289+
_, ok := ctx.sysImports[alias]
290+
return ok
291+
}
292+
230293
// Struct holds the information about a struct
231294
type Struct struct {
232295
Name string // Name of the struct
@@ -257,6 +320,7 @@ type fileContext struct {
257320
sysImports map[string]string
258321
projectImports map[string]string
259322
thirdPartyImports map[string]string
323+
pkgTypeInfo *types.Info
260324
}
261325

262326
// parse a ast.StructType node and renturn allocated *Struct
@@ -287,7 +351,7 @@ func (p *goParser) parseStruct(ctx *fileContext, struName string, struDecl *ast.
287351
}
288352

289353
types := []ThirdPartyIdentity{}
290-
isFunc := getTypeName(ctx.bs, fieldDecl.Type, &types)
354+
isFunc := getTypeName(ctx.fset, ctx.bs, fieldDecl.Type, &types)
291355

292356
for _, ty := range types {
293357
if isFunc {
@@ -328,35 +392,35 @@ func (p *goParser) parseStruct(ctx *fileContext, struName string, struDecl *ast.
328392

329393
// handle typ expr and return not-builtin type identity and return if the type if a func signature.
330394
// ret is used to store results.
331-
func getTypeName(file []byte, typ ast.Expr, ret *[]ThirdPartyIdentity) bool {
395+
func getTypeName(fset *token.FileSet, file []byte, typ ast.Expr, ret *[]ThirdPartyIdentity) bool {
332396
switch ty := typ.(type) {
333397
case *ast.Ident:
334398
if !isGoBuiltinFunc(ty.Name) {
335399
*ret = append(*ret, ThirdPartyIdentity{Identity: ty.Name})
336400
}
337401
return false
338402
case *ast.StarExpr:
339-
return getTypeName(file, ty.X, ret)
403+
return getTypeName(fset, file, ty.X, ret)
340404
case *ast.ArrayType:
341-
return getTypeName(file, ty.Elt, ret)
405+
return getTypeName(fset, file, ty.Elt, ret)
342406
case *ast.MapType:
343-
a := getTypeName(file, ty.Key, ret)
344-
b := getTypeName(file, ty.Value, ret)
407+
a := getTypeName(fset, file, ty.Key, ret)
408+
b := getTypeName(fset, file, ty.Value, ret)
345409
return a || b
346410
case *ast.ChanType:
347-
return getTypeName(file, ty.Value, ret)
411+
return getTypeName(fset, file, ty.Value, ret)
348412
case *ast.SelectorExpr:
349413
pkg, ok := ty.X.(*ast.Ident)
350414
if ok {
351415
*ret = append(*ret, ThirdPartyIdentity{Identity: ty.Sel.Name, PkgPath: pkg.Name})
352416
}
353417
return false
354418
case *ast.FuncType:
355-
name := string(file[ty.Func:typ.End()])
419+
name := string(file[fset.Position(ty.Func).Offset:fset.Position(typ.End()).Offset])
356420
*ret = append(*ret, ThirdPartyIdentity{Identity: name})
357421
return true
358422
case *ast.InterfaceType:
359-
name := string(file[ty.Interface:typ.End()])
423+
name := string(file[fset.Position(ty.Interface).Offset:fset.Position(typ.End()).Offset])
360424
*ret = append(*ret, ThirdPartyIdentity{Identity: name})
361425
return false
362426
}

src/compress/golang/plugin/go_ast.go

Lines changed: 39 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,6 @@ import (
55
"bytes"
66
"encoding/json"
77
"fmt"
8-
"go/ast"
9-
"go/parser"
10-
"go/token"
118
"io/ioutil"
129
"os"
1310
"path/filepath"
@@ -91,83 +88,6 @@ func (p *goParser) associateStructWithMethods() {
9188
}
9289
}
9390

94-
// parseFile parse single go file and return all functions in it
95-
// warning: this function has no cache, do not call it with repeated file
96-
func (p *goParser) parseFile(filePath string) (map[string]*Function, error) {
97-
fset := token.NewFileSet()
98-
99-
bs, err := ioutil.ReadFile(filePath)
100-
if err != nil {
101-
return nil, err
102-
}
103-
104-
f, err := parser.ParseFile(fset, filePath, bs, 0)
105-
if err != nil {
106-
return nil, err
107-
}
108-
109-
sysImports, projectImports, thirdPartyImports := p.seprateImports(f.Imports)
110-
pkgPath := p.pkgPathFromABS(filepath.Dir(filePath))
111-
ctx := &fileContext{
112-
filePath: filePath,
113-
pkgPath: pkgPath,
114-
bs: bs,
115-
fset: fset,
116-
sysImports: sysImports,
117-
projectImports: projectImports,
118-
thirdPartyImports: thirdPartyImports,
119-
}
120-
121-
fileFuncs, _, err := p.inspectFile(ctx, f)
122-
return fileFuncs, err
123-
}
124-
125-
func (p *goParser) inspectFile(ctx *fileContext, f *ast.File) (map[string]*Function, map[string]*Struct, error) {
126-
fileStructs := map[string]*Struct{}
127-
fileFuncs := map[string]*Function{}
128-
cont := true
129-
ast.Inspect(f, func(node ast.Node) bool {
130-
if funcDecl, ok := node.(*ast.FuncDecl); ok {
131-
// parse funcs
132-
f, ct := p.parseFunc(ctx, funcDecl)
133-
fileFuncs[f.Name] = f
134-
cont = ct
135-
} else if typDecl, ok := node.(*ast.TypeSpec); ok {
136-
// parse structs
137-
struName := typDecl.Name.Name
138-
struDecl, ok := typDecl.Type.(*ast.StructType)
139-
if ok {
140-
st, ct := p.parseStruct(ctx, struName, struDecl)
141-
fileStructs[struName] = st
142-
cont = ct
143-
}
144-
}
145-
return cont
146-
})
147-
return fileFuncs, fileStructs, nil
148-
}
149-
150-
// ParseDir parse a single go package in the dir
151-
// If the dir has been visted, return cache
152-
func (p *goParser) ParseDir(dir string) (map[string]*Function, error) {
153-
// unify dir ./xxx/xxx -> xxx/xxx
154-
if !strings.HasPrefix(dir, "/") {
155-
dir = filepath.Join(p.homePageDir, dir)
156-
}
157-
pkgPath := p.pkgPathFromABS(dir)
158-
if p.visited[pkgPath] {
159-
return p.processedPkgFunctions[pkgPath], nil
160-
}
161-
for _, f := range getGoFilesInDir(dir) {
162-
_, err := p.parseFile(f)
163-
if err != nil {
164-
return nil, err
165-
}
166-
}
167-
p.visited[pkgPath] = true
168-
return p.processedPkgFunctions[pkgPath], nil
169-
}
170-
17191
// TODO: Parallel transformation
17292
// ParseTilTheEnd parse the all go files from the starDir,
17393
// and their related go files in the project recursively
@@ -179,18 +99,19 @@ func (p *goParser) ParseTilTheEnd(startDir string) error {
17999
return err
180100
}
181101
}
182-
functionList, err := p.ParseDir(startDir)
183-
if err != nil {
102+
if err := p.ParseDir(startDir); err != nil {
184103
return err
185104
}
186-
for _, f := range functionList {
187-
// Notice: local funcs has been parsed in ParseDir
188-
for _, fc := range f.InternalFunctionCalls {
189-
if fc.FilePath != "" || fc.IsMethod {
190-
continue
191-
}
192-
if err := p.ParseTilTheEnd(p.pkgPathToABS(fc.PkgPath)); err != nil {
193-
return err
105+
for _, pkg := range p.processedPkgFunctions {
106+
for _, f := range pkg {
107+
// Notice: local funcs has been parsed in ParseDir
108+
for _, fc := range f.InternalFunctionCalls {
109+
if fc.FilePath != "" {
110+
continue
111+
}
112+
if err := p.ParseTilTheEnd(p.pkgPathToABS(fc.PkgPath)); err != nil {
113+
return err
114+
}
194115
}
195116
}
196117
}
@@ -207,6 +128,32 @@ type MainStream struct {
207128
RelatedStruct []SingleStruct
208129
}
209130

131+
func (m *MainStream) Dedup() {
132+
fs := map[string]string{}
133+
for _, f := range m.RelatedFunctions {
134+
fs[f.CallName] = f.Content
135+
}
136+
m.RelatedFunctions = m.RelatedFunctions[:len(fs)]
137+
i := 0
138+
for k, v := range fs {
139+
m.RelatedFunctions[i].CallName = k
140+
m.RelatedFunctions[i].Content = v
141+
i++
142+
}
143+
144+
fs = map[string]string{}
145+
for _, f := range m.RelatedStruct {
146+
fs[f.Name] = f.Content
147+
}
148+
m.RelatedStruct = m.RelatedStruct[:len(fs)]
149+
i = 0
150+
for k, v := range fs {
151+
m.RelatedStruct[i].Name = k
152+
m.RelatedStruct[i].Content = v
153+
i++
154+
}
155+
}
156+
210157
type SingleFunction struct {
211158
CallName string
212159
Content string
@@ -257,7 +204,7 @@ func (p *goParser) fillRelatedContent(f *Function, fl *[]SingleFunction, sl *[]S
257204
// for method which has been associated with struct, push the struct
258205
if ff.AssociatedStruct != nil && ff.AssociatedStruct.Content != "" {
259206
ss := SingleStruct{
260-
Name: ff.AssociatedStruct.Name,
207+
Name: ff.PkgPath + "." + ff.AssociatedStruct.Name,
261208
Content: ff.AssociatedStruct.Content,
262209
}
263210
*sl = append(*sl, ss)
@@ -309,6 +256,7 @@ func main() {
309256

310257
// p.generateStruct()
311258
m, _ := p.getMain()
259+
m.Dedup()
312260

313261
out := bytes.NewBuffer(nil)
314262
encoder := json.NewEncoder(out)

src/compress/golang/plugin/go_ast_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ func Test_goParser_ParseTilTheEnd(t *testing.T) {
4242
if fun.Name != "main" {
4343
t.Fail()
4444
}
45+
out.Dedup()
4546
if out, err := json.MarshalIndent(out, "", " "); err != nil {
4647
t.Fatalf("json.Marshal() error = %v", err)
4748
} else {

0 commit comments

Comments
 (0)