mirror of
https://github.com/protocolbuffers/protobuf-go.git
synced 2025-01-04 02:38:50 +00:00
378c1329de
Added API: Message.Len Message.Range Message.Has Message.Clear Message.Get Message.Set Message.Mutable Message.NewMessage Message.WhichOneof Message.GetUnknown Message.SetUnknown Deprecated API (to be removed in subsequent CL): Message.KnownFields Message.UnknownFields The primary difference with the new API is that the top-level Message methods are keyed by FieldDescriptor rather than FieldNumber with the following semantics: * For known fields, the FieldDescriptor must exactly match the field descriptor known by the message. * For extension fields, the FieldDescriptor must implement ExtensionType, where ContainingMessage.FullName matches the message name, and the field number is within the message's extension range. When setting an extension field, it automatically stores the extension type information. * Extension fields are always considered nullable, implying that repeated extension fields are nullable. That is, you can distinguish between a unpopulated list and an empty list. * Message.Get always returns a valid Value even if unpopulated. The behavior is already well-defined for scalars, but for unpopulated composite types, it now returns an empty read-only version of it. Change-Id: Ia120630b4db221aeaaf743d0f64160e1a61a0f61 Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/175458 Reviewed-by: Damien Neil <dneil@google.com>
305 lines
9.0 KiB
Go
305 lines
9.0 KiB
Go
// Copyright 2018 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 protoreflect
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"reflect"
|
|
)
|
|
|
|
// Value is a union where only one Go type may be set at a time.
|
|
// The Value is used to represent all possible values a field may take.
|
|
// The following shows which Go type is used to represent each proto Kind:
|
|
//
|
|
// +------------+-------------------------------------+
|
|
// | Go type | Protobuf kind |
|
|
// +------------+-------------------------------------+
|
|
// | bool | BoolKind |
|
|
// | int32 | Int32Kind, Sint32Kind, Sfixed32Kind |
|
|
// | int64 | Int64Kind, Sint64Kind, Sfixed64Kind |
|
|
// | uint32 | Uint32Kind, Fixed32Kind |
|
|
// | uint64 | Uint64Kind, Fixed64Kind |
|
|
// | float32 | FloatKind |
|
|
// | float64 | DoubleKind |
|
|
// | string | StringKind |
|
|
// | []byte | BytesKind |
|
|
// | EnumNumber | EnumKind |
|
|
// | Message | MessageKind, GroupKind |
|
|
// +------------+-------------------------------------+
|
|
//
|
|
// Multiple protobuf Kinds may be represented by a single Go type if the type
|
|
// can losslessly represent the information for the proto kind. For example,
|
|
// Int64Kind, Sint64Kind, and Sfixed64Kind are all represented by int64,
|
|
// but use different integer encoding methods.
|
|
//
|
|
// The List or Map types are used if the field cardinality is repeated.
|
|
// A field is a List if FieldDescriptor.IsList reports true.
|
|
// A field is a Map if FieldDescriptor.IsMap reports true.
|
|
//
|
|
// Converting to/from a Value and a concrete Go value panics on type mismatch.
|
|
// For example, ValueOf("hello").Int() panics because this attempts to
|
|
// retrieve an int64 from a string.
|
|
type Value value
|
|
|
|
// The protoreflect API uses a custom Value union type instead of interface{}
|
|
// to keep the future open for performance optimizations. Using an interface{}
|
|
// always incurs an allocation for primitives (e.g., int64) since it needs to
|
|
// be boxed on the heap (as interfaces can only contain pointers natively).
|
|
// Instead, we represent the Value union as a flat struct that internally keeps
|
|
// track of which type is set. Using unsafe, the Value union can be reduced
|
|
// down to 24B, which is identical in size to a slice.
|
|
//
|
|
// The latest compiler (Go1.11) currently suffers from some limitations:
|
|
// • With inlining, the compiler should be able to statically prove that
|
|
// only one of these switch cases are taken and inline one specific case.
|
|
// See https://golang.org/issue/22310.
|
|
|
|
// ValueOf returns a Value initialized with the concrete value stored in v.
|
|
// This panics if the type does not match one of the allowed types in the
|
|
// Value union.
|
|
func ValueOf(v interface{}) Value {
|
|
switch v := v.(type) {
|
|
case nil:
|
|
return Value{}
|
|
case bool:
|
|
if v {
|
|
return Value{typ: boolType, num: 1}
|
|
} else {
|
|
return Value{typ: boolType, num: 0}
|
|
}
|
|
case int32:
|
|
return Value{typ: int32Type, num: uint64(v)}
|
|
case int64:
|
|
return Value{typ: int64Type, num: uint64(v)}
|
|
case uint32:
|
|
return Value{typ: uint32Type, num: uint64(v)}
|
|
case uint64:
|
|
return Value{typ: uint64Type, num: uint64(v)}
|
|
case float32:
|
|
return Value{typ: float32Type, num: uint64(math.Float64bits(float64(v)))}
|
|
case float64:
|
|
return Value{typ: float64Type, num: uint64(math.Float64bits(float64(v)))}
|
|
case string:
|
|
return valueOfString(v)
|
|
case []byte:
|
|
return valueOfBytes(v[:len(v):len(v)])
|
|
case EnumNumber:
|
|
return Value{typ: enumType, num: uint64(v)}
|
|
case Message, List, Map:
|
|
return valueOfIface(v)
|
|
default:
|
|
// TODO: Special case Enum, ProtoMessage, *[]T, and *map[K]V?
|
|
// Note: this would violate the documented invariant in Interface.
|
|
panic(fmt.Sprintf("invalid type: %v", reflect.TypeOf(v)))
|
|
}
|
|
}
|
|
|
|
// IsValid reports whether v is populated with a value.
|
|
func (v Value) IsValid() bool {
|
|
return v.typ != nilType
|
|
}
|
|
|
|
// Interface returns v as an interface{}.
|
|
//
|
|
// Invariant: v == ValueOf(v).Interface()
|
|
func (v Value) Interface() interface{} {
|
|
switch v.typ {
|
|
case nilType:
|
|
return nil
|
|
case boolType:
|
|
return v.Bool()
|
|
case int32Type:
|
|
return int32(v.Int())
|
|
case int64Type:
|
|
return int64(v.Int())
|
|
case uint32Type:
|
|
return uint32(v.Uint())
|
|
case uint64Type:
|
|
return uint64(v.Uint())
|
|
case float32Type:
|
|
return float32(v.Float())
|
|
case float64Type:
|
|
return float64(v.Float())
|
|
case stringType:
|
|
return v.String()
|
|
case bytesType:
|
|
return v.Bytes()
|
|
case enumType:
|
|
return v.Enum()
|
|
default:
|
|
return v.getIface()
|
|
}
|
|
}
|
|
|
|
// Bool returns v as a bool and panics if the type is not a bool.
|
|
func (v Value) Bool() bool {
|
|
switch v.typ {
|
|
case boolType:
|
|
return v.num > 0
|
|
default:
|
|
panic("proto: value type mismatch")
|
|
}
|
|
}
|
|
|
|
// Int returns v as a int64 and panics if the type is not a int32 or int64.
|
|
func (v Value) Int() int64 {
|
|
switch v.typ {
|
|
case int32Type, int64Type:
|
|
return int64(v.num)
|
|
default:
|
|
panic("proto: value type mismatch")
|
|
}
|
|
}
|
|
|
|
// Uint returns v as a uint64 and panics if the type is not a uint32 or uint64.
|
|
func (v Value) Uint() uint64 {
|
|
switch v.typ {
|
|
case uint32Type, uint64Type:
|
|
return uint64(v.num)
|
|
default:
|
|
panic("proto: value type mismatch")
|
|
}
|
|
}
|
|
|
|
// Float returns v as a float64 and panics if the type is not a float32 or float64.
|
|
func (v Value) Float() float64 {
|
|
switch v.typ {
|
|
case float32Type, float64Type:
|
|
return math.Float64frombits(uint64(v.num))
|
|
default:
|
|
panic("proto: value type mismatch")
|
|
}
|
|
}
|
|
|
|
// String returns v as a string. Since this method implements fmt.Stringer,
|
|
// this returns the formatted string value for any non-string type.
|
|
func (v Value) String() string {
|
|
switch v.typ {
|
|
case stringType:
|
|
return v.getString()
|
|
default:
|
|
return fmt.Sprint(v.Interface())
|
|
}
|
|
}
|
|
|
|
// Bytes returns v as a []byte and panics if the type is not a []byte.
|
|
func (v Value) Bytes() []byte {
|
|
switch v.typ {
|
|
case bytesType:
|
|
return v.getBytes()
|
|
default:
|
|
panic("proto: value type mismatch")
|
|
}
|
|
}
|
|
|
|
// Enum returns v as a EnumNumber and panics if the type is not a EnumNumber.
|
|
func (v Value) Enum() EnumNumber {
|
|
switch v.typ {
|
|
case enumType:
|
|
return EnumNumber(v.num)
|
|
default:
|
|
panic("proto: value type mismatch")
|
|
}
|
|
}
|
|
|
|
// Message returns v as a Message and panics if the type is not a Message.
|
|
func (v Value) Message() Message {
|
|
switch v := v.getIface().(type) {
|
|
case Message:
|
|
return v
|
|
default:
|
|
panic("proto: value type mismatch")
|
|
}
|
|
}
|
|
|
|
// List returns v as a List and panics if the type is not a List.
|
|
func (v Value) List() List {
|
|
switch v := v.getIface().(type) {
|
|
case List:
|
|
return v
|
|
default:
|
|
panic("proto: value type mismatch")
|
|
}
|
|
}
|
|
|
|
// Map returns v as a Map and panics if the type is not a Map.
|
|
func (v Value) Map() Map {
|
|
switch v := v.getIface().(type) {
|
|
case Map:
|
|
return v
|
|
default:
|
|
panic("proto: value type mismatch")
|
|
}
|
|
}
|
|
|
|
// MapKey returns v as a MapKey and panics for invalid MapKey types.
|
|
func (v Value) MapKey() MapKey {
|
|
switch v.typ {
|
|
case boolType, int32Type, int64Type, uint32Type, uint64Type, stringType:
|
|
return MapKey(v)
|
|
}
|
|
panic("proto: invalid map key type")
|
|
}
|
|
|
|
// MapKey is used to index maps, where the Go type of the MapKey must match
|
|
// the specified key Kind (see MessageDescriptor.IsMapEntry).
|
|
// The following shows what Go type is used to represent each proto Kind:
|
|
//
|
|
// +---------+-------------------------------------+
|
|
// | Go type | Protobuf kind |
|
|
// +---------+-------------------------------------+
|
|
// | bool | BoolKind |
|
|
// | int32 | Int32Kind, Sint32Kind, Sfixed32Kind |
|
|
// | int64 | Int64Kind, Sint64Kind, Sfixed64Kind |
|
|
// | uint32 | Uint32Kind, Fixed32Kind |
|
|
// | uint64 | Uint64Kind, Fixed64Kind |
|
|
// | string | StringKind |
|
|
// +---------+-------------------------------------+
|
|
//
|
|
// A MapKey is constructed and accessed through a Value:
|
|
// k := ValueOf("hash").MapKey() // convert string to MapKey
|
|
// s := k.String() // convert MapKey to string
|
|
//
|
|
// The MapKey is a strict subset of valid types used in Value;
|
|
// converting a Value to a MapKey with an invalid type panics.
|
|
type MapKey value
|
|
|
|
// IsValid reports whether k is populated with a value.
|
|
func (k MapKey) IsValid() bool {
|
|
return Value(k).IsValid()
|
|
}
|
|
|
|
// Interface returns k as an interface{}.
|
|
func (k MapKey) Interface() interface{} {
|
|
return Value(k).Interface()
|
|
}
|
|
|
|
// Bool returns k as a bool and panics if the type is not a bool.
|
|
func (k MapKey) Bool() bool {
|
|
return Value(k).Bool()
|
|
}
|
|
|
|
// Int returns k as a int64 and panics if the type is not a int32 or int64.
|
|
func (k MapKey) Int() int64 {
|
|
return Value(k).Int()
|
|
}
|
|
|
|
// Uint returns k as a uint64 and panics if the type is not a uint32 or uint64.
|
|
func (k MapKey) Uint() uint64 {
|
|
return Value(k).Uint()
|
|
}
|
|
|
|
// String returns k as a string. Since this method implements fmt.Stringer,
|
|
// this returns the formatted string value for any non-string type.
|
|
func (k MapKey) String() string {
|
|
return Value(k).String()
|
|
}
|
|
|
|
// Value returns k as a Value.
|
|
func (k MapKey) Value() Value {
|
|
return Value(k)
|
|
}
|