package s1016 import ( "fmt" "go/ast" "go/token" "go/types" "go/version" "honnef.co/go/tools/analysis/code" "honnef.co/go/tools/analysis/edit" "honnef.co/go/tools/analysis/facts/generated" "honnef.co/go/tools/analysis/lint" "honnef.co/go/tools/analysis/report" "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/passes/inspect" ) var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ Analyzer: &analysis.Analyzer{ Name: "S1016", Run: run, Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer}, }, Doc: &lint.RawDocumentation{ Title: `Use a type conversion instead of manually copying struct fields`, Text: `Two struct types with identical fields can be converted between each other. In older versions of Go, the fields had to have identical struct tags. Since Go 1.8, however, struct tags are ignored during conversions. It is thus not necessary to manually copy every field individually.`, Before: ` var x T1 y := T2{ Field1: x.Field1, Field2: x.Field2, }`, After: ` var x T1 y := T2(x)`, Since: "2017.1", MergeIf: lint.MergeIfAll, }, }) var Analyzer = SCAnalyzer.Analyzer func run(pass *analysis.Pass) (interface{}, error) { // TODO(dh): support conversions between type parameters fn := func(node ast.Node, stack []ast.Node) { if unary, ok := stack[len(stack)-2].(*ast.UnaryExpr); ok && unary.Op == token.AND { // Do not suggest type conversion between pointers return } lit := node.(*ast.CompositeLit) var typ1 types.Type var named1 *types.Named switch typ := pass.TypesInfo.TypeOf(lit.Type).(type) { case *types.Named: typ1 = typ named1 = typ case *types.Alias: ua := types.Unalias(typ) if n, ok := ua.(*types.Named); ok { typ1 = typ named1 = n } } if typ1 == nil { return } s1, ok := typ1.Underlying().(*types.Struct) if !ok { return } var typ2 types.Type var named2 *types.Named var ident *ast.Ident getSelType := func(expr ast.Expr) (types.Type, *ast.Ident, bool) { sel, ok := expr.(*ast.SelectorExpr) if !ok { return nil, nil, false } ident, ok := sel.X.(*ast.Ident) if !ok { return nil, nil, false } typ := pass.TypesInfo.TypeOf(sel.X) return typ, ident, typ != nil } if len(lit.Elts) == 0 { return } if s1.NumFields() != len(lit.Elts) { return } for i, elt := range lit.Elts { var t types.Type var id *ast.Ident var ok bool switch elt := elt.(type) { case *ast.SelectorExpr: t, id, ok = getSelType(elt) if !ok { return } if i >= s1.NumFields() || s1.Field(i).Name() != elt.Sel.Name { return } case *ast.KeyValueExpr: var sel *ast.SelectorExpr sel, ok = elt.Value.(*ast.SelectorExpr) if !ok { return } if elt.Key.(*ast.Ident).Name != sel.Sel.Name { return } t, id, ok = getSelType(elt.Value) } if !ok { return } // All fields must be initialized from the same object if ident != nil && pass.TypesInfo.ObjectOf(ident) != pass.TypesInfo.ObjectOf(id) { return } switch t := t.(type) { case *types.Named: typ2 = t named2 = t case *types.Alias: if n, ok := types.Unalias(t).(*types.Named); ok { typ2 = t named2 = n } } if typ2 == nil { return } ident = id } if typ2 == nil { return } if named1.Obj().Pkg() != named2.Obj().Pkg() { // Do not suggest type conversions between different // packages. Types in different packages might only match // by coincidence. Furthermore, if the dependency ever // adds more fields to its type, it could break the code // that relies on the type conversion to work. return } s2, ok := typ2.Underlying().(*types.Struct) if !ok { return } if typ1 == typ2 { return } if version.Compare(code.LanguageVersion(pass, node), "go1.8") >= 0 { if !types.IdenticalIgnoreTags(s1, s2) { return } } else { if !types.Identical(s1, s2) { return } } r := &ast.CallExpr{ Fun: lit.Type, Args: []ast.Expr{ident}, } report.Report(pass, node, 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))), report.FilterGenerated(), report.Fixes(edit.Fix("use type conversion", edit.ReplaceWithNode(pass.Fset, node, r)))) } code.PreorderStack(pass, fn, (*ast.CompositeLit)(nil)) return nil, nil }