1
2
3
4
5 package modernize
6
7 import (
8 _ "embed"
9 "fmt"
10 "go/ast"
11 "go/token"
12 "go/types"
13 "slices"
14
15 "golang.org/x/tools/go/analysis"
16 "golang.org/x/tools/go/analysis/passes/inspect"
17 "golang.org/x/tools/go/ast/inspector"
18 "golang.org/x/tools/go/types/typeutil"
19 "golang.org/x/tools/internal/analysis/analyzerutil"
20 "golang.org/x/tools/internal/astutil"
21 "golang.org/x/tools/internal/versions"
22 )
23
24 var NewExprAnalyzer = &analysis.Analyzer{
25 Name: "newexpr",
26 Doc: analyzerutil.MustExtractDoc(doc, "newexpr"),
27 URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#newexpr",
28 Requires: []*analysis.Analyzer{inspect.Analyzer},
29 Run: run,
30 FactTypes: []analysis.Fact{&newLike{}},
31 }
32
33 func run(pass *analysis.Pass) (any, error) {
34 var (
35 inspect = pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
36 info = pass.TypesInfo
37 )
38
39
40
41
42
43
44 for curFuncDecl := range inspect.Root().Preorder((*ast.FuncDecl)(nil)) {
45 decl := curFuncDecl.Node().(*ast.FuncDecl)
46 fn := info.Defs[decl.Name].(*types.Func)
47 if decl.Body != nil && len(decl.Body.List) == 1 {
48 if ret, ok := decl.Body.List[0].(*ast.ReturnStmt); ok && len(ret.Results) == 1 {
49 if unary, ok := ret.Results[0].(*ast.UnaryExpr); ok && unary.Op == token.AND {
50 if id, ok := unary.X.(*ast.Ident); ok {
51 if v, ok := info.Uses[id].(*types.Var); ok {
52 sig := fn.Signature()
53 if sig.Results().Len() == 1 &&
54 is[*types.Pointer](sig.Results().At(0).Type()) &&
55 sig.Params().Len() == 1 &&
56 sig.Params().At(0) == v &&
57 !sig.Variadic() {
58
59
60 pass.ExportObjectFact(fn, &newLike{})
61
62
63 file := astutil.EnclosingFile(curFuncDecl)
64 if !analyzerutil.FileUsesGoVersion(pass, file, versions.Go1_26) {
65 continue
66 }
67
68 var edits []analysis.TextEdit
69
70
71
72 curRet, _ := curFuncDecl.FindNode(ret)
73 if lookup(info, curRet, "new") == builtinNew {
74 edits = []analysis.TextEdit{
75
76
77
78 {
79 Pos: unary.OpPos,
80 End: unary.OpPos + token.Pos(len("&")),
81 NewText: []byte("new("),
82 },
83 {
84 Pos: unary.X.End(),
85 End: unary.X.End(),
86 NewText: []byte(")"),
87 },
88 }
89 }
90
91
92
93
94
95
96
97 if !slices.ContainsFunc(astutil.Directives(decl.Doc), func(d *astutil.Directive) bool {
98 return d.Tool == "go" && d.Name == "fix" && d.Args == "inline"
99 }) {
100 edits = append(edits, analysis.TextEdit{
101 Pos: decl.Pos(),
102 End: decl.Pos(),
103 NewText: []byte("//go:fix inline\n"),
104 })
105 }
106
107 if len(edits) > 0 {
108 pass.Report(analysis.Diagnostic{
109 Pos: decl.Name.Pos(),
110 End: decl.Name.End(),
111 Message: fmt.Sprintf("%s can be an inlinable wrapper around new(expr)", decl.Name),
112 SuggestedFixes: []analysis.SuggestedFix{
113 {
114 Message: "Make %s an inlinable wrapper around new(expr)",
115 TextEdits: edits,
116 },
117 },
118 })
119 }
120 }
121 }
122 }
123 }
124 }
125 }
126 }
127
128
129
130
131 for curCall := range inspect.Root().Preorder((*ast.CallExpr)(nil)) {
132 call := curCall.Node().(*ast.CallExpr)
133 var fact newLike
134 if fn, ok := typeutil.Callee(info, call).(*types.Func); ok &&
135 pass.ImportObjectFact(fn, &fact) {
136
137
138 file := astutil.EnclosingFile(curCall)
139 if !analyzerutil.FileUsesGoVersion(pass, file, versions.Go1_26) {
140 continue
141 }
142
143
144 if lookup(info, curCall, "new") != builtinNew {
145 continue
146 }
147
148
149
150
151 var targ types.Type
152 {
153 arg := call.Args[0]
154 tvarg := info.Types[arg]
155
156
157
158
159
160
161
162
163
164
165
166
167 if tvarg.Value != nil {
168 info2 := &types.Info{Types: make(map[ast.Expr]types.TypeAndValue)}
169 if err := types.CheckExpr(token.NewFileSet(), pass.Pkg, token.NoPos, arg, info2); err != nil {
170 continue
171 }
172 tvarg = info2.Types[arg]
173 }
174
175 targ = types.Default(tvarg.Type)
176 }
177 if !types.Identical(types.NewPointer(targ), info.TypeOf(call)) {
178 continue
179 }
180
181 pass.Report(analysis.Diagnostic{
182 Pos: call.Pos(),
183 End: call.End(),
184 Message: fmt.Sprintf("call of %s(x) can be simplified to new(x)", fn.Name()),
185 SuggestedFixes: []analysis.SuggestedFix{{
186 Message: fmt.Sprintf("Simplify %s(x) to new(x)", fn.Name()),
187 TextEdits: []analysis.TextEdit{{
188 Pos: call.Fun.Pos(),
189 End: call.Fun.End(),
190 NewText: []byte("new"),
191 }},
192 }},
193 })
194 }
195 }
196
197 return nil, nil
198 }
199
200
201 type newLike struct{}
202
203 func (*newLike) AFact() {}
204 func (*newLike) String() string { return "newlike" }
205
View as plain text