]> Cypherpunks repositories - gostls13.git/commitdiff
go/types, types2: detect dupl. map keys in comp. literals with generic key type
authorRobert Griesemer <gri@golang.org>
Tue, 17 May 2022 00:43:38 +0000 (17:43 -0700)
committerRobert Griesemer <gri@golang.org>
Mon, 23 May 2022 22:10:24 +0000 (22:10 +0000)
For map composite literals where the key type is a suitably constrained
type parameter, the existing key duplicate detection mechanism doesn't
work when the keys are numeric values of different types but equal value.
For instance, given

        func _[P int64|float64]() {
                _ = map[P]string{0: "foo", 0.0: "bar"}
        }

the key values 0 and 0.0 have the same numeric value 0 but currently
are treated as different values int64(0) and float64(0.0). For any
valid instantiation of P, the keys will collide.

This CL changes the keyVal function to map numeric types to the
"smallest" numeric type in which a value can be represented. For
instance, float64(0.0) is mapped to int64(0). This ensures that
numerically equal values are always represented the same way so
that they can be detected as duplicates.

Fixes #51610.

Change-Id: I3eb71142bbe6b13453282a7f71ee48950e58ecbd
Reviewed-on: https://go-review.googlesource.com/c/go/+/406555
Reviewed-by: Ian Lance Taylor <iant@google.com>
Reviewed-by: Robert Griesemer <gri@google.com>
src/cmd/compile/internal/types2/expr.go
src/cmd/compile/internal/types2/testdata/fixedbugs/issue51610.go [new file with mode: 0644]
src/go/types/expr.go
src/go/types/testdata/fixedbugs/issue51610.go [new file with mode: 0644]

index 33d329f82d894c5f2ccba71d2eddcf76c1ff6efb..b11cd1e9d8dd367423d95ba3a359c4913e81eeca 100644 (file)
@@ -1741,16 +1741,33 @@ Error:
        return statement // avoid follow-up errors
 }
 
+// keyVal maps a complex, float, integer, string or boolean constant value
+// to the corresponding complex128, float64, int64, uint64, string, or bool
+// Go value if possible; otherwise it returns x.
+// A complex constant that can be represented as a float (such as 1.2 + 0i)
+// is returned as a floating point value; if a floating point value can be
+// represented as an integer (such as 1.0) it is returned as an integer value.
+// This ensures that constants of different kind but equal value (such as
+// 1.0 + 0i, 1.0, 1) result in the same value.
 func keyVal(x constant.Value) interface{} {
-       // TODO(gri) This function must map 1, 1.0, and 1.0 + 0i to the same (integer) value.
-       //           Same for 1.1 and 1.1 + 0i.
-       //           Otherwise we won't get duplicate key errors for certain type parameter
-       //           key types. See issue #51610.
        switch x.Kind() {
-       case constant.Bool:
-               return constant.BoolVal(x)
-       case constant.String:
-               return constant.StringVal(x)
+       case constant.Complex:
+               f := constant.ToFloat(x)
+               if f.Kind() != constant.Float {
+                       r, _ := constant.Float64Val(constant.Real(x))
+                       i, _ := constant.Float64Val(constant.Imag(x))
+                       return complex(r, i)
+               }
+               x = f
+               fallthrough
+       case constant.Float:
+               i := constant.ToInt(x)
+               if i.Kind() != constant.Int {
+                       v, _ := constant.Float64Val(x)
+                       return v
+               }
+               x = i
+               fallthrough
        case constant.Int:
                if v, ok := constant.Int64Val(x); ok {
                        return v
@@ -1758,13 +1775,10 @@ func keyVal(x constant.Value) interface{} {
                if v, ok := constant.Uint64Val(x); ok {
                        return v
                }
-       case constant.Float:
-               v, _ := constant.Float64Val(x)
-               return v
-       case constant.Complex:
-               r, _ := constant.Float64Val(constant.Real(x))
-               i, _ := constant.Float64Val(constant.Imag(x))
-               return complex(r, i)
+       case constant.String:
+               return constant.StringVal(x)
+       case constant.Bool:
+               return constant.BoolVal(x)
        }
        return x
 }
diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51610.go b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51610.go
new file mode 100644 (file)
index 0000000..d10c788
--- /dev/null
@@ -0,0 +1,9 @@
+// Copyright 2022 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.
+
+package p
+
+func _[P int | float64 | complex128]() {
+       _ = map[P]int{1: 1, 1.0 /* ERROR duplicate key 1 */ : 2, 1 /* ERROR duplicate key \(1 \+ 0i\) */ + 0i: 3}
+}
index 14ca0c20d80cbf9dc08ba8ba55fa134093697d9b..0e8dca32478ead450e4b8df5ab6feefe098d5c5d 100644 (file)
@@ -1663,16 +1663,33 @@ Error:
        return statement // avoid follow-up errors
 }
 
-func keyVal(x constant.Value) any {
-       // TODO(gri) This function must map 1, 1.0, and 1.0 + 0i to the same (integer) value.
-       //           Same for 1.1 and 1.1 + 0i.
-       //           Otherwise we won't get duplicate key errors for certain type parameter
-       //           key types. See issue #51610.
+// keyVal maps a complex, float, integer, string or boolean constant value
+// to the corresponding complex128, float64, int64, uint64, string, or bool
+// Go value if possible; otherwise it returns x.
+// A complex constant that can be represented as a float (such as 1.2 + 0i)
+// is returned as a floating point value; if a floating point value can be
+// represented as an integer (such as 1.0) it is returned as an integer value.
+// This ensures that constants of different kind but equal value (such as
+// 1.0 + 0i, 1.0, 1) result in the same value.
+func keyVal(x constant.Value) interface{} {
        switch x.Kind() {
-       case constant.Bool:
-               return constant.BoolVal(x)
-       case constant.String:
-               return constant.StringVal(x)
+       case constant.Complex:
+               f := constant.ToFloat(x)
+               if f.Kind() != constant.Float {
+                       r, _ := constant.Float64Val(constant.Real(x))
+                       i, _ := constant.Float64Val(constant.Imag(x))
+                       return complex(r, i)
+               }
+               x = f
+               fallthrough
+       case constant.Float:
+               i := constant.ToInt(x)
+               if i.Kind() != constant.Int {
+                       v, _ := constant.Float64Val(x)
+                       return v
+               }
+               x = i
+               fallthrough
        case constant.Int:
                if v, ok := constant.Int64Val(x); ok {
                        return v
@@ -1680,13 +1697,10 @@ func keyVal(x constant.Value) any {
                if v, ok := constant.Uint64Val(x); ok {
                        return v
                }
-       case constant.Float:
-               v, _ := constant.Float64Val(x)
-               return v
-       case constant.Complex:
-               r, _ := constant.Float64Val(constant.Real(x))
-               i, _ := constant.Float64Val(constant.Imag(x))
-               return complex(r, i)
+       case constant.String:
+               return constant.StringVal(x)
+       case constant.Bool:
+               return constant.BoolVal(x)
        }
        return x
 }
diff --git a/src/go/types/testdata/fixedbugs/issue51610.go b/src/go/types/testdata/fixedbugs/issue51610.go
new file mode 100644 (file)
index 0000000..d10c788
--- /dev/null
@@ -0,0 +1,9 @@
+// Copyright 2022 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.
+
+package p
+
+func _[P int | float64 | complex128]() {
+       _ = map[P]int{1: 1, 1.0 /* ERROR duplicate key 1 */ : 2, 1 /* ERROR duplicate key \(1 \+ 0i\) */ + 0i: 3}
+}