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 }