github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/testutils/lint/passes/unconvert/unconvert.go (about) 1 // Copyright 2016 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 // Package unconvert defines an Analyzer that detects unnecessary type 12 // conversions. 13 package unconvert 14 15 import ( 16 "go/ast" 17 "go/token" 18 "go/types" 19 20 "golang.org/x/tools/go/analysis" 21 "golang.org/x/tools/go/analysis/passes/inspect" 22 "golang.org/x/tools/go/ast/inspector" 23 ) 24 25 // Doc documents this pass. 26 const Doc = `check for unnecessary type conversions` 27 28 // Analyzer defines this pass. 29 var Analyzer = &analysis.Analyzer{ 30 Name: "unconvert", 31 Doc: Doc, 32 Requires: []*analysis.Analyzer{inspect.Analyzer}, 33 Run: run, 34 } 35 36 // Adapted from https://github.com/mdempsky/unconvert/blob/beb68d938016d2dec1d1b078054f4d3db25f97be/unconvert.go#L371-L414. 37 func run(pass *analysis.Pass) (interface{}, error) { 38 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) 39 40 nodeFilter := []ast.Node{ 41 (*ast.CallExpr)(nil), 42 } 43 inspect.Preorder(nodeFilter, func(n ast.Node) { 44 call, ok := n.(*ast.CallExpr) 45 if !ok { 46 return 47 } 48 if len(call.Args) != 1 || call.Ellipsis != token.NoPos { 49 return 50 } 51 ft, ok := pass.TypesInfo.Types[call.Fun] 52 if !ok { 53 pass.Reportf(call.Pos(), "missing type") 54 return 55 } 56 if !ft.IsType() { 57 // Function call; not a conversion. 58 return 59 } 60 at, ok := pass.TypesInfo.Types[call.Args[0]] 61 if !ok { 62 pass.Reportf(call.Pos(), "missing type") 63 return 64 } 65 if !types.Identical(ft.Type, at.Type) { 66 // A real conversion. 67 return 68 } 69 if isUntypedValue(call.Args[0], pass.TypesInfo) { 70 // Workaround golang.org/issue/13061. 71 return 72 } 73 // Adapted from https://github.com/mdempsky/unconvert/blob/beb68d938016d2dec1d1b078054f4d3db25f97be/unconvert.go#L416-L430. 74 // 75 // cmd/cgo generates explicit type conversions that 76 // are often redundant when introducing 77 // _cgoCheckPointer calls (issue #16). Users can't do 78 // anything about these, so skip over them. 79 if ident, ok := call.Fun.(*ast.Ident); ok { 80 if ident.Name == "_cgoCheckPointer" { 81 return 82 } 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. 91 func 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. 144 func 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 }