go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/starlark/starlarkproto/values.go (about) 1 // Copyright 2019 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package starlarkproto 16 17 import ( 18 "fmt" 19 20 "go.starlark.net/starlark" 21 22 "google.golang.org/protobuf/reflect/protoreflect" 23 24 "go.chromium.org/luci/starlark/typed" 25 ) 26 27 // toStarlark converts a protobuf value (whose kind is described by the given 28 // descriptor) to a Starlark value, using the loader to instantiate messages. 29 // 30 // Type of 'v' should match 'fd' (panics otherwise). This is always the case 31 // because we extract both 'v' and 'fd' from the same protoreflect message. 32 func toStarlark(l *Loader, fd protoreflect.FieldDescriptor, v protoreflect.Value) starlark.Value { 33 switch { 34 case fd.IsList(): 35 if !v.IsValid() { // unset repeated field => empty list<T> 36 return newStarlarkList(l, fd) 37 } 38 return toStarlarkList(l, fd, v.List()) 39 40 case fd.IsMap(): 41 if !v.IsValid() { // unset map field => empty dict<K,V> 42 return newStarlarkDict(l, fd, 0) 43 } 44 return toStarlarkDict(l, fd, v.Map()) 45 46 default: 47 return toStarlarkSingular(l, fd, v) 48 } 49 } 50 51 // prepareRHS examines right hand side of some assignment `msg.fd = v`. 52 // 53 // It checks whether 'v' can be assigned to a field described by 'fd', and 54 // converts it to necessary type (using 'converter(fd)') if required. 55 // 56 // On success returns a value of a type matching 'fd'. It can either be 'v' as 57 // is, or a type-converter copy of 'v'. 58 // 59 // See doc.go for the conversion rules. 60 func prepareRHS(l *Loader, fd protoreflect.FieldDescriptor, v starlark.Value) (starlark.Value, error) { 61 switch { 62 case fd.IsList(): 63 // Reuse list<T> by reference if it has matching type. Reject it right away 64 // if it is some other list<T'>, T'!=T. Otherwise make a copy. 65 myT := converter(l, fd) 66 if tpd, ok := v.(*typed.List); ok { 67 if tpd.Converter() == myT { 68 return v, nil // exact same type, reuse by reference 69 } 70 return nil, fmt.Errorf("want list<%s> or just list", myT.Type()) 71 } 72 v, err := typed.AsTypedList(myT, v) // make a copy 73 if err != nil { 74 return nil, fmt.Errorf("when constructing list<%s>: %s", myT.Type(), err) 75 } 76 return v, nil 77 78 case fd.IsMap(): 79 // Reuse dict<K,V> by reference if it has matching type. Reject it right 80 // away if it is some other dict<K',V'>, K'!=K || V'!=V. Otherwise make 81 // a copy. 82 myK := converter(l, fd.MapKey()) 83 myV := converter(l, fd.MapValue()) 84 if tpd, ok := v.(*typed.Dict); ok { 85 if tpd.KeyConverter() == myK && tpd.ValueConverter() == myV { 86 return v, nil // exact same type, reuse by reference 87 } 88 return nil, fmt.Errorf("want dict<%s,%s> or just dict", myK.Type(), myV.Type()) 89 } 90 v, err := typed.AsTypedDict(myK, myV, v) // make a copy 91 if err != nil { 92 return nil, fmt.Errorf("when constructing dict<%s,%s>: %s", myK.Type(), myV.Type(), err) 93 } 94 return v, nil 95 96 default: 97 // Attempt to type-cast 'v' to the necessary type. In particular this 98 // converts dict to messages, checks int ranges, etc. 99 return converter(l, fd).Convert(v) 100 } 101 } 102 103 // assign does 'm.fd = v'. 104 // 105 // Here 'v' is a result of some prepareRHS(.., fd, ...) or toStarlark(fd, ...) 106 // call. Panics if type of 'v' doesn't match 'fd'. This should not be possible. 107 // 108 // m.fd is assumed to be in the initial state (i.e. if it is a list or a map, 109 // they are empty). Panics otherwise. 110 func assign(m protoreflect.Message, fd protoreflect.FieldDescriptor, v starlark.Value) { 111 switch { 112 case fd.IsList(): 113 assignProtoList(m, fd, v.(*typed.List)) 114 case fd.IsMap(): 115 assignProtoMap(m, fd, v.(*typed.Dict)) 116 default: 117 m.Set(fd, toProtoSingular(fd, v)) 118 } 119 }