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  }