blob: 4a6655b31488ef1e0402db064c40683ee8fd68b8 [file] [log] [blame]
Tim Windelschmidt1f4590b2025-07-29 23:05:36 +02001// Copyright 2016 The Cockroach Authors.
2// SPDX-License-Identifier: Apache-2.0
3
4// Package unconvert defines an Analyzer that detects unnecessary type
5// conversions.
6package unconvert
7
8import (
9 "go/ast"
10 "go/token"
11 "go/types"
12
13 "golang.org/x/tools/go/analysis"
14 "golang.org/x/tools/go/analysis/passes/inspect"
15 "golang.org/x/tools/go/ast/inspector"
16
17 "source.monogon.dev/third_party/com_github_cockroachdb_cockroach/passesutil"
18)
19
20// Doc documents this pass.
21const Doc = `check for unnecessary type conversions`
22
23const name = "unconvert"
24
25// Analyzer defines this pass.
26var Analyzer = &analysis.Analyzer{
27 Name: name,
28 Doc: Doc,
29 Requires: []*analysis.Analyzer{inspect.Analyzer},
30 Run: run,
31}
32
33// Adapted from https://github.com/mdempsky/unconvert/blob/beb68d938016d2dec1d1b078054f4d3db25f97be/unconvert.go#L371-L414.
34func run(pass *analysis.Pass) (interface{}, error) {
35 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
36
37 nodeFilter := []ast.Node{
38 (*ast.CallExpr)(nil),
39 }
40 inspect.Preorder(nodeFilter, func(n ast.Node) {
41 call, ok := n.(*ast.CallExpr)
42 if !ok {
43 return
44 }
45 if len(call.Args) != 1 || call.Ellipsis != token.NoPos {
46 return
47 }
48 ft, ok := pass.TypesInfo.Types[call.Fun]
49 if !ok {
50 pass.Reportf(call.Pos(), "missing type")
51 return
52 }
53 if !ft.IsType() {
54 // Function call; not a conversion.
55 return
56 }
57 at, ok := pass.TypesInfo.Types[call.Args[0]]
58 if !ok {
59 pass.Reportf(call.Pos(), "missing type")
60 return
61 }
62 if !types.Identical(ft.Type, at.Type) {
63 // A real conversion.
64 return
65 }
66 if isUntypedValue(call.Args[0], pass.TypesInfo) {
67 // Workaround golang.org/issue/13061.
68 return
69 }
70 // Adapted from https://github.com/mdempsky/unconvert/blob/beb68d938016d2dec1d1b078054f4d3db25f97be/unconvert.go#L416-L430.
71 //
72 // cmd/cgo generates explicit type conversions that
73 // are often redundant when introducing
74 // _cgoCheckPointer calls (issue #16). Users can't do
75 // anything about these, so skip over them.
76 if ident, ok := call.Fun.(*ast.Ident); ok {
77 if ident.Name == "_cgoCheckPointer" {
78 return
79 }
80 }
81 if passesutil.HasNolintComment(pass, call, name) {
82 return
83 }
84 pass.Reportf(call.Pos(), "unnecessary conversion")
85 })
86
87 return nil, nil
88}
89
90// Cribbed from https://github.com/mdempsky/unconvert/blob/beb68d938016d2dec1d1b078054f4d3db25f97be/unconvert.go#L557-L607.
91func isUntypedValue(n ast.Expr, info *types.Info) bool {
92 switch n := n.(type) {
93 case *ast.BinaryExpr:
94 switch n.Op {
95 case token.SHL, token.SHR:
96 // Shifts yield an untyped value if their LHS is untyped.
97 return isUntypedValue(n.X, info)
98 case token.EQL, token.NEQ, token.LSS, token.GTR, token.LEQ, token.GEQ:
99 // Comparisons yield an untyped boolean value.
100 return true
101 case token.ADD, token.SUB, token.MUL, token.QUO, token.REM,
102 token.AND, token.OR, token.XOR, token.AND_NOT,
103 token.LAND, token.LOR:
104 return isUntypedValue(n.X, info) && isUntypedValue(n.Y, info)
105 }
106 case *ast.UnaryExpr:
107 switch n.Op {
108 case token.ADD, token.SUB, token.NOT, token.XOR:
109 return isUntypedValue(n.X, info)
110 }
111 case *ast.BasicLit:
112 // Basic literals are always untyped.
113 return true
114 case *ast.ParenExpr:
115 return isUntypedValue(n.X, info)
116 case *ast.SelectorExpr:
117 return isUntypedValue(n.Sel, info)
118 case *ast.Ident:
119 if obj, ok := info.Uses[n]; ok {
120 if obj.Pkg() == nil && obj.Name() == "nil" {
121 // The universal untyped zero value.
122 return true
123 }
124 if b, ok := obj.Type().(*types.Basic); ok && b.Info()&types.IsUntyped != 0 {
125 // Reference to an untyped constant.
126 return true
127 }
128 }
129 case *ast.CallExpr:
130 if b, ok := asBuiltin(n.Fun, info); ok {
131 switch b.Name() {
132 case "real", "imag":
133 return isUntypedValue(n.Args[0], info)
134 case "complex":
135 return isUntypedValue(n.Args[0], info) && isUntypedValue(n.Args[1], info)
136 }
137 }
138 }
139
140 return false
141}
142
143// Cribbed from https://github.com/mdempsky/unconvert/blob/beb68d938016d2dec1d1b078054f4d3db25f97be/unconvert.go#L609-L630.
144func asBuiltin(n ast.Expr, info *types.Info) (*types.Builtin, bool) {
145 for {
146 paren, ok := n.(*ast.ParenExpr)
147 if !ok {
148 break
149 }
150 n = paren.X
151 }
152
153 ident, ok := n.(*ast.Ident)
154 if !ok {
155 return nil, false
156 }
157
158 obj, ok := info.Uses[ident]
159 if !ok {
160 return nil, false
161 }
162
163 b, ok := obj.(*types.Builtin)
164 return b, ok
165}