Finding the type of a variable through static analysis?

Viewed 161

How can I determine the type of a variable through static analysis?

Suppose I have the following code:

func doSomething(x interface{}) {}

func main() {
  p := Person()
  doSomething(p)
}

And I want to analyze doSomething(person), is it possible to get the type of Person through static analysis?

What if there were multiple levels of assignment?

p1 := Person()
p2 := p1
doSomething(p2)

or

parent := Parent()
p := Parent.Child() // type Person
doSomething(p)

The use case is that I have a generic function that is commonly used throughout the (very large) codebase, and would like to introduce a new type safe version of this function. To do this, I hope to automatically determine the "type" of the function and refactor it accordingly:

// old
DB.InsertRow(person)

// new
Person.InsertRow(person)
2 Answers

Finding the type of an expression through static analysis is non-trivial, and sometimes not possible, for details see Golang static identifier resolution.

The use case is that I have a generic function that is commonly used throughout the (very large) codebase, and would like to introduce a new type safe version of this function. To do this, I hope to automatically determine the "type" of the function and refactor it accordingly:

// old
DB.InsertRow(person)

// new
Person.InsertRow(person)

Just for refactoring purposes, I don't think it is worth the hassle to implement it.

What you may do is change the signature of DB.InsertRow() temporarily to accept only a specific type such as int or your custom type you're sure is not used anywhere (e.g. type tempFoo struct{}).

To what end? Doing so, the compiler will do the hard work for you. You will see error messages showing exactly the types your codebase is trying to pass to DB.InsertRow(), so I'd say mission accomplished.

For example this code compiles:

func doSomething(x interface{}) {}

func main() {
    doSomething(image.Pt(1, 2))
    doSomething("abc")
    doSomething(image.Rect) // image.Rect is a function which we don't call,
                            // so we're passing a value of a function type here
}

If we change doSomething():

func doSomething(x int) {}

We get the types we're seeking for from the compiler:

./prog.go:10:14: cannot use image.Pt(1, 2) (value of type image.Point) as type int in argument to doSomething

./prog.go:11:14: cannot use "abc" (untyped string constant) as int value in argument to doSomething

./prog.go:12:14: cannot use image.Rect (value of type func(x0 int, y0 int, x1 int, y1 int) image.Rectangle) as type int in argument to doSomething

Using the advice from Golang static identifier resolution to use golang.org/x/tools/go/types, I found that this was pretty straight forward to do with the golang.org/x/tools/go/analysis package, which has the types info available alongside the parsed ast.

This was my solution:

package rewriter

import (
    "go/ast"

    "golang.org/x/tools/go/analysis"

    "golang.org/x/tools/go/analysis/passes/inspect"

    "golang.org/x/tools/go/ast/inspector"
)

func run(pass *analysis.Pass) (interface{}, error) {
    inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)

    nodeFilter := []ast.Node{
        (*ast.CallExpr)(nil),
    }

    inspect.Nodes(nodeFilter, func(node ast.Node, push bool) bool {
        callExpr, ok := node.(*ast.CallExpr)
        if !ok {
            return true
        }

        funcExpr, ok := callExpr.Fun.(*ast.SelectorExpr)
        if !ok {
            return true
        }

        // check method name
        if funcExpr.Sel.Name != "doSomething" {
            return true
        }

        for _, arg := range callExpr.Args {
            // lookup type of the arg
            argType := pass.TypesInfo.Types[arg].Type
            if argType.String() == "*rewriter.Person" {
                // do whatever you want here
            }
        }
        return false
    })
    return nil, nil
}

One can augment this to look at the receiver of the method and add refactoring logic as needed (using analysis.Diagnostic).

Related