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