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