Search code examples
gotypesstatic-analysis

Finding the type of a variable through static analysis?


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)

Solution

  • 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).