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