通过静态分析找到变量的类型?

huangapple go评论78阅读模式
英文:

Finding the type of a variable through static analysis?

问题

如何通过静态分析确定变量的类型?

假设我有以下代码:

func doSomething(x interface{}) {}

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

我想分析doSomething(person),是否可以通过静态分析获取Person的类型?

如果有多层赋值会怎样?

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

或者

parent := Parent()
p := Parent.Child() // 类型为Person
doSomething(p)

使用情况是,我有一个通用函数在整个(非常大的)代码库中经常使用,并且希望引入一个新的类型安全版本的该函数。为了做到这一点,我希望自动确定函数的“类型”,并相应地进行重构:

// 旧的
DB.InsertRow(person)

// 新的
Person.InsertRow(person)
英文:

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)

答案1

得分: 1

通过静态分析找到表达式的类型是一项复杂的任务,有时甚至是不可能的,具体请参考https://stackoverflow.com/questions/27976493/golang-static-identifier-resolution。

使用案例是我有一个在整个(非常庞大)代码库中常用的通用函数,并且希望引入一个新的类型安全版本的该函数。为了做到这一点,我希望自动确定函数的“类型”并相应地进行重构:

// 旧的
DB.InsertRow(person)

// 新的
Person.InsertRow(person)

仅出于重构的目的,我认为实现它并不值得麻烦。

你可以临时更改DB.InsertRow()的签名,只接受特定类型,例如int或你确定在任何地方都没有使用的自定义类型(例如type tempFoo struct{})。

这样做的目的是什么?这样一来,编译器将为你完成艰巨的工作。你将看到错误消息,显示代码库试图传递给DB.InsertRow()的确切类型,所以我认为任务完成了。

例如,以下代码可以编译通过:

func doSomething(x interface{}) {}

func main() {
    doSomething(image.Pt(1, 2))
    doSomething("abc")
    doSomething(image.Rect) // image.Rect是一个我们没有调用的函数,
                            // 所以我们在这里传递了一个函数类型的值
}

如果我们更改doSomething()

func doSomething(x int) {}

我们将从编译器得到我们正在寻找的类型:

./prog.go:10:14: 无法将类型为 image.Point 的 image.Pt(1, 2) 作为 int 类型的参数传递给 doSomething

./prog.go:11:14: 无法将无类型的 字符串常量 "abc" 作为 int 值的参数传递给 doSomething

./prog.go:12:14: 无法将类型为 func(x0 int, y0 int, x1 int, y1 int) image.Rectangle 的 image.Rect 作为 int 类型的参数传递给 doSomething

英文:

Finding the type of an expression through static analysis is non-trivial, and sometimes not possible, for details see https://stackoverflow.com/questions/27976493/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

答案2

得分: 1

使用来自https://stackoverflow.com/questions/27976493/golang-static-identifier-resolution的建议,使用golang.org/x/tools/go/types,我发现使用golang.org/x/tools/go/analysis包相当简单,该包提供了可用的类型信息与解析的ast并列

这是我的解决方案:

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
		}

		// 检查方法名
		if funcExpr.Sel.Name != "doSomething" {
			return true
		}

		for _, arg := range callExpr.Args {
			// 查找参数的类型
			argType := pass.TypesInfo.Types[arg].Type
			if argType.String() == "*rewriter.Person" {
				// 在这里进行你想要的操作
			}
		}
		return false
	})
	return nil, nil
}

可以根据需要扩展此代码,查看方法的接收器并添加重构逻辑(使用analysis.Diagnostic)。

英文:

Using the advice from https://stackoverflow.com/questions/27976493/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).

huangapple
  • 本文由 发表于 2022年5月26日 09:47:10
  • 转载请务必保留本文链接:https://go.coder-hub.com/72385824.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定