mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-25 23:42:23 +00:00
compiler: use fully-qualified names for tracking functions
Function name now consists of 3 parts: 1) full package path 2) method receiver type (if any) 3) function name itself . Fix #1150. Signed-off-by: Evgenii Stratonikov <evgeniy@nspcc.ru>
This commit is contained in:
parent
528c184f00
commit
a781d299e0
8 changed files with 87 additions and 15 deletions
|
@ -113,14 +113,15 @@ func (c *codegen) analyzeFuncUsage() funcUsage {
|
||||||
case *ast.CallExpr:
|
case *ast.CallExpr:
|
||||||
switch t := n.Fun.(type) {
|
switch t := n.Fun.(type) {
|
||||||
case *ast.Ident:
|
case *ast.Ident:
|
||||||
usage[t.Name] = true
|
usage[c.getIdentName("", t.Name)] = true
|
||||||
case *ast.SelectorExpr:
|
case *ast.SelectorExpr:
|
||||||
usage[t.Sel.Name] = true
|
name, _ := c.getFuncNameFromSelector(t)
|
||||||
|
usage[name] = true
|
||||||
}
|
}
|
||||||
case *ast.FuncDecl:
|
case *ast.FuncDecl:
|
||||||
// exported functions are always assumed to be used
|
// exported functions are always assumed to be used
|
||||||
if isMain && n.Name.IsExported() {
|
if isMain && n.Name.IsExported() {
|
||||||
usage[n.Name.Name] = true
|
usage[c.getFuncNameFromDecl(pkg.Path(), n)] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -68,6 +68,9 @@ type codegen struct {
|
||||||
// constMap contains constants from foreign packages.
|
// constMap contains constants from foreign packages.
|
||||||
constMap map[string]types.TypeAndValue
|
constMap map[string]types.TypeAndValue
|
||||||
|
|
||||||
|
// currPkg is current package being processed.
|
||||||
|
currPkg *types.Package
|
||||||
|
|
||||||
// mainPkg is a main package metadata.
|
// mainPkg is a main package metadata.
|
||||||
mainPkg *loader.PackageInfo
|
mainPkg *loader.PackageInfo
|
||||||
|
|
||||||
|
@ -278,19 +281,18 @@ func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl, pkg *types.
|
||||||
ok, isLambda bool
|
ok, isLambda bool
|
||||||
)
|
)
|
||||||
|
|
||||||
f, ok = c.funcs[decl.Name.Name]
|
f, ok = c.funcs[c.getFuncNameFromDecl("", decl)]
|
||||||
if ok {
|
if ok {
|
||||||
// If this function is a syscall or builtin we will not convert it to bytecode.
|
// If this function is a syscall or builtin we will not convert it to bytecode.
|
||||||
if isSyscall(f) || isCustomBuiltin(f) {
|
if isSyscall(f) || isCustomBuiltin(f) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.setLabel(f.label)
|
c.setLabel(f.label)
|
||||||
} else if f, ok = c.lambda[decl.Name.Name]; ok {
|
} else if f, ok = c.lambda[c.getIdentName("", decl.Name.Name)]; ok {
|
||||||
isLambda = ok
|
isLambda = ok
|
||||||
c.setLabel(f.label)
|
c.setLabel(f.label)
|
||||||
} else {
|
} else {
|
||||||
f = c.newFunc(decl)
|
f = c.newFunc(decl)
|
||||||
f.pkg = pkg
|
|
||||||
}
|
}
|
||||||
|
|
||||||
f.rng.Start = uint16(c.prog.Len())
|
f.rng.Start = uint16(c.prog.Len())
|
||||||
|
@ -738,7 +740,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
|
|
||||||
switch fun := n.Fun.(type) {
|
switch fun := n.Fun.(type) {
|
||||||
case *ast.Ident:
|
case *ast.Ident:
|
||||||
f, ok = c.funcs[fun.Name]
|
f, ok = c.funcs[c.getIdentName("", fun.Name)]
|
||||||
isBuiltin = isGoBuiltin(fun.Name)
|
isBuiltin = isGoBuiltin(fun.Name)
|
||||||
if !ok && !isBuiltin {
|
if !ok && !isBuiltin {
|
||||||
name = fun.Name
|
name = fun.Name
|
||||||
|
@ -751,13 +753,14 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
||||||
// If this is a method call we need to walk the AST to load the struct locally.
|
// If this is a method call we need to walk the AST to load the struct locally.
|
||||||
// Otherwise this is a function call from a imported package and we can call it
|
// Otherwise this is a function call from a imported package and we can call it
|
||||||
// directly.
|
// directly.
|
||||||
if c.typeInfo.Selections[fun] != nil {
|
name, isMethod := c.getFuncNameFromSelector(fun)
|
||||||
|
if isMethod {
|
||||||
ast.Walk(c, fun.X)
|
ast.Walk(c, fun.X)
|
||||||
// Dont forget to add 1 extra argument when its a method.
|
// Dont forget to add 1 extra argument when its a method.
|
||||||
numArgs++
|
numArgs++
|
||||||
}
|
}
|
||||||
|
|
||||||
f, ok = c.funcs[fun.Sel.Name]
|
f, ok = c.funcs[name]
|
||||||
// @FIXME this could cause runtime errors.
|
// @FIXME this could cause runtime errors.
|
||||||
f.selector = fun.X.(*ast.Ident)
|
f.selector = fun.X.(*ast.Ident)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -1431,18 +1434,30 @@ func (c *codegen) convertToken(tok token.Token) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *codegen) newFunc(decl *ast.FuncDecl) *funcScope {
|
func (c *codegen) newFunc(decl *ast.FuncDecl) *funcScope {
|
||||||
f := newFuncScope(decl, c.newLabel())
|
f := c.newFuncScope(decl, c.newLabel())
|
||||||
c.funcs[f.name] = f
|
c.funcs[c.getFuncNameFromDecl("", decl)] = f
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getFuncNameFromSelector returns fully-qualified function name from the selector expression.
|
||||||
|
// Second return value is true iff this was a method call, not foreign package call.
|
||||||
|
func (c *codegen) getFuncNameFromSelector(e *ast.SelectorExpr) (string, bool) {
|
||||||
|
ident := e.X.(*ast.Ident)
|
||||||
|
if c.typeInfo.Selections[e] != nil {
|
||||||
|
typ := c.typeInfo.Types[ident].Type.String()
|
||||||
|
return c.getIdentName(typ, e.Sel.Name), true
|
||||||
|
}
|
||||||
|
return c.getIdentName(ident.Name, e.Sel.Name), false
|
||||||
|
}
|
||||||
|
|
||||||
func (c *codegen) newLambda(u uint16, lit *ast.FuncLit) {
|
func (c *codegen) newLambda(u uint16, lit *ast.FuncLit) {
|
||||||
name := fmt.Sprintf("lambda@%d", u)
|
name := fmt.Sprintf("lambda@%d", u)
|
||||||
c.lambda[name] = newFuncScope(&ast.FuncDecl{
|
f := c.newFuncScope(&ast.FuncDecl{
|
||||||
Name: ast.NewIdent(name),
|
Name: ast.NewIdent(name),
|
||||||
Type: lit.Type,
|
Type: lit.Type,
|
||||||
Body: lit.Body,
|
Body: lit.Body,
|
||||||
}, u)
|
}, u)
|
||||||
|
c.lambda[c.getFuncNameFromDecl("", f.decl)] = f
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *codegen) compile(info *buildInfo, pkg *loader.PackageInfo) error {
|
func (c *codegen) compile(info *buildInfo, pkg *loader.PackageInfo) error {
|
||||||
|
@ -1472,7 +1487,8 @@ func (c *codegen) compile(info *buildInfo, pkg *loader.PackageInfo) error {
|
||||||
case *ast.FuncDecl:
|
case *ast.FuncDecl:
|
||||||
// Don't convert the function if it's not used. This will save a lot
|
// Don't convert the function if it's not used. This will save a lot
|
||||||
// of bytecode space.
|
// of bytecode space.
|
||||||
if funUsage.funcUsed(n.Name.Name) {
|
name := c.getFuncNameFromDecl(pkg.Path(), n)
|
||||||
|
if funUsage.funcUsed(name) && !isInteropPath(pkg.Path()) {
|
||||||
c.convertFuncDecl(f, n, pkg)
|
c.convertFuncDecl(f, n, pkg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1519,7 +1535,6 @@ func (c *codegen) resolveFuncDecls(f *ast.File, pkg *types.Package) {
|
||||||
switch n := decl.(type) {
|
switch n := decl.(type) {
|
||||||
case *ast.FuncDecl:
|
case *ast.FuncDecl:
|
||||||
c.newFunc(n)
|
c.newFunc(n)
|
||||||
c.funcs[n.Name.Name].pkg = pkg
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,7 @@ type buildInfo struct {
|
||||||
func (c *codegen) ForEachFile(fn func(*ast.File, *types.Package)) {
|
func (c *codegen) ForEachFile(fn func(*ast.File, *types.Package)) {
|
||||||
for _, pkg := range c.buildInfo.program.AllPackages {
|
for _, pkg := range c.buildInfo.program.AllPackages {
|
||||||
c.typeInfo = &pkg.Info
|
c.typeInfo = &pkg.Info
|
||||||
|
c.currPkg = pkg.Pkg
|
||||||
for _, f := range pkg.Files {
|
for _, f := range pkg.Files {
|
||||||
c.fillImportMap(f, pkg.Pkg)
|
c.fillImportMap(f, pkg.Pkg)
|
||||||
fn(f, pkg.Pkg)
|
fn(f, pkg.Pkg)
|
||||||
|
|
|
@ -152,6 +152,8 @@ func (c *codegen) methodInfoFromScope(name string, scope *funcScope) *MethodDebu
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ss := strings.Split(name, ".")
|
||||||
|
name = ss[len(ss)-1]
|
||||||
return &MethodDebugInfo{
|
return &MethodDebugInfo{
|
||||||
ID: name,
|
ID: name,
|
||||||
Name: DebugMethodName{
|
Name: DebugMethodName{
|
||||||
|
|
|
@ -45,7 +45,7 @@ type funcScope struct {
|
||||||
i int
|
i int
|
||||||
}
|
}
|
||||||
|
|
||||||
func newFuncScope(decl *ast.FuncDecl, label uint16) *funcScope {
|
func (c *codegen) newFuncScope(decl *ast.FuncDecl, label uint16) *funcScope {
|
||||||
var name string
|
var name string
|
||||||
if decl.Name != nil {
|
if decl.Name != nil {
|
||||||
name = decl.Name.Name
|
name = decl.Name.Name
|
||||||
|
@ -54,6 +54,7 @@ func newFuncScope(decl *ast.FuncDecl, label uint16) *funcScope {
|
||||||
name: name,
|
name: name,
|
||||||
decl: decl,
|
decl: decl,
|
||||||
label: label,
|
label: label,
|
||||||
|
pkg: c.currPkg,
|
||||||
vars: newVarScope(),
|
vars: newVarScope(),
|
||||||
voidCalls: map[*ast.CallExpr]bool{},
|
voidCalls: map[*ast.CallExpr]bool{},
|
||||||
variables: []string{},
|
variables: []string{},
|
||||||
|
@ -61,6 +62,14 @@ func newFuncScope(decl *ast.FuncDecl, label uint16) *funcScope {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *codegen) getFuncNameFromDecl(pkgPath string, decl *ast.FuncDecl) string {
|
||||||
|
name := decl.Name.Name
|
||||||
|
if decl.Recv != nil {
|
||||||
|
name = decl.Recv.List[0].Type.(*ast.Ident).Name + "." + name
|
||||||
|
}
|
||||||
|
return c.getIdentName(pkgPath, name)
|
||||||
|
}
|
||||||
|
|
||||||
// analyzeVoidCalls checks for functions that are not assigned
|
// analyzeVoidCalls checks for functions that are not assigned
|
||||||
// and therefore we need to cleanup the return value from the stack.
|
// and therefore we need to cleanup the return value from the stack.
|
||||||
func (c *funcScope) analyzeVoidCalls(node ast.Node) bool {
|
func (c *funcScope) analyzeVoidCalls(node ast.Node) bool {
|
||||||
|
|
|
@ -196,3 +196,33 @@ func TestExportedConst(t *testing.T) {
|
||||||
}`
|
}`
|
||||||
eval(t, src, big.NewInt(42))
|
eval(t, src, big.NewInt(42))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMultipleFuncSameName(t *testing.T) {
|
||||||
|
t.Run("Simple", func(t *testing.T) {
|
||||||
|
src := `package foo
|
||||||
|
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/multi"
|
||||||
|
func Main() int {
|
||||||
|
return multi.Sum() + Sum()
|
||||||
|
}
|
||||||
|
func Sum() int {
|
||||||
|
return 11
|
||||||
|
}`
|
||||||
|
eval(t, src, big.NewInt(53))
|
||||||
|
})
|
||||||
|
t.Run("WithMethod", func(t *testing.T) {
|
||||||
|
src := `package foo
|
||||||
|
import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/foo"
|
||||||
|
type Foo struct{}
|
||||||
|
func (f Foo) Bar() int { return 11 }
|
||||||
|
func Bar() int { return 22 }
|
||||||
|
func Main() int {
|
||||||
|
var a Foo
|
||||||
|
var b foo.Foo
|
||||||
|
return a.Bar() + // 11
|
||||||
|
foo.Bar() + // 1
|
||||||
|
b.Bar() + // 8
|
||||||
|
Bar() // 22
|
||||||
|
}`
|
||||||
|
eval(t, src, big.NewInt(42))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
13
pkg/compiler/testdata/foo/foo.go
vendored
13
pkg/compiler/testdata/foo/foo.go
vendored
|
@ -4,3 +4,16 @@ package foo
|
||||||
func NewBar() int {
|
func NewBar() int {
|
||||||
return 10
|
return 10
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Foo is a type.
|
||||||
|
type Foo struct{}
|
||||||
|
|
||||||
|
// Bar is a function.
|
||||||
|
func Bar() int {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bar is a method.
|
||||||
|
func (f Foo) Bar() int {
|
||||||
|
return 8
|
||||||
|
}
|
||||||
|
|
|
@ -74,6 +74,7 @@ func vmAndCompileInterop(t *testing.T, src string) (*vm.VM, *storagePlugin) {
|
||||||
|
|
||||||
b, di, err := compiler.CompileWithDebugInfo(strings.NewReader(src))
|
b, di, err := compiler.CompileWithDebugInfo(strings.NewReader(src))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
invokeMethod(t, testMainIdent, b, vm, di)
|
invokeMethod(t, testMainIdent, b, vm, di)
|
||||||
return vm, storePlugin
|
return vm, storePlugin
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue