src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

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 }