src

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

sa9004.go (4323B)


      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.RawDocumentation{
     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) (any, 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],
    179 				"only the first constant in this group has an explicit type",
    180 				report.Fixes(edit.Fix("Add type to all constants in group", edits...)))
    181 		}
    182 	}
    183 	code.Preorder(pass, fn, (*ast.GenDecl)(nil))
    184 	return nil, nil
    185 }