s1016.go (4590B)
1 package s1016 2 3 import ( 4 "fmt" 5 "go/ast" 6 "go/token" 7 "go/types" 8 "go/version" 9 10 "honnef.co/go/tools/analysis/code" 11 "honnef.co/go/tools/analysis/edit" 12 "honnef.co/go/tools/analysis/facts/generated" 13 "honnef.co/go/tools/analysis/lint" 14 "honnef.co/go/tools/analysis/report" 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 ) 20 21 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ 22 Analyzer: &analysis.Analyzer{ 23 Name: "S1016", 24 Run: run, 25 Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer}, 26 }, 27 Doc: &lint.RawDocumentation{ 28 Title: `Use a type conversion instead of manually copying struct fields`, 29 Text: `Two struct types with identical fields can be converted between each 30 other. In older versions of Go, the fields had to have identical 31 struct tags. Since Go 1.8, however, struct tags are ignored during 32 conversions. It is thus not necessary to manually copy every field 33 individually.`, 34 Before: ` 35 var x T1 36 y := T2{ 37 Field1: x.Field1, 38 Field2: x.Field2, 39 }`, 40 After: ` 41 var x T1 42 y := T2(x)`, 43 Since: "2017.1", 44 MergeIf: lint.MergeIfAll, 45 }, 46 }) 47 48 var Analyzer = SCAnalyzer.Analyzer 49 50 func run(pass *analysis.Pass) (any, error) { 51 // TODO(dh): support conversions between type parameters 52 fn := func(c inspector.Cursor) { 53 node := c.Node() 54 if unary, ok := c.Parent().Node().(*ast.UnaryExpr); ok && unary.Op == token.AND { 55 // Do not suggest type conversion between pointers 56 return 57 } 58 59 lit := node.(*ast.CompositeLit) 60 var typ1 types.Type 61 var named1 *types.Named 62 switch typ := pass.TypesInfo.TypeOf(lit.Type).(type) { 63 case *types.Named: 64 typ1 = typ 65 named1 = typ 66 case *types.Alias: 67 ua := types.Unalias(typ) 68 if n, ok := ua.(*types.Named); ok { 69 typ1 = typ 70 named1 = n 71 } 72 } 73 if typ1 == nil { 74 return 75 } 76 s1, ok := typ1.Underlying().(*types.Struct) 77 if !ok { 78 return 79 } 80 81 var typ2 types.Type 82 var named2 *types.Named 83 var ident *ast.Ident 84 getSelType := func(expr ast.Expr) (types.Type, *ast.Ident, bool) { 85 sel, ok := expr.(*ast.SelectorExpr) 86 if !ok { 87 return nil, nil, false 88 } 89 ident, ok := sel.X.(*ast.Ident) 90 if !ok { 91 return nil, nil, false 92 } 93 typ := pass.TypesInfo.TypeOf(sel.X) 94 return typ, ident, typ != nil 95 } 96 if len(lit.Elts) == 0 { 97 return 98 } 99 if s1.NumFields() != len(lit.Elts) { 100 return 101 } 102 for i, elt := range lit.Elts { 103 var t types.Type 104 var id *ast.Ident 105 var ok bool 106 switch elt := elt.(type) { 107 case *ast.SelectorExpr: 108 t, id, ok = getSelType(elt) 109 if !ok { 110 return 111 } 112 if i >= s1.NumFields() || s1.Field(i).Name() != elt.Sel.Name { 113 return 114 } 115 case *ast.KeyValueExpr: 116 var sel *ast.SelectorExpr 117 sel, ok = elt.Value.(*ast.SelectorExpr) 118 if !ok { 119 return 120 } 121 122 if elt.Key.(*ast.Ident).Name != sel.Sel.Name { 123 return 124 } 125 t, id, ok = getSelType(elt.Value) 126 } 127 if !ok { 128 return 129 } 130 // All fields must be initialized from the same object 131 if ident != nil && pass.TypesInfo.ObjectOf(ident) != pass.TypesInfo.ObjectOf(id) { 132 return 133 } 134 switch t := t.(type) { 135 case *types.Named: 136 typ2 = t 137 named2 = t 138 case *types.Alias: 139 if n, ok := types.Unalias(t).(*types.Named); ok { 140 typ2 = t 141 named2 = n 142 } 143 } 144 if typ2 == nil { 145 return 146 } 147 ident = id 148 } 149 150 if typ2 == nil { 151 return 152 } 153 154 if named1.Obj().Pkg() != named2.Obj().Pkg() { 155 // Do not suggest type conversions between different 156 // packages. Types in different packages might only match 157 // by coincidence. Furthermore, if the dependency ever 158 // adds more fields to its type, it could break the code 159 // that relies on the type conversion to work. 160 return 161 } 162 163 s2, ok := typ2.Underlying().(*types.Struct) 164 if !ok { 165 return 166 } 167 if typ1 == typ2 { 168 return 169 } 170 if version.Compare(code.LanguageVersion(pass, node), "go1.8") >= 0 { 171 if !types.IdenticalIgnoreTags(s1, s2) { 172 return 173 } 174 } else { 175 if !types.Identical(s1, s2) { 176 return 177 } 178 } 179 180 r := &ast.CallExpr{ 181 Fun: lit.Type, 182 Args: []ast.Expr{ident}, 183 } 184 report.Report(pass, node, 185 fmt.Sprintf("should convert %s (type %s) to %s instead of using struct literal", ident.Name, types.TypeString(typ2, types.RelativeTo(pass.Pkg)), types.TypeString(typ1, types.RelativeTo(pass.Pkg))), 186 report.FilterGenerated(), 187 report.Fixes(edit.Fix("Use type conversion", edit.ReplaceWithNode(pass.Fset, node, r)))) 188 } 189 for c := range code.Cursor(pass).Preorder((*ast.CompositeLit)(nil)) { 190 fn(c) 191 } 192 return nil, nil 193 }