Skip to content

Commit c302785

Browse files
committed
cmd/compile: fix "previous" position info for duplicate switch cases
Because the Node AST represents references to declared objects (e.g., variables, packages, types, constants) by directly pointing to the referred object, we don't have use-position info for these objects. For switch statements with duplicate cases, we report back where the first duplicate value appeared. However, due to the AST representation, if the value was a declared constant, we mistakenly reported the constant declaration position as the previous case position. This CL reports back against the 'case' keyword's position instead, if there's no more precise information available to us. It also refactors code to emit the same "previous at" error message for duplicate values in map literals. Thanks to Emmanuel Odeke for the test case. Fixes #33460. Change-Id: Iec69542ccd4aad594dde8df02d1b880a422c5622 Reviewed-on: https://go-review.googlesource.com/c/go/+/188901 Reviewed-by: Robert Griesemer <[email protected]> Reviewed-by: Emmanuel Odeke <[email protected]> Run-TryBot: Emmanuel Odeke <[email protected]> TryBot-Result: Gobot Gobot <[email protected]>
1 parent c1df518 commit c302785

File tree

5 files changed

+102
-49
lines changed

5 files changed

+102
-49
lines changed

src/cmd/compile/internal/gc/const.go

+36-12
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ package gc
66

77
import (
88
"cmd/compile/internal/types"
9+
"cmd/internal/src"
10+
"fmt"
911
"math/big"
1012
"strings"
1113
)
@@ -1397,28 +1399,30 @@ func hascallchan(n *Node) bool {
13971399

13981400
// A constSet represents a set of Go constant expressions.
13991401
type constSet struct {
1400-
m map[constSetKey]*Node
1402+
m map[constSetKey]src.XPos
14011403
}
14021404

14031405
type constSetKey struct {
14041406
typ *types.Type
14051407
val interface{}
14061408
}
14071409

1408-
// add adds constant expressions to s. If a constant expression of
1409-
// equal value and identical type has already been added, then that
1410-
// type expression is returned. Otherwise, add returns nil.
1410+
// add adds constant expression n to s. If a constant expression of
1411+
// equal value and identical type has already been added, then add
1412+
// reports an error about the duplicate value.
14111413
//
1412-
// add also returns nil if n is not a Go constant expression.
1414+
// pos provides position information for where expression n occured
1415+
// (in case n does not have its own position information). what and
1416+
// where are used in the error message.
14131417
//
14141418
// n must not be an untyped constant.
1415-
func (s *constSet) add(n *Node) *Node {
1419+
func (s *constSet) add(pos src.XPos, n *Node, what, where string) {
14161420
if n.Op == OCONVIFACE && n.Implicit() {
14171421
n = n.Left
14181422
}
14191423

14201424
if !n.isGoConst() {
1421-
return nil
1425+
return
14221426
}
14231427
if n.Type.IsUntyped() {
14241428
Fatalf("%v is untyped", n)
@@ -1448,12 +1452,32 @@ func (s *constSet) add(n *Node) *Node {
14481452
}
14491453
k := constSetKey{typ, n.Val().Interface()}
14501454

1455+
if hasUniquePos(n) {
1456+
pos = n.Pos
1457+
}
1458+
14511459
if s.m == nil {
1452-
s.m = make(map[constSetKey]*Node)
1460+
s.m = make(map[constSetKey]src.XPos)
14531461
}
1454-
old, dup := s.m[k]
1455-
if !dup {
1456-
s.m[k] = n
1462+
1463+
if prevPos, isDup := s.m[k]; isDup {
1464+
yyerrorl(pos, "duplicate %s %s in %s\n\tprevious %s at %v",
1465+
what, nodeAndVal(n), where,
1466+
what, linestr(prevPos))
1467+
} else {
1468+
s.m[k] = pos
14571469
}
1458-
return old
1470+
}
1471+
1472+
// nodeAndVal reports both an expression and its constant value, if
1473+
// the latter is non-obvious.
1474+
//
1475+
// TODO(mdempsky): This could probably be a fmt.go flag.
1476+
func nodeAndVal(n *Node) string {
1477+
show := n.String()
1478+
val := n.Val().Interface()
1479+
if s := fmt.Sprintf("%#v", val); show != s {
1480+
show += " (value " + s + ")"
1481+
}
1482+
return show
14591483
}

src/cmd/compile/internal/gc/subr.go

+27-20
Original file line numberDiff line numberDiff line change
@@ -194,30 +194,37 @@ func Fatalf(fmt_ string, args ...interface{}) {
194194
errorexit()
195195
}
196196

197-
func setlineno(n *Node) src.XPos {
198-
lno := lineno
199-
if n != nil {
200-
switch n.Op {
201-
case ONAME, OPACK:
202-
break
203-
204-
case OLITERAL, OTYPE:
205-
if n.Sym != nil {
206-
break
207-
}
208-
fallthrough
197+
// hasUniquePos reports whether n has a unique position that can be
198+
// used for reporting error messages.
199+
//
200+
// It's primarily used to distinguish references to named objects,
201+
// whose Pos will point back to their declaration position rather than
202+
// their usage position.
203+
func hasUniquePos(n *Node) bool {
204+
switch n.Op {
205+
case ONAME, OPACK:
206+
return false
207+
case OLITERAL, OTYPE:
208+
if n.Sym != nil {
209+
return false
210+
}
211+
}
209212

210-
default:
211-
lineno = n.Pos
212-
if !lineno.IsKnown() {
213-
if Debug['K'] != 0 {
214-
Warn("setlineno: unknown position (line 0)")
215-
}
216-
lineno = lno
217-
}
213+
if !n.Pos.IsKnown() {
214+
if Debug['K'] != 0 {
215+
Warn("setlineno: unknown position (line 0)")
218216
}
217+
return false
219218
}
220219

220+
return true
221+
}
222+
223+
func setlineno(n *Node) src.XPos {
224+
lno := lineno
225+
if n != nil && hasUniquePos(n) {
226+
lineno = n.Pos
227+
}
221228
return lno
222229
}
223230

src/cmd/compile/internal/gc/swt.go

+1-14
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ package gc
66

77
import (
88
"cmd/compile/internal/types"
9-
"fmt"
109
"sort"
1110
)
1211

@@ -641,23 +640,11 @@ func checkDupExprCases(exprname *Node, clauses []*Node) {
641640
continue
642641
}
643642

644-
if prev := cs.add(n); prev != nil {
645-
yyerrorl(ncase.Pos, "duplicate case %s in switch\n\tprevious case at %v",
646-
nodeAndVal(n), prev.Line())
647-
}
643+
cs.add(ncase.Pos, n, "case", "switch")
648644
}
649645
}
650646
}
651647

652-
func nodeAndVal(n *Node) string {
653-
show := n.String()
654-
val := n.Val().Interface()
655-
if s := fmt.Sprintf("%#v", val); show != s {
656-
show += " (value " + s + ")"
657-
}
658-
return show
659-
}
660-
661648
// walk generates an AST that implements sw,
662649
// where sw is a type switch.
663650
// The AST is generally of the form of a linear

src/cmd/compile/internal/gc/typecheck.go

+1-3
Original file line numberDiff line numberDiff line change
@@ -2911,9 +2911,7 @@ func typecheckcomplit(n *Node) (res *Node) {
29112911
r = typecheck(r, ctxExpr)
29122912
r = defaultlit(r, t.Key())
29132913
l.Left = assignconv(r, t.Key(), "map key")
2914-
if cs.add(l.Left) != nil {
2915-
yyerror("duplicate key %v in map literal", l.Left)
2916-
}
2914+
cs.add(lineno, l.Left, "key", "map literal")
29172915

29182916
r = l.Right
29192917
pushtype(r, t.Elem())

test/fixedbugs/issue33460.go

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// errorcheck
2+
3+
// Copyright 2019 The Go Authors. All rights reserved.
4+
// Use of this source code is governed by a BSD-style
5+
// license that can be found in the LICENSE file.
6+
7+
package p
8+
9+
const (
10+
zero = iota
11+
one
12+
two
13+
three
14+
)
15+
16+
const iii int = 0x3
17+
18+
func f(v int) {
19+
switch v {
20+
case zero, one:
21+
case two, one: // ERROR "previous case at LINE-1"
22+
23+
case three:
24+
case 3: // ERROR "previous case at LINE-1"
25+
case iii: // ERROR "previous case at LINE-2"
26+
}
27+
}
28+
29+
const b = "b"
30+
31+
var _ = map[string]int{
32+
"a": 0,
33+
b: 1,
34+
"a": 2, // ERROR "previous key at LINE-2"
35+
"b": 3, // ERROR "previous key at LINE-2"
36+
"b": 4, // ERROR "previous key at LINE-3"
37+
}

0 commit comments

Comments
 (0)