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