blob: f6e243975df3b2f742226f084ed43f523937c546 [file] [log] [blame]
// Copyright 2019 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package starlarkproto
import (
"fmt"
"sort"
"go.starlark.net/starlark"
"go.starlark.net/syntax"
"google.golang.org/protobuf/reflect/protoreflect"
"go.chromium.org/luci/starlark/typed"
)
// newStarlarkDict returns a new typed.Dict of an appropriate type.
func newStarlarkDict(l *Loader, fd protoreflect.FieldDescriptor, capacity int) *typed.Dict {
return typed.NewDict(
converter(l, fd.MapKey()),
converter(l, fd.MapValue()),
capacity)
}
// toStarlarkDict does protoreflect.Map => typed.Dict conversion.
//
// Panics if type of 'm' (or some of its items) doesn't match 'fd'.
func toStarlarkDict(l *Loader, fd protoreflect.FieldDescriptor, m protoreflect.Map) *typed.Dict {
keyFD := fd.MapKey()
valFD := fd.MapValue()
// A key of a protoreflect.Map, together with its Starlark copy.
type key struct {
pv protoreflect.MapKey // as proto value
sv starlark.Value // as starlark value
}
// Protobuf maps are unordered, but Starlark dicts retain order, so collect
// keys first to sort them before adding to a dict.
keys := make([]key, 0, m.Len())
m.Range(func(k protoreflect.MapKey, _ protoreflect.Value) bool {
keys = append(keys, key{k, toStarlarkSingular(l, keyFD, k.Value())})
return true
})
// Sort keys as Starlark values. Proto map keys are (u)int(32|64) or bools,
// they *must* be sortable, so panic on errors.
sort.Slice(keys, func(i, j int) bool {
lt, err := starlark.Compare(syntax.LT, keys[i].sv, keys[j].sv)
if err != nil {
panic(fmt.Errorf("internal error: when sorting dict keys: %s", err))
}
return lt
})
// Construct typed.Dict from sorted keys and values from the proto map.
d := newStarlarkDict(l, fd, len(keys))
for _, k := range keys {
sv := toStarlarkSingular(l, valFD, m.Get(k.pv))
if err := d.SetKey(k.sv, sv); err != nil {
panic(fmt.Errorf("internal error: value of key %s: %s", k.pv, err))
}
}
return d
}
// assignProtoMap does m.fd = iter, where fd is a map.
//
// Assumes type checks have been done already (this is responsibility of
// prepareRHS). Panics on type mismatch.
func assignProtoMap(m protoreflect.Message, fd protoreflect.FieldDescriptor, dict *typed.Dict) {
mp := m.Mutable(fd).Map()
if mp.Len() != 0 {
panic(fmt.Errorf("internal error: the proto map is not empty"))
}
keyFD := fd.MapKey()
valFD := fd.MapValue()
it := dict.Iterate()
defer it.Done()
var sk starlark.Value
for it.Next(&sk) {
switch sv, ok, err := dict.Get(sk); {
case err != nil:
panic(fmt.Errorf("internal error: key %s: %s", sk, err))
case !ok:
panic(fmt.Errorf("internal error: key %s: suddenly gone while iterating", sk))
default:
mp.Set(toProtoSingular(keyFD, sk).MapKey(), toProtoSingular(valFD, sv))
}
}
}