Repository: goplus/gop
Pull request #923 (2021-11-22 14:00:02)

switch case duplicate check


Patch:
diff --git a/cl/stmt.go b/cl/stmt.go
index 6c61904e..6173bd5e 100644
--- a/cl/stmt.go
+++ b/cl/stmt.go
@@ -18,6 +18,7 @@ package cl
 
 import (
 	"fmt"
+	"go/constant"
 	"log"
 	"path/filepath"
 	"reflect"
@@ -425,6 +426,8 @@ func compileSwitchStmt(ctx *blockCtx, v *ast.SwitchStmt) {
 		cb.None() // switch {...}
 	}
 	cb.Then()
+	seen := make(valueMap)
+	var firstDefault ast.Stmt
 	for _, stmt := range v.Body.List {
 		c, ok := stmt.(*ast.CaseClause)
 		if !ok {
@@ -432,6 +435,32 @@ func compileSwitchStmt(ctx *blockCtx, v *ast.SwitchStmt) {
 		}
 		for _, citem := range c.List {
 			compileExpr(ctx, citem)
+			v := cb.Get(-1)
+			if val := goVal(v.CVal); val != nil {
+				// look for duplicate types for a given value
+				// (quadratic algorithm, but these lists tend to be very short)
+				typ := types.Default(v.Type)
+				for _, vt := range seen[val] {
+					if types.Identical(typ, vt.typ) {
+						src, pos := ctx.LoadExpr(v.Src)
+						if _, ok := v.Src.(*ast.BasicLit); ok {
+							ctx.handleCodeErrorf(&pos, "duplicate case %s in switch\n\tprevious case at %v",
+								src, ctx.Position(vt.pos))
+						} else {
+							ctx.handleCodeErrorf(&pos, "duplicate case %s (value %#v) in switch\n\tprevious case at %v",
+								src, val, ctx.Position(vt.pos))
+						}
+					}
+				}
+				seen[val] = append(seen[val], valueType{v.Src.Pos(), typ})
+			}
+		}
+		if c.List == nil {
+			if firstDefault != nil {
+				pos := ctx.Position(c.Pos())
+				ctx.handleCodeErrorf(&pos, "multiple defaults in switch (first at %v)", ctx.Position(firstDefault.Pos()))
+			}
+			firstDefault = c
 		}
 		cb.Case(len(c.List)) // Case(0) means default case
 		body, has := hasFallthrough(c.Body)
@@ -573,4 +602,40 @@ func compileType(ctx *blockCtx, t *ast.TypeSpec) {
 	}
 }
 
+type (
+	valueMap  map[interface{}][]valueType // underlying Go value -> valueType
+	valueType struct {
+		pos token.Pos
+		typ types.Type
+	}
+)
+
+// goVal returns the Go value for val, or nil.
+func goVal(val constant.Value) interface{} {
+	// val should exist, but be conservative and check
+	if val == nil {
+		return nil
+	}
+	// Match implementation restriction of other compilers.
+	// gc only checks duplicates for integer, floating-point
+	// and string values, so only create Go values for these
+	// types.
+	switch val.Kind() {
+	case constant.Int:
+		if x, ok := constant.Int64Val(val); ok {
+			return x
+		}
+		if x, ok := constant.Uint64Val(val); ok {
+			return x
+		}
+	case constant.Float:
+		if x, ok := constant.Float64Val(val); ok {
+			return x
+		}
+	case constant.String:
+		return constant.StringVal(val)
+	}
+	return nil
+}
+
 // -----------------------------------------------------------------------------


Test patch:
diff --git a/cl/error_msg_test.go b/cl/error_msg_test.go
index 95cc9310..bf0de0db 100644
--- a/cl/error_msg_test.go
+++ b/cl/error_msg_test.go
@@ -627,3 +627,65 @@ type Point struct {
 }
 `)
 }
+
+func TestErrSwitchDuplicate(t *testing.T) {
+	codeErrorTest(t,
+		"./bar.gop:4:7: duplicate case 100 in switch\n\tprevious case at ./bar.gop:3:7",
+		`var n int
+switch n {
+	case 100:
+	case 100:
+}`)
+	codeErrorTest(t,
+		"./bar.gop:4:7: duplicate case int(100) (value 100) in switch\n\tprevious case at ./bar.gop:3:7",
+		`var n int
+switch n {
+	case 100:
+	case int(100):
+}`)
+	codeErrorTest(t,
+		"./bar.gop:4:7: duplicate case 50 + 50 (value 100) in switch\n\tprevious case at ./bar.gop:3:7",
+		`var n int
+switch n {
+	case 100:
+	case 50 + 50:
+}`)
+	codeErrorTest(t,
+		"./bar.gop:5:7: duplicate case int(100) (value 100) in switch\n\tprevious case at ./bar.gop:3:7",
+		`var n interface{}
+switch n {
+	case 100:
+	case uint(100):
+	case int(100):
+}`)
+	codeErrorTest(t,
+		"./bar.gop:4:7: duplicate case 100.0 in switch\n\tprevious case at ./bar.gop:3:7",
+		`var n interface{}
+switch n {
+	case 100.0:
+	case 100.0:
+}`)
+	codeErrorTest(t,
+		"./bar.gop:5:7: duplicate case v (value 100) in switch\n\tprevious case at ./bar.gop:4:7",
+		`var n interface{}
+const v = 100.0
+switch n {
+	case 100.0:
+	case v:
+}`)
+	codeErrorTest(t,
+		"./bar.gop:5:7: duplicate case v (value \"hello\") in switch\n\tprevious case at ./bar.gop:4:7",
+		`var n interface{}
+const v = "hello"
+switch n {
+	case "hello":
+	case v:
+}`)
+	codeErrorTest(t,
+		"./bar.gop:4:2: multiple defaults in switch (first at ./bar.gop:3:2)",
+		`var n interface{}
+switch n {
+	default:
+	default:
+}`)
+}