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
58
59 pass.ExportObjectFact(fn, &newLike{})
60
61
62 file := astutil.EnclosingFile(curFuncDecl)
63 if !analyzerutil.FileUsesGoVersion(pass, file, versions.Go1_26) {
64 continue
65 }
66
67 var edits []analysis.TextEdit
68
69
70
71 curRet, _ := curFuncDecl.FindNode(ret)
72 if lookup(info, curRet, "new") == builtinNew {
73 edits = []analysis.TextEdit{
74
75
76
77 {
78 Pos: unary.OpPos,
79 End: unary.OpPos + token.Pos(len("&")),
80 NewText: []byte("new("),
81 },
82 {
83 Pos: unary.X.End(),
84 End: unary.X.End(),
85 NewText: []byte(")"),
86 },
87 }
88 }
89
90
91
92
93
94
95
96 if !slices.ContainsFunc(astutil.Directives(decl.Doc), func(d *astutil.Directive) bool {
97 return d.Tool == "go" && d.Name == "fix" && d.Args == "inline"
98 }) {
99 edits = append(edits, analysis.TextEdit{
100 Pos: decl.Pos(),
101 End: decl.Pos(),
102 NewText: []byte("//go:fix inline\n"),
103 })
104 }
105
106 if len(edits) > 0 {
107 pass.Report(analysis.Diagnostic{
108 Pos: decl.Name.Pos(),
109 End: decl.Name.End(),
110 Message: fmt.Sprintf("%s can be an inlinable wrapper around new(expr)", decl.Name),
111 SuggestedFixes: []analysis.SuggestedFix{
112 {
113 Message: "Make %s an inlinable wrapper around new(expr)",
114 TextEdits: edits,
115 },
116 },
117 })
118 }
119 }
120 }
121 }
122 }
123 }
124 }
125 }
126
127
128
129
130 for curCall := range inspect.Root().Preorder((*ast.CallExpr)(nil)) {
131 call := curCall.Node().(*ast.CallExpr)
132 var fact newLike
133 if fn, ok := typeutil.Callee(info, call).(*types.Func); ok &&
134 pass.ImportObjectFact(fn, &fact) {
135
136
137 file := astutil.EnclosingFile(curCall)
138 if !analyzerutil.FileUsesGoVersion(pass, file, versions.Go1_26) {
139 continue
140 }
141
142
143 if lookup(info, curCall, "new") != builtinNew {
144 continue
145 }
146
147
148
149
150 var targ types.Type
151 {
152 arg := call.Args[0]
153 tvarg := info.Types[arg]
154
155
156
157
158
159
160
161
162
163
164
165
166 if tvarg.Value != nil {
167 info2 := &types.Info{Types: make(map[ast.Expr]types.TypeAndValue)}
168 if err := types.CheckExpr(token.NewFileSet(), pass.Pkg, token.NoPos, arg, info2); err != nil {
169 continue
170 }
171 tvarg = info2.Types[arg]
172 }
173
174 targ = types.Default(tvarg.Type)
175 }
176 if !types.Identical(types.NewPointer(targ), info.TypeOf(call)) {
177 continue
178 }
179
180 pass.Report(analysis.Diagnostic{
181 Pos: call.Pos(),
182 End: call.End(),
183 Message: fmt.Sprintf("call of %s(x) can be simplified to new(x)", fn.Name()),
184 SuggestedFixes: []analysis.SuggestedFix{{
185 Message: fmt.Sprintf("Simplify %s(x) to new(x)", fn.Name()),
186 TextEdits: []analysis.TextEdit{{
187 Pos: call.Fun.Pos(),
188 End: call.Fun.End(),
189 NewText: []byte("new"),
190 }},
191 }},
192 })
193 }
194 }
195
196 return nil, nil
197 }
198
199
200 type newLike struct{}
201
202 func (*newLike) AFact() {}
203 func (*newLike) String() string { return "newlike" }
204
View as plain text