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) (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 }
184