reflect: add Value.CanConvert
For #395
For #46746
Change-Id: I4bfc094cf1cecd27ce48e31f92384cf470f371a6
Reviewed-on: https://go-review.googlesource.com/c/go/+/334669
Trust: Ian Lance Taylor <iant@golang.org>
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
Reviewed-by: Joe Tsai <thebrokentoaster@gmail.com>
diff --git a/api/go1.17.txt b/api/go1.17.txt
index 3d0a464..4850538 100644
--- a/api/go1.17.txt
+++ b/api/go1.17.txt
@@ -80,6 +80,7 @@
pkg reflect, func VisibleFields(Type) []StructField
pkg reflect, method (Method) IsExported() bool
pkg reflect, method (StructField) IsExported() bool
+pkg reflect, method (Value) CanConvert(Type) bool
pkg runtime/cgo (darwin-amd64-cgo), func NewHandle(interface{}) Handle
pkg runtime/cgo (darwin-amd64-cgo), method (Handle) Delete()
pkg runtime/cgo (darwin-amd64-cgo), method (Handle) Value() interface{}
diff --git a/doc/go1.17.html b/doc/go1.17.html
index b31006f..7739d1c 100644
--- a/doc/go1.17.html
+++ b/doc/go1.17.html
@@ -989,6 +989,18 @@
<dl id="reflect"><dt><a href="/pkg/reflect/">reflect</a></dt>
<dd>
+ <p><!-- CL 334669 -->
+ The new
+ <a href="/pkg/reflect/#Value.CanConvert"><code>Value.CanConvert</code></a>
+ method reports whether a value can be converted to a type.
+ This may be used to avoid a panic when converting a slice to an
+ array pointer type if the slice is too short.
+ Previously it was sufficient to use
+ <a href="/pkg/reflect/#Type.ConvertibleTo"><code>Type.ConvertibleTo</code></a>
+ for this, but the newly permitted conversion from slice to array
+ pointer type can panic even if the types are convertible.
+ </p>
+
<p><!-- CL 266197 -->
The new
<a href="/pkg/reflect/#StructField.IsExported"><code>StructField.IsExported</code></a>
diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go
index 0db5e13..eac27e8 100644
--- a/src/reflect/all_test.go
+++ b/src/reflect/all_test.go
@@ -4304,6 +4304,9 @@
// vout1 represents the in value converted to the in type.
v1 := tt.in
+ if !v1.CanConvert(t1) {
+ t.Errorf("ValueOf(%T(%[1]v)).CanConvert(%s) = false, want true", tt.in.Interface(), t1)
+ }
vout1 := v1.Convert(t1)
out1 := vout1.Interface()
if vout1.Type() != tt.in.Type() || !DeepEqual(out1, tt.in.Interface()) {
@@ -4311,6 +4314,9 @@
}
// vout2 represents the in value converted to the out type.
+ if !v1.CanConvert(t2) {
+ t.Errorf("ValueOf(%T(%[1]v)).CanConvert(%s) = false, want true", tt.in.Interface(), t2)
+ }
vout2 := v1.Convert(t2)
out2 := vout2.Interface()
if vout2.Type() != tt.out.Type() || !DeepEqual(out2, tt.out.Interface()) {
@@ -4371,6 +4377,9 @@
if !v.Type().ConvertibleTo(pt) {
t.Errorf("[]byte should be convertible to *[8]byte")
}
+ if v.CanConvert(pt) {
+ t.Errorf("slice with length 4 should not be convertible to *[8]byte")
+ }
shouldPanic("reflect: cannot convert slice with length 4 to pointer to array with length 8", func() {
_ = v.Convert(pt)
})
diff --git a/src/reflect/value.go b/src/reflect/value.go
index 9dce251..6f878eb 100644
--- a/src/reflect/value.go
+++ b/src/reflect/value.go
@@ -2811,6 +2811,26 @@
return op(v, t)
}
+// CanConvert reports whether the value v can be converted to type t.
+// If v.CanConvert(t) returns true then v.Convert(t) will not panic.
+func (v Value) CanConvert(t Type) bool {
+ vt := v.Type()
+ if !vt.ConvertibleTo(t) {
+ return false
+ }
+ // Currently the only conversion that is OK in terms of type
+ // but that can panic depending on the value is converting
+ // from slice to pointer-to-array.
+ if vt.Kind() == Slice && t.Kind() == Ptr && t.Elem().Kind() == Array {
+ n := t.Elem().Len()
+ h := (*unsafeheader.Slice)(v.ptr)
+ if n > h.Len {
+ return false
+ }
+ }
+ return true
+}
+
// convertOp returns the function to convert a value of type src
// to a value of type dst. If the conversion is illegal, convertOp returns nil.
func convertOp(dst, src *rtype) func(Value, Type) Value {