| // Copyright 2020 The Bazel 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 proto defines a module of utilities for constructing and |
| // accessing protocol messages within Starlark programs. |
| // |
| // THIS PACKAGE IS EXPERIMENTAL AND ITS INTERFACE MAY CHANGE. |
| // |
| // This package defines several types of Starlark value: |
| // |
| // Message -- a protocol message |
| // RepeatedField -- a repeated field of a message, like a list |
| // |
| // FileDescriptor -- information about a .proto file |
| // FieldDescriptor -- information about a message field (or extension field) |
| // MessageDescriptor -- information about the type of a message |
| // EnumDescriptor -- information about an enumerated type |
| // EnumValueDescriptor -- a value of an enumerated type |
| // |
| // A Message value is a wrapper around a protocol message instance. |
| // Starlark programs may access and update Messages using dot notation: |
| // |
| // x = msg.field |
| // msg.field = x + 1 |
| // msg.field += 1 |
| // |
| // Assignments to message fields perform dynamic checks on the type and |
| // range of the value to ensure that the message is at all times valid. |
| // |
| // The value of a repeated field of a message is represented by the |
| // list-like data type, RepeatedField. Its elements may be accessed, |
| // iterated, and updated in the usual ways. As with assignments to |
| // message fields, an assignment to an element of a RepeatedField |
| // performs a dynamic check to ensure that the RepeatedField holds |
| // only elements of the correct type. |
| // |
| // type(msg.uint32s) # "proto.repeated<uint32>" |
| // msg.uint32s[0] = 1 |
| // msg.uint32s[0] = -1 # error: invalid uint32: -1 |
| // |
| // Any iterable may be assigned to a repeated field of a message. If |
| // the iterable is itself a value of type RepeatedField, the message |
| // field holds a reference to it. |
| // |
| // msg2.uint32s = msg.uint32s # both messages share one RepeatedField |
| // msg.uint32s[0] = 123 |
| // print(msg2.uint32s[0]) # "123" |
| // |
| // The RepeatedFields' element types must match. |
| // It is not enough for the values to be merely valid: |
| // |
| // msg.uint32s = [1, 2, 3] # makes a copy |
| // msg.uint64s = msg.uint32s # error: repeated field has wrong type |
| // msg.uint64s = list(msg.uint32s) # ok; makes a copy |
| // |
| // For all other iterables, a new RepeatedField is constructed from the |
| // elements of the iterable. |
| // |
| // msg.uints32s = [1, 2, 3] |
| // print(type(msg.uints32s)) # "proto.repeated<uint32>" |
| // |
| // |
| // To construct a Message from encoded binary or text data, call |
| // Unmarshal or UnmarshalText. These two functions are exposed to |
| // Starlark programs as proto.unmarshal{,_text}. |
| // |
| // To construct a Message from an existing Go proto.Message instance, |
| // you must first encode the Go message to binary, then decode it using |
| // Unmarshal. This ensures that messages visible to Starlark are |
| // encapsulated and cannot be mutated once their Starlark wrapper values |
| // are frozen. |
| // |
| // TODO(adonovan): document descriptors, enums, message instantiation. |
| // |
| // See proto_test.go for an example of how to use the 'proto' |
| // module in an application that embeds Starlark. |
| // |
| package proto |
| |
| // TODO(adonovan): Go and Starlark API improvements: |
| // - Make Message and RepeatedField comparable. |
| // (NOTE: proto.Equal works only with generated message types.) |
| // - Support maps, oneof, any. But not messageset if we can avoid it. |
| // - Support "well-known types". |
| // - Defend against cycles in object graph. |
| // - Test missing required fields in marshalling. |
| |
| import ( |
| "bytes" |
| "fmt" |
| "sort" |
| "strings" |
| "unsafe" |
| _ "unsafe" // for linkname hack |
| |
| "google.golang.org/protobuf/encoding/prototext" |
| "google.golang.org/protobuf/proto" |
| "google.golang.org/protobuf/reflect/protoreflect" |
| "google.golang.org/protobuf/reflect/protoregistry" |
| "google.golang.org/protobuf/types/dynamicpb" |
| |
| "go.starlark.net/starlark" |
| "go.starlark.net/starlarkstruct" |
| "go.starlark.net/syntax" |
| ) |
| |
| // SetPool associates with the specified Starlark thread the |
| // descriptor pool used to find descriptors for .proto files and to |
| // instantiate messages from descriptors. Clients must call SetPool |
| // for a Starlark thread to use this package. |
| // |
| // For example: |
| // SetPool(thread, protoregistry.GlobalFiles) |
| // |
| func SetPool(thread *starlark.Thread, pool DescriptorPool) { |
| thread.SetLocal(contextKey, pool) |
| } |
| |
| // Pool returns the descriptor pool previously associated with this thread. |
| func Pool(thread *starlark.Thread) DescriptorPool { |
| pool, _ := thread.Local(contextKey).(DescriptorPool) |
| return pool |
| } |
| |
| const contextKey = "proto.DescriptorPool" |
| |
| // A DescriptorPool loads FileDescriptors by path name or package name, |
| // possibly on demand. |
| // |
| // It is a superinterface of protodesc.Resolver, so any Resolver |
| // implementation is a valid pool. For example. |
| // protoregistry.GlobalFiles, which loads FileDescriptors from the |
| // compressed binary information in all the *.pb.go files linked into |
| // the process; and protodesc.NewFiles, which holds a set of |
| // FileDescriptorSet messages. See star2proto for example usage. |
| type DescriptorPool interface { |
| FindFileByPath(string) (protoreflect.FileDescriptor, error) |
| } |
| |
| var Module = &starlarkstruct.Module{ |
| Name: "proto", |
| Members: starlark.StringDict{ |
| "file": starlark.NewBuiltin("proto.file", file), |
| "has": starlark.NewBuiltin("proto.has", has), |
| "marshal": starlark.NewBuiltin("proto.marshal", marshal), |
| "marshal_text": starlark.NewBuiltin("proto.marshal_text", marshal), |
| "set_field": starlark.NewBuiltin("proto.set_field", setFieldStarlark), |
| "get_field": starlark.NewBuiltin("proto.get_field", getFieldStarlark), |
| "unmarshal": starlark.NewBuiltin("proto.unmarshal", unmarshal), |
| "unmarshal_text": starlark.NewBuiltin("proto.unmarshal_text", unmarshal_text), |
| |
| // TODO(adonovan): |
| // - merge(msg, msg) -> msg |
| // - equals(msg, msg) -> bool |
| // - diff(msg, msg) -> string |
| // - clone(msg) -> msg |
| }, |
| } |
| |
| // file(filename) loads the FileDescriptor of the given name, or the |
| // first if the pool contains more than one. |
| // |
| // It's unfortunate that renaming a .proto file in effect breaks the |
| // interface it presents to Starlark. Ideally one would import |
| // descriptors by package name, but there may be many FileDescriptors |
| // for the same package name, and there is no "package descriptor". |
| // (Technically a pool may also have many FileDescriptors with the same |
| // file name, but this can't happen with a single consistent snapshot.) |
| func file(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { |
| var filename string |
| if err := starlark.UnpackPositionalArgs(fn.Name(), args, kwargs, 1, &filename); err != nil { |
| return nil, err |
| } |
| |
| pool := Pool(thread) |
| if pool == nil { |
| return nil, fmt.Errorf("internal error: SetPool was not called") |
| } |
| |
| desc, err := pool.FindFileByPath(filename) |
| if err != nil { |
| return nil, err |
| } |
| |
| return FileDescriptor{Desc: desc}, nil |
| } |
| |
| // has(msg, field) reports whether the specified field of the message is present. |
| // A field may be specified by name (string) or FieldDescriptor. |
| // has reports an error if the message type has no such field. |
| func has(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { |
| var x, field starlark.Value |
| if err := starlark.UnpackPositionalArgs(fn.Name(), args, kwargs, 2, &x, &field); err != nil { |
| return nil, err |
| } |
| msg, ok := x.(*Message) |
| if !ok { |
| return nil, fmt.Errorf("%s: got %s, want proto.Message", fn.Name(), x.Type()) |
| } |
| |
| var fdesc protoreflect.FieldDescriptor |
| switch field := field.(type) { |
| case starlark.String: |
| var err error |
| fdesc, err = fieldDesc(msg.desc(), string(field)) |
| if err != nil { |
| return nil, err |
| } |
| |
| case FieldDescriptor: |
| if field.Desc.ContainingMessage() != msg.desc() { |
| return nil, fmt.Errorf("%s: %v does not have field %v", fn.Name(), msg.desc().FullName(), field) |
| } |
| fdesc = field.Desc |
| |
| default: |
| return nil, fmt.Errorf("%s: for field argument, got %s, want string or proto.FieldDescriptor", fn.Name(), field.Type()) |
| } |
| |
| return starlark.Bool(msg.msg.Has(fdesc)), nil |
| } |
| |
| // marshal{,_text}(msg) encodes a Message value to binary or text form. |
| func marshal(_ *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { |
| var m *Message |
| if err := starlark.UnpackPositionalArgs(fn.Name(), args, kwargs, 1, &m); err != nil { |
| return nil, err |
| } |
| if fn.Name() == "proto.marshal" { |
| data, err := proto.Marshal(m.Message()) |
| if err != nil { |
| return nil, fmt.Errorf("%s: %v", fn.Name(), err) |
| } |
| return starlark.Bytes(data), nil |
| } else { |
| text, err := prototext.MarshalOptions{Indent: " "}.Marshal(m.Message()) |
| if err != nil { |
| return nil, fmt.Errorf("%s: %v", fn.Name(), err) |
| } |
| return starlark.String(text), nil |
| } |
| } |
| |
| // unmarshal(msg) decodes a binary protocol message to a Message. |
| func unmarshal(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { |
| var desc MessageDescriptor |
| var data starlark.Bytes |
| if err := starlark.UnpackPositionalArgs(fn.Name(), args, kwargs, 2, &desc, &data); err != nil { |
| return nil, err |
| } |
| return unmarshalData(desc.Desc, []byte(data), true) |
| } |
| |
| // unmarshal_text(msg) decodes a text protocol message to a Message. |
| func unmarshal_text(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { |
| var desc MessageDescriptor |
| var data string |
| if err := starlark.UnpackPositionalArgs(fn.Name(), args, kwargs, 2, &desc, &data); err != nil { |
| return nil, err |
| } |
| return unmarshalData(desc.Desc, []byte(data), false) |
| } |
| |
| // set_field(msg, field, value) updates the value of a field. |
| // It is typically used for extensions, which cannot be updated using msg.field = v notation. |
| func setFieldStarlark(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { |
| // TODO(adonovan): allow field to be specified by name (for non-extension fields), like has? |
| var m *Message |
| var field FieldDescriptor |
| var v starlark.Value |
| if err := starlark.UnpackPositionalArgs(fn.Name(), args, kwargs, 3, &m, &field, &v); err != nil { |
| return nil, err |
| } |
| |
| if *m.frozen { |
| return nil, fmt.Errorf("%s: cannot set %v field of frozen %v message", fn.Name(), field, m.desc().FullName()) |
| } |
| |
| if field.Desc.ContainingMessage() != m.desc() { |
| return nil, fmt.Errorf("%s: %v does not have field %v", fn.Name(), m.desc().FullName(), field) |
| } |
| |
| return starlark.None, setField(m.msg, field.Desc, v) |
| } |
| |
| // get_field(msg, field) retrieves the value of a field. |
| // It is typically used for extension fields, which cannot be accessed using msg.field notation. |
| func getFieldStarlark(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { |
| // TODO(adonovan): allow field to be specified by name (for non-extension fields), like has? |
| var msg *Message |
| var field FieldDescriptor |
| if err := starlark.UnpackPositionalArgs(fn.Name(), args, kwargs, 2, &msg, &field); err != nil { |
| return nil, err |
| } |
| |
| if field.Desc.ContainingMessage() != msg.desc() { |
| return nil, fmt.Errorf("%s: %v does not have field %v", fn.Name(), msg.desc().FullName(), field) |
| } |
| |
| return msg.getField(field.Desc), nil |
| } |
| |
| // The Call method implements the starlark.Callable interface. |
| // When a message descriptor is called, it returns a new instance of the |
| // protocol message it describes. |
| // |
| // Message(msg) -- return a shallow copy of an existing message |
| // Message(k=v, ...) -- return a new message with the specified fields |
| // Message(dict(...)) -- return a new message with the specified fields |
| // |
| func (d MessageDescriptor) CallInternal(thread *starlark.Thread, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { |
| dest := &Message{ |
| msg: newMessage(d.Desc), |
| frozen: new(bool), |
| } |
| |
| // Single positional argument? |
| if len(args) > 0 { |
| if len(kwargs) > 0 { |
| return nil, fmt.Errorf("%s: got both positional and named arguments", d.Desc.Name()) |
| } |
| if len(args) > 1 { |
| return nil, fmt.Errorf("%s: got %d positional arguments, want at most 1", d.Desc.Name(), len(args)) |
| } |
| |
| // Keep consistent with MessageKind case of toProto. |
| // (support the same argument types). |
| switch src := args[0].(type) { |
| case *Message: |
| if dest.desc() != src.desc() { |
| return nil, fmt.Errorf("%s: got message of type %s, want type %s", d.Desc.Name(), src.desc().FullName(), dest.desc().FullName()) |
| } |
| |
| // Make shallow copy of message. |
| // TODO(adonovan): How does frozen work if we have shallow copy? |
| src.msg.Range(func(fdesc protoreflect.FieldDescriptor, v protoreflect.Value) bool { |
| dest.msg.Set(fdesc, v) |
| return true |
| }) |
| return dest, nil |
| |
| case *starlark.Dict: |
| kwargs = src.Items() |
| // fall through |
| |
| default: |
| return nil, fmt.Errorf("%s: got %s, want dict or message", d.Desc.Name(), src.Type()) |
| } |
| } |
| |
| // Convert named arguments to field values. |
| err := setFields(dest.msg, kwargs) |
| return dest, err |
| } |
| |
| // setFields updates msg as if by msg.name=value for each (name, value) in items. |
| func setFields(msg protoreflect.Message, items []starlark.Tuple) error { |
| for _, item := range items { |
| name, ok := starlark.AsString(item[0]) |
| if !ok { |
| return fmt.Errorf("got %s, want string", item[0].Type()) |
| } |
| fdesc, err := fieldDesc(msg.Descriptor(), name) |
| if err != nil { |
| return err |
| } |
| if err := setField(msg, fdesc, item[1]); err != nil { |
| return err |
| } |
| } |
| return nil |
| } |
| |
| // setField validates a Starlark field value, converts it to canonical form, |
| // and assigns to the field of msg. If value is None, the field is unset. |
| func setField(msg protoreflect.Message, fdesc protoreflect.FieldDescriptor, value starlark.Value) error { |
| // None unsets a field. |
| if value == starlark.None { |
| msg.Clear(fdesc) |
| return nil |
| } |
| |
| // Assigning to a repeated field must make a copy, |
| // because the fields.Set doesn't specify whether |
| // it aliases the list or not, so we cannot assume. |
| // |
| // This is potentially surprising as |
| // x = []; msg.x = x; y = msg.x |
| // causes x and y not to alias. |
| if fdesc.IsList() { |
| iter := starlark.Iterate(value) |
| if iter == nil { |
| return fmt.Errorf("got %s for .%s field, want iterable", value.Type(), fdesc.Name()) |
| } |
| defer iter.Done() |
| |
| // TODO(adonovan): handle maps |
| list := msg.Mutable(fdesc).List() |
| var x starlark.Value |
| for i := 0; iter.Next(&x); i++ { |
| v, err := toProto(fdesc, x) |
| if err != nil { |
| return fmt.Errorf("index %d: %v", i, err) |
| } |
| list.Append(v) |
| } |
| return nil |
| } |
| |
| v, err := toProto(fdesc, value) |
| if err != nil { |
| return fmt.Errorf("in field %s: %v", fdesc.Name(), err) |
| } |
| |
| if fdesc.IsExtension() { |
| // The protoreflect.Message.NewField method must be able |
| // to return a new instance of the field type. Without |
| // having the Go type information available for extensions, |
| // the implementation of NewField won't know what to do. |
| // |
| // Thus we must augment the FieldDescriptor to one that |
| // additional holds Go representation type information |
| // (based in this case on dynamicpb). |
| fdesc = dynamicpb.NewExtensionType(fdesc).TypeDescriptor() |
| _ = fdesc.(protoreflect.ExtensionTypeDescriptor) |
| } |
| |
| msg.Set(fdesc, v) |
| return nil |
| } |
| |
| // toProto converts a Starlark value for a message field into protoreflect form. |
| func toProto(fdesc protoreflect.FieldDescriptor, v starlark.Value) (protoreflect.Value, error) { |
| switch fdesc.Kind() { |
| case protoreflect.BoolKind: |
| // To avoid mistakes, we require v be exactly a bool. |
| if v, ok := v.(starlark.Bool); ok { |
| return protoreflect.ValueOfBool(bool(v)), nil |
| } |
| |
| case protoreflect.Fixed32Kind, |
| protoreflect.Uint32Kind: |
| // uint32 |
| if i, ok := v.(starlark.Int); ok { |
| if u, ok := i.Uint64(); ok && uint64(uint32(u)) == u { |
| return protoreflect.ValueOfUint32(uint32(u)), nil |
| } |
| return noValue, fmt.Errorf("invalid %s: %v", typeString(fdesc), i) |
| } |
| |
| case protoreflect.Int32Kind, |
| protoreflect.Sfixed32Kind, |
| protoreflect.Sint32Kind: |
| // int32 |
| if i, ok := v.(starlark.Int); ok { |
| if i, ok := i.Int64(); ok && int64(int32(i)) == i { |
| return protoreflect.ValueOfInt32(int32(i)), nil |
| } |
| return noValue, fmt.Errorf("invalid %s: %v", typeString(fdesc), i) |
| } |
| |
| case protoreflect.Uint64Kind, |
| protoreflect.Fixed64Kind: |
| // uint64 |
| if i, ok := v.(starlark.Int); ok { |
| if u, ok := i.Uint64(); ok { |
| return protoreflect.ValueOfUint64(u), nil |
| } |
| return noValue, fmt.Errorf("invalid %s: %v", typeString(fdesc), i) |
| } |
| |
| case protoreflect.Int64Kind, |
| protoreflect.Sfixed64Kind, |
| protoreflect.Sint64Kind: |
| // int64 |
| if i, ok := v.(starlark.Int); ok { |
| if i, ok := i.Int64(); ok { |
| return protoreflect.ValueOfInt64(i), nil |
| } |
| return noValue, fmt.Errorf("invalid %s: %v", typeString(fdesc), i) |
| } |
| |
| case protoreflect.StringKind: |
| if s, ok := starlark.AsString(v); ok { |
| return protoreflect.ValueOfString(s), nil |
| } else if b, ok := v.(starlark.Bytes); ok { |
| // TODO(adonovan): allow bytes for string? Not friendly to a Java port. |
| return protoreflect.ValueOfBytes([]byte(b)), nil |
| } |
| |
| case protoreflect.BytesKind: |
| if s, ok := starlark.AsString(v); ok { |
| // TODO(adonovan): don't allow string for bytes: it's hostile to a Java port. |
| // Instead provide b"..." literals in the core |
| // and a bytes(str) conversion. |
| return protoreflect.ValueOfBytes([]byte(s)), nil |
| } else if b, ok := v.(starlark.Bytes); ok { |
| return protoreflect.ValueOfBytes([]byte(b)), nil |
| } |
| |
| case protoreflect.DoubleKind: |
| switch v := v.(type) { |
| case starlark.Float: |
| return protoreflect.ValueOfFloat64(float64(v)), nil |
| case starlark.Int: |
| return protoreflect.ValueOfFloat64(float64(v.Float())), nil |
| } |
| |
| case protoreflect.FloatKind: |
| switch v := v.(type) { |
| case starlark.Float: |
| return protoreflect.ValueOfFloat32(float32(v)), nil |
| case starlark.Int: |
| return protoreflect.ValueOfFloat32(float32(v.Float())), nil |
| } |
| |
| case protoreflect.GroupKind, |
| protoreflect.MessageKind: |
| // Keep consistent with MessageDescriptor.CallInternal! |
| desc := fdesc.Message() |
| switch v := v.(type) { |
| case *Message: |
| if desc != v.desc() { |
| return noValue, fmt.Errorf("got %s, want %s", v.desc().FullName(), desc.FullName()) |
| } |
| return protoreflect.ValueOfMessage(v.msg), nil // alias it directly |
| |
| case *starlark.Dict: |
| dest := newMessage(desc) |
| err := setFields(dest, v.Items()) |
| return protoreflect.ValueOfMessage(dest), err |
| } |
| |
| case protoreflect.EnumKind: |
| enumval, err := enumValueOf(fdesc.Enum(), v) |
| if err != nil { |
| return noValue, err |
| } |
| return protoreflect.ValueOfEnum(enumval.Number()), nil |
| } |
| |
| return noValue, fmt.Errorf("got %s, want %s", v.Type(), typeString(fdesc)) |
| } |
| |
| var noValue protoreflect.Value |
| |
| // toStarlark returns a Starlark value for the value x of a message field. |
| // If the result is a repeated field or message, |
| // the result aliases the original and has the specified "frozenness" flag. |
| // |
| // fdesc is only used for the type, not other properties of the field. |
| func toStarlark(typ protoreflect.FieldDescriptor, x protoreflect.Value, frozen *bool) starlark.Value { |
| if list, ok := x.Interface().(protoreflect.List); ok { |
| return &RepeatedField{ |
| typ: typ, |
| list: list, |
| frozen: frozen, |
| } |
| } |
| return toStarlark1(typ, x, frozen) |
| } |
| |
| // toStarlark1, for scalar (non-repeated) values only. |
| func toStarlark1(typ protoreflect.FieldDescriptor, x protoreflect.Value, frozen *bool) starlark.Value { |
| |
| switch typ.Kind() { |
| case protoreflect.BoolKind: |
| return starlark.Bool(x.Bool()) |
| |
| case protoreflect.Fixed32Kind, |
| protoreflect.Uint32Kind, |
| protoreflect.Uint64Kind, |
| protoreflect.Fixed64Kind: |
| return starlark.MakeUint64(x.Uint()) |
| |
| case protoreflect.Int32Kind, |
| protoreflect.Sfixed32Kind, |
| protoreflect.Sint32Kind, |
| protoreflect.Int64Kind, |
| protoreflect.Sfixed64Kind, |
| protoreflect.Sint64Kind: |
| return starlark.MakeInt64(x.Int()) |
| |
| case protoreflect.StringKind: |
| return starlark.String(x.String()) |
| |
| case protoreflect.BytesKind: |
| return starlark.Bytes(x.Bytes()) |
| |
| case protoreflect.DoubleKind, protoreflect.FloatKind: |
| return starlark.Float(x.Float()) |
| |
| case protoreflect.GroupKind, protoreflect.MessageKind: |
| return &Message{ |
| msg: x.Message(), |
| frozen: frozen, |
| } |
| |
| case protoreflect.EnumKind: |
| // Invariant: only EnumValueDescriptor may appear here. |
| enumval := typ.Enum().Values().ByNumber(x.Enum()) |
| return EnumValueDescriptor{Desc: enumval} |
| } |
| |
| panic(fmt.Sprintf("got %T, want %s", x, typeString(typ))) |
| } |
| |
| // A Message is a Starlark value that wraps a protocol message. |
| // |
| // Two Messages are equivalent if and only if they are identical. |
| // |
| // When a Message value becomes frozen, a Starlark program may |
| // not modify the underlying protocol message, nor any Message |
| // or RepeatedField wrapper values derived from it. |
| type Message struct { |
| msg protoreflect.Message // any concrete type is allowed |
| frozen *bool // shared by a group of related Message/RepeatedField wrappers |
| } |
| |
| // Message returns the wrapped message. |
| func (m *Message) Message() protoreflect.ProtoMessage { return m.msg.Interface() } |
| |
| func (m *Message) desc() protoreflect.MessageDescriptor { return m.msg.Descriptor() } |
| |
| var _ starlark.HasSetField = (*Message)(nil) |
| |
| // Unmarshal parses the data as a binary protocol message of the specified type, |
| // and returns it as a new Starlark message value. |
| func Unmarshal(desc protoreflect.MessageDescriptor, data []byte) (*Message, error) { |
| return unmarshalData(desc, data, true) |
| } |
| |
| // UnmarshalText parses the data as a text protocol message of the specified type, |
| // and returns it as a new Starlark message value. |
| func UnmarshalText(desc protoreflect.MessageDescriptor, data []byte) (*Message, error) { |
| return unmarshalData(desc, data, false) |
| } |
| |
| // unmarshalData constructs a Starlark proto.Message by decoding binary or text data. |
| func unmarshalData(desc protoreflect.MessageDescriptor, data []byte, binary bool) (*Message, error) { |
| m := &Message{ |
| msg: newMessage(desc), |
| frozen: new(bool), |
| } |
| var err error |
| if binary { |
| err = proto.Unmarshal(data, m.Message()) |
| } else { |
| err = prototext.Unmarshal(data, m.Message()) |
| } |
| if err != nil { |
| return nil, fmt.Errorf("unmarshalling %s failed: %v", desc.FullName(), err) |
| } |
| return m, nil |
| } |
| |
| func (m *Message) String() string { |
| buf := new(bytes.Buffer) |
| buf.WriteString(string(m.desc().FullName())) |
| buf.WriteByte('(') |
| |
| // Sort fields (including extensions) by number. |
| var fields []protoreflect.FieldDescriptor |
| m.msg.Range(func(fdesc protoreflect.FieldDescriptor, v protoreflect.Value) bool { |
| // TODO(adonovan): opt: save v in table too. |
| fields = append(fields, fdesc) |
| return true |
| }) |
| sort.Slice(fields, func(i, j int) bool { |
| return fields[i].Number() < fields[j].Number() |
| }) |
| |
| for i, fdesc := range fields { |
| if i > 0 { |
| buf.WriteString(", ") |
| } |
| if fdesc.IsExtension() { |
| // extension field: "[pkg.Msg.field]" |
| buf.WriteString(string(fdesc.FullName())) |
| } else if fdesc.Kind() != protoreflect.GroupKind { |
| // ordinary field: "field" |
| buf.WriteString(string(fdesc.Name())) |
| } else { |
| // group field: "MyGroup" |
| // |
| // The name of a group is the mangled version, |
| // while the true name of a group is the message itself. |
| // For example, for a group called "MyGroup", |
| // the inlined message will be called "MyGroup", |
| // but the field will be named "mygroup". |
| // This rule complicates name logic everywhere. |
| buf.WriteString(string(fdesc.Message().Name())) |
| } |
| buf.WriteString("=") |
| writeString(buf, fdesc, m.msg.Get(fdesc)) |
| } |
| buf.WriteByte(')') |
| return buf.String() |
| } |
| |
| func (m *Message) Type() string { return "proto.Message" } |
| func (m *Message) Truth() starlark.Bool { return true } |
| func (m *Message) Freeze() { *m.frozen = true } |
| func (m *Message) Hash() (h uint32, err error) { return uint32(uintptr(unsafe.Pointer(m))), nil } // identity hash |
| |
| // Attr returns the value of this message's field of the specified name. |
| // Extension fields are not accessible this way as their names are not unique. |
| func (m *Message) Attr(name string) (starlark.Value, error) { |
| // The name 'descriptor' is already effectively reserved |
| // by the Go API for generated message types. |
| if name == "descriptor" { |
| return MessageDescriptor{Desc: m.desc()}, nil |
| } |
| |
| fdesc, err := fieldDesc(m.desc(), name) |
| if err != nil { |
| return nil, err |
| } |
| return m.getField(fdesc), nil |
| } |
| |
| func (m *Message) getField(fdesc protoreflect.FieldDescriptor) starlark.Value { |
| if fdesc.IsExtension() { |
| // See explanation in setField. |
| fdesc = dynamicpb.NewExtensionType(fdesc).TypeDescriptor() |
| } |
| |
| if m.msg.Has(fdesc) { |
| return toStarlark(fdesc, m.msg.Get(fdesc), m.frozen) |
| } |
| return defaultValue(fdesc) |
| } |
| |
| //go:linkname detrandDisable google.golang.org/protobuf/internal/detrand.Disable |
| func detrandDisable() |
| |
| func init() { |
| // Nasty hack to disable the randomization of output that occurs in textproto. |
| // TODO(adonovan): once go/proto-proposals/canonical-serialization |
| // is resolved the need for the hack should go away. See also go/go-proto-stability. |
| // If the proposal is rejected, we will need our own text-mode formatter. |
| detrandDisable() |
| } |
| |
| // defaultValue returns the (frozen) default Starlark value for a given message field. |
| func defaultValue(fdesc protoreflect.FieldDescriptor) starlark.Value { |
| frozen := true |
| |
| // The default value of a repeated field is an empty list. |
| if fdesc.IsList() { |
| return &RepeatedField{typ: fdesc, list: emptyList{}, frozen: &frozen} |
| } |
| |
| // The zero value for a message type is an empty instance of that message. |
| if desc := fdesc.Message(); desc != nil { |
| return &Message{msg: newMessage(desc), frozen: &frozen} |
| } |
| |
| // Convert the default value, which is not necessarily zero, to Starlark. |
| // The frozenness isn't used as the remaining types are all immutable. |
| return toStarlark1(fdesc, fdesc.Default(), &frozen) |
| } |
| |
| // A frozen empty implementation of protoreflect.List. |
| type emptyList struct{ protoreflect.List } |
| |
| func (emptyList) Len() int { return 0 } |
| |
| // newMessage returns a new empty instance of the message type described by desc. |
| func newMessage(desc protoreflect.MessageDescriptor) protoreflect.Message { |
| // If desc refers to a built-in message, |
| // use the more efficient generated type descriptor (a Go struct). |
| mt, err := protoregistry.GlobalTypes.FindMessageByName(desc.FullName()) |
| if err == nil && mt.Descriptor() == desc { |
| return mt.New() |
| } |
| |
| // For all others, use the generic dynamicpb representation. |
| return dynamicpb.NewMessage(desc).ProtoReflect() |
| } |
| |
| // fieldDesc returns the descriptor for the named non-extension field. |
| func fieldDesc(desc protoreflect.MessageDescriptor, name string) (protoreflect.FieldDescriptor, error) { |
| if fdesc := desc.Fields().ByName(protoreflect.Name(name)); fdesc != nil { |
| return fdesc, nil |
| } |
| return nil, starlark.NoSuchAttrError(fmt.Sprintf("%s has no .%s field", desc.FullName(), name)) |
| } |
| |
| // SetField updates a non-extension field of this message. |
| // It implements the HasSetField interface. |
| func (m *Message) SetField(name string, v starlark.Value) error { |
| fdesc, err := fieldDesc(m.desc(), name) |
| if err != nil { |
| return err |
| } |
| if *m.frozen { |
| return fmt.Errorf("cannot set .%s field of frozen %s message", |
| name, m.desc().FullName()) |
| } |
| return setField(m.msg, fdesc, v) |
| } |
| |
| // AttrNames returns the set of field names defined for this message. |
| // It satisfies the starlark.HasAttrs interface. |
| func (m *Message) AttrNames() []string { |
| seen := make(map[string]bool) |
| |
| // standard fields |
| seen["descriptor"] = true |
| |
| // non-extension fields |
| fields := m.desc().Fields() |
| for i := 0; i < fields.Len(); i++ { |
| fdesc := fields.Get(i) |
| if !fdesc.IsExtension() { |
| seen[string(fdesc.Name())] = true |
| } |
| } |
| |
| names := make([]string, 0, len(seen)) |
| for name := range seen { |
| names = append(names, name) |
| } |
| sort.Strings(names) |
| return names |
| } |
| |
| // typeString returns a user-friendly description of the type of a |
| // protocol message field (or element of a repeated field). |
| func typeString(fdesc protoreflect.FieldDescriptor) string { |
| switch fdesc.Kind() { |
| case protoreflect.GroupKind, |
| protoreflect.MessageKind: |
| return string(fdesc.Message().FullName()) |
| |
| case protoreflect.EnumKind: |
| return string(fdesc.Enum().FullName()) |
| |
| default: |
| return strings.ToLower(strings.TrimPrefix(fdesc.Kind().String(), "TYPE_")) |
| } |
| } |
| |
| // A RepeatedField is a Starlark value that wraps a repeated field of a protocol message. |
| // |
| // An assignment to an element of a repeated field incurs a dynamic |
| // check that the new value has (or can be converted to) the correct |
| // type using conversions similar to those done when calling a |
| // MessageDescriptor to construct a message. |
| // |
| // TODO(adonovan): make RepeatedField implement starlark.Comparable. |
| // Should the comparison include type, or be defined on the elements alone? |
| type RepeatedField struct { |
| typ protoreflect.FieldDescriptor // only for type information, not field name |
| list protoreflect.List |
| frozen *bool |
| itercount int |
| } |
| |
| var _ starlark.HasSetIndex = (*RepeatedField)(nil) |
| |
| func (rf *RepeatedField) Type() string { |
| return fmt.Sprintf("proto.repeated<%s>", typeString(rf.typ)) |
| } |
| |
| func (rf *RepeatedField) SetIndex(i int, v starlark.Value) error { |
| if *rf.frozen { |
| return fmt.Errorf("cannot insert value in frozen repeated field") |
| } |
| if rf.itercount > 0 { |
| return fmt.Errorf("cannot insert value in repeated field with active iterators") |
| } |
| x, err := toProto(rf.typ, v) |
| if err != nil { |
| // The repeated field value cannot know which field it |
| // belongs to---it might be shared by several of the |
| // same type---so the error message is suboptimal. |
| return fmt.Errorf("setting element of repeated field: %v", err) |
| } |
| rf.list.Set(i, x) |
| return nil |
| } |
| |
| func (rf *RepeatedField) Freeze() { *rf.frozen = true } |
| func (rf *RepeatedField) Hash() (uint32, error) { return 0, fmt.Errorf("unhashable: %s", rf.Type()) } |
| func (rf *RepeatedField) Index(i int) starlark.Value { |
| return toStarlark1(rf.typ, rf.list.Get(i), rf.frozen) |
| } |
| func (rf *RepeatedField) Iterate() starlark.Iterator { |
| if !*rf.frozen { |
| rf.itercount++ |
| } |
| return &repeatedFieldIterator{rf, 0} |
| } |
| func (rf *RepeatedField) Len() int { return rf.list.Len() } |
| func (rf *RepeatedField) String() string { |
| // We use list [...] notation even though it not exactly a list. |
| buf := new(bytes.Buffer) |
| buf.WriteByte('[') |
| for i := 0; i < rf.list.Len(); i++ { |
| if i > 0 { |
| buf.WriteString(", ") |
| } |
| writeString(buf, rf.typ, rf.list.Get(i)) |
| } |
| buf.WriteByte(']') |
| return buf.String() |
| } |
| func (rf *RepeatedField) Truth() starlark.Bool { return rf.list.Len() > 0 } |
| |
| type repeatedFieldIterator struct { |
| rf *RepeatedField |
| i int |
| } |
| |
| func (it *repeatedFieldIterator) Next(p *starlark.Value) bool { |
| if it.i < it.rf.Len() { |
| *p = it.rf.Index(it.i) |
| it.i++ |
| return true |
| } |
| return false |
| } |
| |
| func (it *repeatedFieldIterator) Done() { |
| if !*it.rf.frozen { |
| it.rf.itercount-- |
| } |
| } |
| |
| func writeString(buf *bytes.Buffer, fdesc protoreflect.FieldDescriptor, v protoreflect.Value) { |
| // TODO(adonovan): opt: don't materialize the Starlark value. |
| // TODO(adonovan): skip message type when printing submessages? {...}? |
| var frozen bool // ignored |
| x := toStarlark(fdesc, v, &frozen) |
| buf.WriteString(x.String()) |
| } |
| |
| // -------- descriptor values -------- |
| |
| // A FileDescriptor is an immutable Starlark value that describes a |
| // .proto file. It is a reference to a protoreflect.FileDescriptor. |
| // Two FileDescriptor values compare equal if and only if they refer to |
| // the same protoreflect.FileDescriptor. |
| // |
| // Its fields are the names of the message types (MessageDescriptor) and enum |
| // types (EnumDescriptor). |
| type FileDescriptor struct { |
| Desc protoreflect.FileDescriptor // TODO(adonovan): hide field, expose method? |
| } |
| |
| var _ starlark.HasAttrs = FileDescriptor{} |
| |
| func (f FileDescriptor) String() string { return string(f.Desc.Path()) } |
| func (f FileDescriptor) Type() string { return "proto.FileDescriptor" } |
| func (f FileDescriptor) Truth() starlark.Bool { return true } |
| func (f FileDescriptor) Freeze() {} // immutable |
| func (f FileDescriptor) Hash() (h uint32, err error) { return starlark.String(f.Desc.Path()).Hash() } |
| func (f FileDescriptor) Attr(name string) (starlark.Value, error) { |
| if desc := f.Desc.Messages().ByName(protoreflect.Name(name)); desc != nil { |
| return MessageDescriptor{Desc: desc}, nil |
| } |
| if desc := f.Desc.Extensions().ByName(protoreflect.Name(name)); desc != nil { |
| return FieldDescriptor{desc}, nil |
| } |
| if enum := f.Desc.Enums().ByName(protoreflect.Name(name)); enum != nil { |
| return EnumDescriptor{Desc: enum}, nil |
| } |
| return nil, nil |
| } |
| func (f FileDescriptor) AttrNames() []string { |
| var names []string |
| messages := f.Desc.Messages() |
| for i, n := 0, messages.Len(); i < n; i++ { |
| names = append(names, string(messages.Get(i).Name())) |
| } |
| extensions := f.Desc.Extensions() |
| for i, n := 0, extensions.Len(); i < n; i++ { |
| names = append(names, string(extensions.Get(i).Name())) |
| } |
| enums := f.Desc.Enums() |
| for i, n := 0, enums.Len(); i < n; i++ { |
| names = append(names, string(enums.Get(i).Name())) |
| } |
| sort.Strings(names) |
| return names |
| } |
| |
| // A MessageDescriptor is an immutable Starlark value that describes a protocol |
| // message type. |
| // |
| // A MessageDescriptor value contains a reference to a protoreflect.MessageDescriptor. |
| // Two MessageDescriptor values compare equal if and only if they refer to the |
| // same protoreflect.MessageDescriptor. |
| // |
| // The fields of a MessageDescriptor value are the names of any message types |
| // (MessageDescriptor), fields or extension fields (FieldDescriptor), |
| // and enum types (EnumDescriptor) nested within the declaration of this message type. |
| type MessageDescriptor struct { |
| Desc protoreflect.MessageDescriptor |
| } |
| |
| var ( |
| _ starlark.Callable = MessageDescriptor{} |
| _ starlark.HasAttrs = MessageDescriptor{} |
| ) |
| |
| func (d MessageDescriptor) String() string { return string(d.Desc.FullName()) } |
| func (d MessageDescriptor) Type() string { return "proto.MessageDescriptor" } |
| func (d MessageDescriptor) Truth() starlark.Bool { return true } |
| func (d MessageDescriptor) Freeze() {} // immutable |
| func (d MessageDescriptor) Hash() (h uint32, err error) { |
| return starlark.String(d.Desc.FullName()).Hash() |
| } |
| func (d MessageDescriptor) Attr(name string) (starlark.Value, error) { |
| if desc := d.Desc.Messages().ByName(protoreflect.Name(name)); desc != nil { |
| return MessageDescriptor{desc}, nil |
| } |
| if desc := d.Desc.Extensions().ByName(protoreflect.Name(name)); desc != nil { |
| return FieldDescriptor{desc}, nil |
| } |
| if desc := d.Desc.Fields().ByName(protoreflect.Name(name)); desc != nil { |
| return FieldDescriptor{desc}, nil |
| } |
| if desc := d.Desc.Enums().ByName(protoreflect.Name(name)); desc != nil { |
| return EnumDescriptor{desc}, nil |
| } |
| return nil, nil |
| } |
| func (d MessageDescriptor) AttrNames() []string { |
| var names []string |
| messages := d.Desc.Messages() |
| for i, n := 0, messages.Len(); i < n; i++ { |
| names = append(names, string(messages.Get(i).Name())) |
| } |
| enums := d.Desc.Enums() |
| for i, n := 0, enums.Len(); i < n; i++ { |
| names = append(names, string(enums.Get(i).Name())) |
| } |
| sort.Strings(names) |
| return names |
| } |
| func (d MessageDescriptor) Name() string { return string(d.Desc.Name()) } // for Callable |
| |
| // A FieldDescriptor is an immutable Starlark value that describes |
| // a field (possibly an extension field) of protocol message. |
| // |
| // A FieldDescriptor value contains a reference to a protoreflect.FieldDescriptor. |
| // Two FieldDescriptor values compare equal if and only if they refer to the |
| // same protoreflect.FieldDescriptor. |
| // |
| // The primary use for FieldDescriptors is to access extension fields of a message. |
| // |
| // A FieldDescriptor value has not attributes. |
| // TODO(adonovan): expose metadata fields (e.g. name, type). |
| type FieldDescriptor struct { |
| Desc protoreflect.FieldDescriptor |
| } |
| |
| var ( |
| _ starlark.HasAttrs = FieldDescriptor{} |
| ) |
| |
| func (d FieldDescriptor) String() string { return string(d.Desc.FullName()) } |
| func (d FieldDescriptor) Type() string { return "proto.FieldDescriptor" } |
| func (d FieldDescriptor) Truth() starlark.Bool { return true } |
| func (d FieldDescriptor) Freeze() {} // immutable |
| func (d FieldDescriptor) Hash() (h uint32, err error) { |
| return starlark.String(d.Desc.FullName()).Hash() |
| } |
| func (d FieldDescriptor) Attr(name string) (starlark.Value, error) { |
| // TODO(adonovan): expose metadata fields of Desc? |
| return nil, nil |
| } |
| func (d FieldDescriptor) AttrNames() []string { |
| var names []string |
| // TODO(adonovan): expose metadata fields of Desc? |
| sort.Strings(names) |
| return names |
| } |
| |
| // An EnumDescriptor is an immutable Starlark value that describes an |
| // protocol enum type. |
| // |
| // An EnumDescriptor contains a reference to a protoreflect.EnumDescriptor. |
| // Two EnumDescriptor values compare equal if and only if they |
| // refer to the same protoreflect.EnumDescriptor. |
| // |
| // An EnumDescriptor may be called like a function. It converts its |
| // sole argument, which must be an int, string, or EnumValueDescriptor, |
| // to an EnumValueDescriptor. |
| // |
| // The fields of an EnumDescriptor value are the values of the |
| // enumeration, each of type EnumValueDescriptor. |
| type EnumDescriptor struct { |
| Desc protoreflect.EnumDescriptor |
| } |
| |
| var ( |
| _ starlark.HasAttrs = EnumDescriptor{} |
| _ starlark.Callable = EnumDescriptor{} |
| ) |
| |
| func (e EnumDescriptor) String() string { return string(e.Desc.FullName()) } |
| func (e EnumDescriptor) Type() string { return "proto.EnumDescriptor" } |
| func (e EnumDescriptor) Truth() starlark.Bool { return true } |
| func (e EnumDescriptor) Freeze() {} // immutable |
| func (e EnumDescriptor) Hash() (h uint32, err error) { return 0, nil } // TODO(adonovan): number? |
| func (e EnumDescriptor) Attr(name string) (starlark.Value, error) { |
| if v := e.Desc.Values().ByName(protoreflect.Name(name)); v != nil { |
| return EnumValueDescriptor{v}, nil |
| } |
| return nil, nil |
| } |
| func (e EnumDescriptor) AttrNames() []string { |
| var names []string |
| values := e.Desc.Values() |
| for i, n := 0, values.Len(); i < n; i++ { |
| names = append(names, string(values.Get(i).Name())) |
| } |
| sort.Strings(names) |
| return names |
| } |
| func (e EnumDescriptor) Name() string { return string(e.Desc.Name()) } // for Callable |
| |
| // The Call method implements the starlark.Callable interface. |
| // A call to an enum descriptor converts its argument to a value of that enum type. |
| func (e EnumDescriptor) CallInternal(_ *starlark.Thread, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { |
| var x starlark.Value |
| if err := starlark.UnpackPositionalArgs(string(e.Desc.Name()), args, kwargs, 1, &x); err != nil { |
| return nil, err |
| } |
| v, err := enumValueOf(e.Desc, x) |
| if err != nil { |
| return nil, fmt.Errorf("%s: %v", e.Desc.Name(), err) |
| } |
| return EnumValueDescriptor{Desc: v}, nil |
| } |
| |
| // enumValueOf converts an int, string, or enum value to a value of the specified enum type. |
| func enumValueOf(enum protoreflect.EnumDescriptor, x starlark.Value) (protoreflect.EnumValueDescriptor, error) { |
| switch x := x.(type) { |
| case starlark.Int: |
| i, err := starlark.AsInt32(x) |
| if err != nil { |
| return nil, fmt.Errorf("invalid number %s for %s enum", x, enum.Name()) |
| } |
| desc := enum.Values().ByNumber(protoreflect.EnumNumber(i)) |
| if desc == nil { |
| return nil, fmt.Errorf("invalid number %d for %s enum", i, enum.Name()) |
| } |
| return desc, nil |
| |
| case starlark.String: |
| name := protoreflect.Name(x) |
| desc := enum.Values().ByName(name) |
| if desc == nil { |
| return nil, fmt.Errorf("invalid name %q for %s enum", name, enum.Name()) |
| } |
| return desc, nil |
| |
| case EnumValueDescriptor: |
| if parent := x.Desc.Parent(); parent != enum { |
| return nil, fmt.Errorf("invalid value %s.%s for %s enum", |
| parent.Name(), x.Desc.Name(), enum.Name()) |
| } |
| return x.Desc, nil |
| } |
| |
| return nil, fmt.Errorf("cannot convert %s to %s enum", x.Type(), enum.Name()) |
| } |
| |
| // An EnumValueDescriptor is an immutable Starlark value that represents one value of an enumeration. |
| // |
| // An EnumValueDescriptor contains a reference to a protoreflect.EnumValueDescriptor. |
| // Two EnumValueDescriptor values compare equal if and only if they |
| // refer to the same protoreflect.EnumValueDescriptor. |
| // |
| // An EnumValueDescriptor has the following fields: |
| // |
| // index -- int, index of this value within the enum sequence |
| // name -- string, name of this enum value |
| // number -- int, numeric value of this enum value |
| // type -- EnumDescriptor, the enum type to which this value belongs |
| // |
| type EnumValueDescriptor struct { |
| Desc protoreflect.EnumValueDescriptor |
| } |
| |
| var ( |
| _ starlark.HasAttrs = EnumValueDescriptor{} |
| _ starlark.Comparable = EnumValueDescriptor{} |
| ) |
| |
| func (e EnumValueDescriptor) String() string { |
| enum := e.Desc.Parent() |
| return string(enum.Name() + "." + e.Desc.Name()) // "Enum.EnumValue" |
| } |
| func (e EnumValueDescriptor) Type() string { return "proto.EnumValueDescriptor" } |
| func (e EnumValueDescriptor) Truth() starlark.Bool { return true } |
| func (e EnumValueDescriptor) Freeze() {} // immutable |
| func (e EnumValueDescriptor) Hash() (h uint32, err error) { return uint32(e.Desc.Number()), nil } |
| func (e EnumValueDescriptor) AttrNames() []string { |
| return []string{"index", "name", "number", "type"} |
| } |
| func (e EnumValueDescriptor) Attr(name string) (starlark.Value, error) { |
| switch name { |
| case "index": |
| return starlark.MakeInt(e.Desc.Index()), nil |
| case "name": |
| return starlark.String(e.Desc.Name()), nil |
| case "number": |
| return starlark.MakeInt(int(e.Desc.Number())), nil |
| case "type": |
| enum := e.Desc.Parent() |
| return EnumDescriptor{Desc: enum.(protoreflect.EnumDescriptor)}, nil |
| } |
| return nil, nil |
| } |
| func (x EnumValueDescriptor) CompareSameType(op syntax.Token, y_ starlark.Value, depth int) (bool, error) { |
| y := y_.(EnumValueDescriptor) |
| switch op { |
| case syntax.EQL: |
| return x.Desc == y.Desc, nil |
| case syntax.NEQ: |
| return x.Desc != y.Desc, nil |
| default: |
| return false, fmt.Errorf("%s %s %s not implemented", x.Type(), op, y_.Type()) |
| } |
| } |