cmd/compile: move duplicate type-case checking into typecheck
Part of the general trend of moving yyerror calls out of walk and into
typecheck.
Notably, this requires splitting test/typeswitch2.go into two files,
because now some of the errors are reported during typecheck and
others are still reported during walk; and if there were any errors
during typecheck, then cmd/compile exits without invoking walk.
Passes toolstash-check.
Change-Id: I05ee0c00b99af659ee1eef098d342d0d736cf31e
Reviewed-on: https://go-review.googlesource.com/c/go/+/194659
Reviewed-by: Robert Griesemer <gri@golang.org>
diff --git a/src/cmd/compile/internal/gc/swt.go b/src/cmd/compile/internal/gc/swt.go
index 40c0ea1..5089efe 100644
--- a/src/cmd/compile/internal/gc/swt.go
+++ b/src/cmd/compile/internal/gc/swt.go
@@ -6,6 +6,7 @@
import (
"cmd/compile/internal/types"
+ "cmd/internal/src"
"sort"
)
@@ -80,6 +81,7 @@
}
var defCase, nilCase *Node
+ var ts typeSet
for _, ncase := range n.List.Slice() {
ls := ncase.List.Slice()
if len(ls) == 0 { // default:
@@ -120,6 +122,10 @@
" (missing %v method)", n.Left.Right, n1.Type, missing.Sym)
}
}
+
+ if n1.Op == OTYPE {
+ ts.add(ncase.Pos, n1.Type)
+ }
}
if ncase.Rlist.Len() != 0 {
@@ -151,6 +157,34 @@
}
}
+type typeSet struct {
+ m map[string][]typeSetEntry
+}
+
+type typeSetEntry struct {
+ pos src.XPos
+ typ *types.Type
+}
+
+func (s *typeSet) add(pos src.XPos, typ *types.Type) {
+ if s.m == nil {
+ s.m = make(map[string][]typeSetEntry)
+ }
+
+ // LongString does not uniquely identify types, so we need to
+ // disambiguate collisions with types.Identical.
+ // TODO(mdempsky): Add a method that *is* unique.
+ ls := typ.LongString()
+ prevs := s.m[ls]
+ for _, prev := range prevs {
+ if types.Identical(typ, prev.typ) {
+ yyerrorl(pos, "duplicate case %v in type switch\n\tprevious case at %s", typ, linestr(prev.pos))
+ return
+ }
+ }
+ s.m[ls] = append(prevs, typeSetEntry{pos, typ})
+}
+
func typecheckExprSwitch(n *Node) {
t := types.Types[TBOOL]
if n.Left != nil {
@@ -599,41 +633,9 @@
cc.defjmp = nod(OBREAK, nil, nil)
}
- // diagnose duplicate cases
- s.checkDupCases(cc.list)
return cc
}
-func (s *typeSwitch) checkDupCases(cc []caseClause) {
- if len(cc) < 2 {
- return
- }
- // We store seen types in a map keyed by type hash.
- // It is possible, but very unlikely, for multiple distinct types to have the same hash.
- seen := make(map[uint32][]*Node)
- // To avoid many small allocations of length 1 slices,
- // also set up a single large slice to slice into.
- nn := make([]*Node, 0, len(cc))
-Outer:
- for _, c := range cc {
- prev, ok := seen[c.hash]
- if !ok {
- // First entry for this hash.
- nn = append(nn, c.node)
- seen[c.hash] = nn[len(nn)-1 : len(nn) : len(nn)]
- continue
- }
- for _, n := range prev {
- if types.Identical(n.Left.Type, c.node.Left.Type) {
- yyerrorl(c.node.Pos, "duplicate case %v in type switch\n\tprevious case at %v", c.node.Left.Type, n.Line())
- // avoid double-reporting errors
- continue Outer
- }
- }
- seen[c.hash] = append(seen[c.hash], c.node)
- }
-}
-
// walk generates an AST that implements sw,
// where sw is a type switch.
// The AST is generally of the form of a linear
diff --git a/test/typeswitch2.go b/test/typeswitch2.go
index 5958b7d..62c96c8 100644
--- a/test/typeswitch2.go
+++ b/test/typeswitch2.go
@@ -35,13 +35,3 @@
}
return ""
}
-
-func notused(x interface{}) {
- // The first t is in a different scope than the 2nd t; it cannot
- // be accessed (=> declared and not used error); but it is legal
- // to declare it.
- switch t := 0; t := x.(type) { // ERROR "declared and not used"
- case int:
- _ = t // this is using the t of "t := x.(type)"
- }
-}
diff --git a/test/typeswitch2b.go b/test/typeswitch2b.go
new file mode 100644
index 0000000..135ae86
--- /dev/null
+++ b/test/typeswitch2b.go
@@ -0,0 +1,20 @@
+// errorcheck
+
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Verify that various erroneous type switches are caught by the compiler.
+// Does not compile.
+
+package main
+
+func notused(x interface{}) {
+ // The first t is in a different scope than the 2nd t; it cannot
+ // be accessed (=> declared and not used error); but it is legal
+ // to declare it.
+ switch t := 0; t := x.(type) { // ERROR "declared and not used"
+ case int:
+ _ = t // this is using the t of "t := x.(type)"
+ }
+}