honnef.co/go/tools@v0.5.0-0.dev.0.20240520180541-dcae280a5e87/staticcheck/sa9004/sa9004.go (about)

     1  package sa9004
     2  
     3  import (
     4  	"go/ast"
     5  	"go/token"
     6  	"go/types"
     7  
     8  	"honnef.co/go/tools/analysis/code"
     9  	"honnef.co/go/tools/analysis/edit"
    10  	"honnef.co/go/tools/analysis/lint"
    11  	"honnef.co/go/tools/analysis/report"
    12  	"honnef.co/go/tools/go/ast/astutil"
    13  
    14  	"golang.org/x/tools/go/analysis"
    15  	"golang.org/x/tools/go/analysis/passes/inspect"
    16  )
    17  
    18  var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
    19  	Analyzer: &analysis.Analyzer{
    20  		Name:     "SA9004",
    21  		Run:      run,
    22  		Requires: []*analysis.Analyzer{inspect.Analyzer},
    23  	},
    24  	Doc: &lint.Documentation{
    25  		Title: `Only the first constant has an explicit type`,
    26  
    27  		Text: `In a constant declaration such as the following:
    28  
    29      const (
    30          First byte = 1
    31          Second     = 2
    32      )
    33  
    34  the constant Second does not have the same type as the constant First.
    35  This construct shouldn't be confused with
    36  
    37      const (
    38          First byte = iota
    39          Second
    40      )
    41  
    42  where \'First\' and \'Second\' do indeed have the same type. The type is only
    43  passed on when no explicit value is assigned to the constant.
    44  
    45  When declaring enumerations with explicit values it is therefore
    46  important not to write
    47  
    48      const (
    49            EnumFirst EnumType = 1
    50            EnumSecond         = 2
    51            EnumThird          = 3
    52      )
    53  
    54  This discrepancy in types can cause various confusing behaviors and
    55  bugs.
    56  
    57  
    58  Wrong type in variable declarations
    59  
    60  The most obvious issue with such incorrect enumerations expresses
    61  itself as a compile error:
    62  
    63      package pkg
    64  
    65      const (
    66          EnumFirst  uint8 = 1
    67          EnumSecond       = 2
    68      )
    69  
    70      func fn(useFirst bool) {
    71          x := EnumSecond
    72          if useFirst {
    73              x = EnumFirst
    74          }
    75      }
    76  
    77  fails to compile with
    78  
    79      ./const.go:11:5: cannot use EnumFirst (type uint8) as type int in assignment
    80  
    81  
    82  Losing method sets
    83  
    84  A more subtle issue occurs with types that have methods and optional
    85  interfaces. Consider the following:
    86  
    87      package main
    88  
    89      import "fmt"
    90  
    91      type Enum int
    92  
    93      func (e Enum) String() string {
    94          return "an enum"
    95      }
    96  
    97      const (
    98          EnumFirst  Enum = 1
    99          EnumSecond      = 2
   100      )
   101  
   102      func main() {
   103          fmt.Println(EnumFirst)
   104          fmt.Println(EnumSecond)
   105      }
   106  
   107  This code will output
   108  
   109      an enum
   110      2
   111  
   112  as \'EnumSecond\' has no explicit type, and thus defaults to \'int\'.`,
   113  		Since:    "2019.1",
   114  		Severity: lint.SeverityWarning,
   115  		MergeIf:  lint.MergeIfAny,
   116  	},
   117  })
   118  
   119  var Analyzer = SCAnalyzer.Analyzer
   120  
   121  func run(pass *analysis.Pass) (interface{}, error) {
   122  	fn := func(node ast.Node) {
   123  		decl := node.(*ast.GenDecl)
   124  		if !decl.Lparen.IsValid() {
   125  			return
   126  		}
   127  		if decl.Tok != token.CONST {
   128  			return
   129  		}
   130  
   131  		groups := astutil.GroupSpecs(pass.Fset, decl.Specs)
   132  	groupLoop:
   133  		for _, group := range groups {
   134  			if len(group) < 2 {
   135  				continue
   136  			}
   137  			if group[0].(*ast.ValueSpec).Type == nil {
   138  				// first constant doesn't have a type
   139  				continue groupLoop
   140  			}
   141  
   142  			firstType := pass.TypesInfo.TypeOf(group[0].(*ast.ValueSpec).Values[0])
   143  			for i, spec := range group {
   144  				spec := spec.(*ast.ValueSpec)
   145  				if i > 0 && spec.Type != nil {
   146  					continue groupLoop
   147  				}
   148  				if len(spec.Names) != 1 || len(spec.Values) != 1 {
   149  					continue groupLoop
   150  				}
   151  
   152  				if !types.ConvertibleTo(pass.TypesInfo.TypeOf(spec.Values[0]), firstType) {
   153  					continue groupLoop
   154  				}
   155  
   156  				switch v := spec.Values[0].(type) {
   157  				case *ast.BasicLit:
   158  				case *ast.UnaryExpr:
   159  					if _, ok := v.X.(*ast.BasicLit); !ok {
   160  						continue groupLoop
   161  					}
   162  				default:
   163  					// if it's not a literal it might be typed, such as
   164  					// time.Microsecond = 1000 * Nanosecond
   165  					continue groupLoop
   166  				}
   167  			}
   168  			var edits []analysis.TextEdit
   169  			typ := group[0].(*ast.ValueSpec).Type
   170  			for _, spec := range group[1:] {
   171  				nspec := *spec.(*ast.ValueSpec)
   172  				nspec.Type = typ
   173  				// The position of `spec` node excludes comments (if any).
   174  				// However, on generating the source back from the node, the comments are included. Setting `Comment` to nil ensures deduplication of comments.
   175  				nspec.Comment = nil
   176  				edits = append(edits, edit.ReplaceWithNode(pass.Fset, spec, &nspec))
   177  			}
   178  			report.Report(pass, group[0], "only the first constant in this group has an explicit type", report.Fixes(edit.Fix("add type to all constants in group", edits...)))
   179  		}
   180  	}
   181  	code.Preorder(pass, fn, (*ast.GenDecl)(nil))
   182  	return nil, nil
   183  }