go fix の裏側
modernize analyzer を読み解く
Backend Engineer @ Finatext
- func ToPtr[T any](x T) *T {
- return &x
- }
Go 1.26: new(expr)
- type data struct {
- x *int
- y *int
- }
- d := data{
- x: ToPtr(10),
- y: ToPtr(10),
- }
- type data struct {
- x *int
- y *int
- }
- d := data{
- x: new(10),
- y: new(10),
- }
Go 1.26: 新しい go fix
- func ToPtr[T any](x T) *T {
- return &x
- }
- var _ = ToPtr(123)
⬇ go fix ./...
- //go:fix inline
- func ToPtr[T any](x T) *T {
- return new(x)
- }
- var _ = new(123)
なぜこれを理解する必要があるのか?
LLMの時代はLinterの時代
LLMの時代はLinterの時代
LLMの時代はLinterの時代
LLMの時代はLinterの時代
modernizer の仕組み
- package main
- import (
- "golang.org/x/tools/go/analysis/
multichecker"
- "golang.org/x/tools/go/analysis/
passes/modernize"
- )
- func main() {
- multichecker.Main(modernize.Suite...)
- }
- package modernize
- var Suite = []*analysis.Analyzer{
- AnyAnalyzer,
- // AppendClippedAnalyzer, // not nil-preserving!
- // BLoopAnalyzer, // may skew benchmark results, see golang/go#74967
- FmtAppendfAnalyzer,
- ForVarAnalyzer,
- MapsLoopAnalyzer,
- MinMaxAnalyzer,
- NewExprAnalyzer,
- ...
- }
- package modernize
- var Suite = []*analysis.Analyzer{
- AnyAnalyzer,
- // AppendClippedAnalyzer, // not nil-preserving!
- // BLoopAnalyzer, // may skew benchmark results, see golang/go#74967
- FmtAppendfAnalyzer,
- ForVarAnalyzer,
- MapsLoopAnalyzer,
- MinMaxAnalyzer,
- NewExprAnalyzer,
- ...
- }
- var NewExprAnalyzer = &analysis.Analyzer{
- Name: "newexpr",
- Doc: ...,
- URL: ...,
- Requires: []*analysis.Analyzer{
- inspect.Analyzer,
- },
- Run: run,
- FactTypes: []analysis.Fact{&newLike{}},
- }
- var NewExprAnalyzer = &analysis.Analyzer{
- ...
- Requires: []*analysis.Analyzer{
- inspect.Analyzer,
- },
- Run: run,
- FactTypes: []analysis.Fact{&newLike{}},
- }
- func run(pass *analysis.Pass) (any, error) {
- var (
- inspect = pass.ResultOf[inspect.Analyzer].
(*inspector.Inspector)
- info = pass.TypesInfo
- )
- ...
- var NewExprAnalyzer = &analysis.Analyzer{
- ...
- Requires: []*analysis.Analyzer{
- inspect.Analyzer,
- },
- Run: run,
- FactTypes: []analysis.Fact{&newLike{}},
- }
- func run(pass *analysis.Pass) (any, error) {
- var (
- inspect = pass.ResultOf[inspect.Analyzer].
(*inspector.Inspector)
- info = pass.TypesInfo
- )
- ...
- var NewExprAnalyzer = &analysis.Analyzer{
- ...
- Requires: []*analysis.Analyzer{
- inspect.Analyzer,
- },
- Run: run,
- FactTypes: []analysis.Fact{&newLike{}},
- }
- func run(pass *analysis.Pass) (any, error) {
- var (
- inspect = pass.ResultOf[inspect.Analyzer].
(*inspector.Inspector)
- info = pass.TypesInfo
- )
- ...
- var NewExprAnalyzer = &analysis.Analyzer{
- ...
- Requires: []*analysis.Analyzer{
- inspect.Analyzer,
- },
- Run: run,
- FactTypes: []analysis.Fact{&newLike{}},
- }
- func run(pass *analysis.Pass) (any, error) {
- var (
- inspect = pass.ResultOf[inspect.Analyzer].
(*inspector.Inspector)
- info = pass.TypesInfo
- )
- ...
- package inspect
- var Analyzer = &analysis.Analyzer{
- Name: "inspect",
- Doc: ...,
- URL: ...,
- Run: run,
- RunDespiteErrors: true,
- ResultType: reflect.
TypeFor[*inspector.Inspector](),
- }
- func run(pass *analysis.Pass) (any, error) {
- ...
- package inspect
- var Analyzer = &analysis.Analyzer{
- Name: "inspect",
- Doc: ...,
- URL: ...,
- Run: run,
- RunDespiteErrors: true,
- ResultType: reflect.
TypeFor[*inspector.Inspector](),
- }
- func run(pass *analysis.Pass) (any, error) {
- ...
- var NewExprAnalyzer = &analysis.Analyzer{
- ...
- Requires: []*analysis.Analyzer{
- inspect.Analyzer,
- },
- Run: run,
- FactTypes: []analysis.Fact{&newLike{}},
- }
- func run(pass *analysis.Pass) (any, error) {
- var (
- inspect = pass.ResultOf[inspect.Analyzer].
(*inspector.Inspector)
- info = pass.TypesInfo
- )
- ...
Part 1:
対象関数を見つける
- for curFuncDecl := range inspect.Root().
- Preorder((*ast.FuncDecl)(nil)) {
- decl := curFuncDecl.Node().(*ast.FuncDecl)
- fn := info.Defs[decl.Name].(*types.Func)
- func _(){}
- for curFuncDecl := range inspect.Root().
- Preorder((*ast.FuncDecl)(nil)) {
- decl := curFuncDecl.Node().(*ast.FuncDecl)
- fn := info.Defs[decl.Name].(*types.Func)
- func _(){}
- if decl.Body != nil &&
- len(decl.Body.List) == 1 {
- func _(){
- 〇〇
- }
- if ret, ok := decl.Body.List[0]. (*ast.ReturnStmt);
- ok && len(ret.Results) == 1 {
- func _(){
- return 〇〇
- }
- if unary, ok := ret.Results[0].(*ast.UnaryExpr);
- ok && unary.Op == token.AND {
- func _(){
- return &〇〇
- }
- if id, ok := unary.X.(*ast.Ident); ok {
- func _(){
- return &v
- }
- if v, ok := info.Uses[id].(*types.Var); ok {
- func _(){
- return &v
- }
- sig := fn.Signature()
- if sig.Results().Len() == 1 &&
- is[*types.Pointer](
sig.Results().At(0).Type()) &&
- sig.Params().Len() == 1 &&
- sig.Params().At(0) == v {
- func _(){
- return &v
- }
- sig := fn.Signature()
- if sig.Results().Len() == 1 &&
- is[*types.Pointer](
sig.Results().At(0).Type()) &&
- sig.Params().Len() == 1 &&
- sig.Params().At(0) == v {
- func _() T{
- return &v
- }
- sig := fn.Signature()
- if sig.Results().Len() == 1 &&
- is[*types.Pointer](
sig.Results().At(0).Type()) &&
- sig.Params().Len() == 1 &&
- sig.Params().At(0) == v {
- func _() *T{
- return &v
- }
- sig := fn.Signature()
- if sig.Results().Len() == 1 &&
- is[*types.Pointer](
sig.Results().At(0).Type()) &&
- sig.Params().Len() == 1 &&
- sig.Params().At(0) == v {
- func _(〇〇 〇〇) *T{
- return &v
- }
- sig := fn.Signature()
- if sig.Results().Len() == 1 &&
- is[*types.Pointer](
sig.Results().At(0).Type()) &&
- sig.Params().Len() == 1 &&
- sig.Params().At(0) == v {
- func _(v T) *T{
- return &v
- }
- pass.ExportObjectFact(fn, &newLike{})
- var NewExprAnalyzer = &analysis.Analyzer{
- ...
- FactTypes: []analysis.Fact{&newLike{}},
- }
- func run(pass *analysis.Pass) (any, error) {
- ...
- pass.ExportObjectFact(fn, &newLike{})
- ...
- }
- var NewExprAnalyzer = &analysis.Analyzer{
- ...
- FactTypes: []analysis.Fact{&newLike{}},
- }
- func run(pass *analysis.Pass) (any, error) {
- ...
- pass.ExportObjectFact(fn, &newLike{})
- ...
- }
- type newLike struct{}
- func (*newLike) AFact() {}
- func (*newLike) String() string {
- return "newlike"
- }
Part 2:
関数内のnew対応
- file := astutil.EnclosingFile(curFuncDecl)
- if !analyzerutil.FileUsesGoVersion(
- pass, file, versions.Go1_26,
- ) {
- continue
- }
- var edits []analysis.TextEdit
- curRet, _ := curFuncDecl.FindNode(ret)
- if lookup(info, curRet, "new") == builtinNew {
- edits = []analysis.TextEdit{
- {
- Pos: unary.OpPos,
- End: unary.OpPos + token.Pos(len("&")),
- NewText: []byte("new("),
- },
- {
- Pos: unary.X.End(),
- End: unary.X.End(),
- NewText: []byte(")"),
- },
- }
- }
Part 3:
アノテーション
- if !strings.Contains(
- decl.Doc.Text(),
- "go:fix inline",
- ) {
- edits = append(edits, analysis.TextEdit{
- Pos: decl.Pos(),
- End: decl.Pos(),
- NewText: []byte("//go:fix inline\n"),
- })
- }
- if len(edits) > 0 {
- pass.Report(analysis.Diagnostic{
- Pos: decl.Name.Pos(),
- End: decl.Name.End(),
- Message: fmt.Sprintf(
- "%s can be an inlinable"+
- " wrapper around new(expr)",
- decl.Name),
- SuggestedFixes: []analysis.SuggestedFix{{
- Message: "Make %s an inlinable" +
- " wrapper around new(expr)",
- TextEdits: edits,
- }},
- })
- }
Part 3:
呼び出し箇所の対応
- }
- }
- }
- }
- }
- }
- }
- for curCall := range inspect.Root().
- Preorder((*ast.CallExpr)(nil)) {
- call := curCall.Node().(*ast.CallExpr)
- var fact newLike
- if fn, ok := typeutil.Callee(info, call).
(*types.Func); ok &&
- pass.ImportObjectFact(fn, &fact) {
- file := astutil.EnclosingFile(curCall)
- if !analyzerutil.FileUsesGoVersion(
- pass, file, versions.Go1_26,
- ) {
- continue
- }
- if lookup(info, curCall, "new") != builtinNew {
- continue
- }
- var targ types.Type
- {
- arg := call.Args[0]
- tvarg := info.Types[arg]
- if tvarg.Value != nil {
- info2 := &types.Info{
- Types: make(map[ast.Expr]types.TypeAndValue),
- }
- if err := types.CheckExpr(
- token.NewFileSet(), pass.Pkg,
- token.NoPos, arg, info2); err != nil {
- continue
- }
- tvarg = info2.Types[arg]
- }
- targ = types.Default(tvarg.Type)
- }
- if !types.Identical(
- types.NewPointer(targ),
- info.TypeOf(call),
- ) {
- continue
- }
- pass.Report(analysis.Diagnostic{
- Pos: call.Pos(),
- End: call.End(),
- Message: fmt.Sprintf(
- "call of %s(x) can be simplified to new(x)",
- fn.Name()),
- SuggestedFixes: []analysis.SuggestedFix{{
- Message: fmt.Sprintf(
- "Simplify %s(x) to new(x)", fn.Name()),
- TextEdits: []analysis.TextEdit{{
- Pos: call.Fun.Pos(),
- End: call.Fun.End(),
- NewText: []byte("new"),
- }},
- }},
- })
- }
- }
- return nil, nil
- }
覚えてほしいこと
- リンターは1パッケージずつのASTしか見れない
- Requires と pass.ResultOf で別のリンターの ResultType を受けることができる
- pass.ExportObjectFact と pass.ImportObjectFact で
事実を共有することができる
ご清聴ありがとうございました!
Dennis Metzger
Go 1.26 Release Party