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>
This commit is contained in:
Ian Lance Taylor 2021-07-14 14:46:09 -07:00
parent 9e26569293
commit 48c88f1b1b
4 changed files with 42 additions and 0 deletions

View File

@ -80,6 +80,7 @@ pkg net/url, method (Values) Has(string) bool
pkg reflect, func VisibleFields(Type) []StructField pkg reflect, func VisibleFields(Type) []StructField
pkg reflect, method (Method) IsExported() bool pkg reflect, method (Method) IsExported() bool
pkg reflect, method (StructField) 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), func NewHandle(interface{}) Handle
pkg runtime/cgo (darwin-amd64-cgo), method (Handle) Delete() pkg runtime/cgo (darwin-amd64-cgo), method (Handle) Delete()
pkg runtime/cgo (darwin-amd64-cgo), method (Handle) Value() interface{} pkg runtime/cgo (darwin-amd64-cgo), method (Handle) Value() interface{}

View File

@ -989,6 +989,18 @@ func Foo() bool {
<dl id="reflect"><dt><a href="/pkg/reflect/">reflect</a></dt> <dl id="reflect"><dt><a href="/pkg/reflect/">reflect</a></dt>
<dd> <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 --> <p><!-- CL 266197 -->
The new The new
<a href="/pkg/reflect/#StructField.IsExported"><code>StructField.IsExported</code></a> <a href="/pkg/reflect/#StructField.IsExported"><code>StructField.IsExported</code></a>

View File

@ -4304,6 +4304,9 @@ func TestConvert(t *testing.T) {
// vout1 represents the in value converted to the in type. // vout1 represents the in value converted to the in type.
v1 := tt.in 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) vout1 := v1.Convert(t1)
out1 := vout1.Interface() out1 := vout1.Interface()
if vout1.Type() != tt.in.Type() || !DeepEqual(out1, tt.in.Interface()) { if vout1.Type() != tt.in.Type() || !DeepEqual(out1, tt.in.Interface()) {
@ -4311,6 +4314,9 @@ func TestConvert(t *testing.T) {
} }
// vout2 represents the in value converted to the out type. // 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) vout2 := v1.Convert(t2)
out2 := vout2.Interface() out2 := vout2.Interface()
if vout2.Type() != tt.out.Type() || !DeepEqual(out2, tt.out.Interface()) { if vout2.Type() != tt.out.Type() || !DeepEqual(out2, tt.out.Interface()) {
@ -4371,6 +4377,9 @@ func TestConvertPanic(t *testing.T) {
if !v.Type().ConvertibleTo(pt) { if !v.Type().ConvertibleTo(pt) {
t.Errorf("[]byte should be convertible to *[8]byte") 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() { shouldPanic("reflect: cannot convert slice with length 4 to pointer to array with length 8", func() {
_ = v.Convert(pt) _ = v.Convert(pt)
}) })

View File

@ -2811,6 +2811,26 @@ func (v Value) Convert(t Type) Value {
return op(v, t) 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 // 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. // to a value of type dst. If the conversion is illegal, convertOp returns nil.
func convertOp(dst, src *rtype) func(Value, Type) Value { func convertOp(dst, src *rtype) func(Value, Type) Value {