go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/starlark/starlarkproto/values_singular.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  	"math"
    20  
    21  	"go.starlark.net/starlark"
    22  
    23  	"google.golang.org/protobuf/reflect/protoreflect"
    24  
    25  	"go.chromium.org/luci/starlark/typed"
    26  )
    27  
    28  // toStarlarkSingular converts 'v' to starlark, based on type in 'fd'.
    29  //
    30  // This is Proto => Starlark converter. Ignores 'repeated' qualifier.
    31  //
    32  // Panics if type of 'v' doesn't match 'fd'.
    33  func toStarlarkSingular(l *Loader, fd protoreflect.FieldDescriptor, v protoreflect.Value) starlark.Value {
    34  	// See https://godoc.org/google.golang.org/protobuf/reflect/protoreflect#Kind
    35  	// Also https://developers.google.com/protocol-buffers/docs/proto#scalar
    36  
    37  	switch fd.Kind() {
    38  	case protoreflect.BoolKind:
    39  		return starlark.Bool(v.Bool())
    40  
    41  	case protoreflect.EnumKind:
    42  		return starlark.MakeInt(int(v.Enum()))
    43  
    44  	case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind:
    45  		return starlark.MakeInt64(v.Int())
    46  
    47  	case protoreflect.Uint32Kind, protoreflect.Fixed32Kind:
    48  		return starlark.MakeUint64(v.Uint())
    49  
    50  	case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind:
    51  		return starlark.MakeInt64(v.Int())
    52  
    53  	case protoreflect.Uint64Kind, protoreflect.Fixed64Kind:
    54  		return starlark.MakeUint64(v.Uint())
    55  
    56  	case protoreflect.FloatKind, protoreflect.DoubleKind:
    57  		return starlark.Float(v.Float())
    58  
    59  	case protoreflect.StringKind:
    60  		return starlark.String(v.String())
    61  
    62  	case protoreflect.BytesKind:
    63  		return starlark.String(v.Bytes())
    64  
    65  	case protoreflect.MessageKind, protoreflect.GroupKind:
    66  		typ := l.MessageType(fd.Message())
    67  		if v.IsValid() {
    68  			return typ.MessageFromProto(v.Message().Interface())
    69  		}
    70  		return typ.Message()
    71  
    72  	default:
    73  		panic(fmt.Errorf("internal error: unexpected field kind %s", fd.Kind()))
    74  	}
    75  }
    76  
    77  // toProtoSingular converts 'v' to a protobuf value described by 'fd'.
    78  //
    79  // This is Starlark => Proto converter.
    80  //
    81  // Assumes type checks have been done already (this is responsibility of
    82  // 'converter' function, see below).
    83  //
    84  // Panics on type mismatch.
    85  func toProtoSingular(fd protoreflect.FieldDescriptor, v starlark.Value) protoreflect.Value {
    86  	// See https://godoc.org/google.golang.org/protobuf/reflect/protoreflect#Kind
    87  	// Also https://developers.google.com/protocol-buffers/docs/proto#scalar
    88  	switch kind := fd.Kind(); kind {
    89  	case protoreflect.BoolKind:
    90  		return protoreflect.ValueOf(bool(v.(starlark.Bool)))
    91  
    92  	case protoreflect.EnumKind:
    93  		return protoreflect.ValueOf(protoreflect.EnumNumber(starlarkToInt(v, kind, math.MinInt32, math.MaxInt32)))
    94  
    95  	case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind:
    96  		return protoreflect.ValueOf(int32(starlarkToInt(v, kind, math.MinInt32, math.MaxInt32)))
    97  
    98  	case protoreflect.Uint32Kind, protoreflect.Fixed32Kind:
    99  		return protoreflect.ValueOf(uint32(starlarkToUint(v, kind, math.MaxUint32)))
   100  
   101  	case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind:
   102  		return protoreflect.ValueOf(int64(starlarkToInt(v, kind, math.MinInt64, math.MaxInt64)))
   103  
   104  	case protoreflect.Uint64Kind, protoreflect.Fixed64Kind:
   105  		return protoreflect.ValueOf(uint64(starlarkToUint(v, kind, math.MaxUint64)))
   106  
   107  	case protoreflect.FloatKind:
   108  		return protoreflect.ValueOf(float32(v.(starlark.Float)))
   109  
   110  	case protoreflect.DoubleKind:
   111  		return protoreflect.ValueOf(float64(v.(starlark.Float)))
   112  
   113  	case protoreflect.StringKind:
   114  		return protoreflect.ValueOf(v.(starlark.String).GoString())
   115  
   116  	case protoreflect.BytesKind:
   117  		return protoreflect.ValueOf([]byte(v.(starlark.String).GoString()))
   118  
   119  	case protoreflect.MessageKind, protoreflect.GroupKind:
   120  		return protoreflect.ValueOf(v.(*Message).ToProto())
   121  
   122  	default:
   123  		panic(fmt.Errorf("internal error: unexpected field kind %s", fd.Kind()))
   124  	}
   125  }
   126  
   127  // Extracts int64 from 'v' checking its range. Panics on mismatches.
   128  func starlarkToInt(v starlark.Value, k protoreflect.Kind, min, max int64) int64 {
   129  	si, ok := v.(starlark.Int)
   130  	if !ok {
   131  		panic(fmt.Errorf("internal error: got %s, expect %s", v.Type(), k))
   132  	}
   133  	i, ok := si.Int64()
   134  	if !ok || i < min || i > max {
   135  		panic(fmt.Errorf("internal error: %s doesn't fit into %s field", v, k))
   136  	}
   137  	return i
   138  }
   139  
   140  // Extract uint64 from 'v' checking its range. Panics on mismatches.
   141  func starlarkToUint(v starlark.Value, k protoreflect.Kind, max uint64) uint64 {
   142  	si, ok := v.(starlark.Int)
   143  	if !ok {
   144  		panic(fmt.Errorf("internal error: got %s, expect %s", v.Type(), k))
   145  	}
   146  	i, ok := si.Uint64()
   147  	if !ok || i > max {
   148  		panic(fmt.Errorf("internal error: %s doesn't fit into %s field", v, k))
   149  	}
   150  	return i
   151  }
   152  
   153  // converter returns typed.Converter that converts arbitrary Starlark values to
   154  // another Starlark values of a scalar (i.e. non-repeated, non-dict) type
   155  // described by the descriptor, if such conversion is allowed.
   156  //
   157  // This is Starlark => Starlark converter. Such converter is involved when
   158  // executing following Starlark statements:
   159  //
   160  //	msg.scalar = <some starlark value>
   161  //	msg.repeated[123] = <some starlark value>
   162  //	msg.dict[<some starlark value>] = <some starlark value>
   163  //
   164  // It ensures that *Message fields at all times conform to the proto message
   165  // schema.
   166  //
   167  // Some notable conversion rules:
   168  //   - converter([u]int(32|64)) checks int fits within the corresponding range.
   169  //   - converter(float[32|64]) implicitly converts ints to floats.
   170  //   - converter(Message) implicitly converts dicts and Nones to messages.
   171  //
   172  // The following invariant holds (and relied upon by 'assign'): for all possible
   173  // 'fd' and all possible 'x' the following doesn't panic:
   174  //
   175  //	v, err := converter(l, fd).Convert(x)
   176  //	if err != nil {
   177  //	  return err // type of 'x' is incompatible with 'fd'
   178  //	}
   179  //	p := toProtoSingular(fd, v) // compatible values can be converted to proto
   180  func converter(l *Loader, fd protoreflect.FieldDescriptor) typed.Converter {
   181  	// Note: primitive type converters need "stable" addresses, since converters
   182  	// are compared by identity when checking type compatibility. So we use
   183  	// globals for them. For Message types, "stable" addresses are guaranteed by
   184  	// the loader, which caches types internally.
   185  	switch fd.Kind() {
   186  	case protoreflect.BoolKind:
   187  		return &boolConverter
   188  
   189  	case protoreflect.EnumKind, protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind:
   190  		return &int32Converter
   191  
   192  	case protoreflect.Uint32Kind, protoreflect.Fixed32Kind:
   193  		return &uint32Converter
   194  
   195  	case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind:
   196  		return &int64Converter
   197  
   198  	case protoreflect.Uint64Kind, protoreflect.Fixed64Kind:
   199  		return &uint64Converter
   200  
   201  	case protoreflect.FloatKind, protoreflect.DoubleKind:
   202  		return &floatConverter
   203  
   204  	case protoreflect.StringKind, protoreflect.BytesKind:
   205  		return &stringConverter
   206  
   207  	case protoreflect.MessageKind, protoreflect.GroupKind:
   208  		return l.MessageType(fd.Message()).Converter()
   209  
   210  	default:
   211  		panic(fmt.Errorf("internal error: unexpected field kind %s", fd.Kind()))
   212  	}
   213  }
   214  
   215  // Converters into built-in types.
   216  
   217  var (
   218  	boolConverter = simpleConverter{
   219  		tp: "bool",
   220  		cb: func(x starlark.Value) (starlark.Value, error) {
   221  			if _, ok := x.(starlark.Bool); ok {
   222  				return x, nil
   223  			}
   224  			return nil, fmt.Errorf("got %s, want bool", x.Type())
   225  		},
   226  	}
   227  
   228  	floatConverter = simpleConverter{
   229  		tp: "float",
   230  		cb: func(x starlark.Value) (starlark.Value, error) {
   231  			if _, ok := x.(starlark.Float); ok {
   232  				return x, nil
   233  			}
   234  			if i, ok := x.(starlark.Int); ok {
   235  				return i.Float(), nil
   236  			}
   237  			return nil, fmt.Errorf("got %s, want float", x.Type())
   238  		},
   239  	}
   240  
   241  	stringConverter = simpleConverter{
   242  		tp: "string",
   243  		cb: func(x starlark.Value) (starlark.Value, error) {
   244  			if _, ok := x.(starlark.String); ok {
   245  				return x, nil
   246  			}
   247  			return nil, fmt.Errorf("got %s, want string", x.Type())
   248  		},
   249  	}
   250  
   251  	int32Converter = intConverter{
   252  		tp:  "int32",
   253  		min: math.MinInt32,
   254  		max: math.MaxInt32,
   255  	}
   256  
   257  	uint32Converter = uintConverter{
   258  		tp:  "uint32",
   259  		max: math.MaxUint32,
   260  	}
   261  
   262  	int64Converter = intConverter{
   263  		tp:  "int64",
   264  		min: math.MinInt64,
   265  		max: math.MaxInt64,
   266  	}
   267  
   268  	uint64Converter = uintConverter{
   269  		tp:  "uint64",
   270  		max: math.MaxUint64,
   271  	}
   272  )
   273  
   274  type simpleConverter struct {
   275  	tp string
   276  	cb func(x starlark.Value) (starlark.Value, error)
   277  }
   278  
   279  func (c *simpleConverter) Type() string                                     { return c.tp }
   280  func (c *simpleConverter) Convert(x starlark.Value) (starlark.Value, error) { return c.cb(x) }
   281  
   282  type intConverter struct {
   283  	tp  string
   284  	min int64
   285  	max int64
   286  }
   287  
   288  func (c *intConverter) Type() string { return c.tp }
   289  
   290  func (c *intConverter) Convert(x starlark.Value) (starlark.Value, error) {
   291  	si, ok := x.(starlark.Int)
   292  	if !ok {
   293  		return nil, fmt.Errorf("got %s, want int", x.Type())
   294  	}
   295  	if i, ok := si.Int64(); !ok || i < c.min || i > c.max {
   296  		return nil, fmt.Errorf("%s doesn't fit into %s", x, c.tp)
   297  	}
   298  	return si, nil
   299  }
   300  
   301  type uintConverter struct {
   302  	tp  string
   303  	max uint64
   304  }
   305  
   306  func (c *uintConverter) Type() string { return c.tp }
   307  
   308  func (c *uintConverter) Convert(x starlark.Value) (starlark.Value, error) {
   309  	si, ok := x.(starlark.Int)
   310  	if !ok {
   311  		return nil, fmt.Errorf("got %s, want int", x.Type())
   312  	}
   313  	if i, ok := si.Uint64(); !ok || i > c.max {
   314  		return nil, fmt.Errorf("%s doesn't fit into %s", x, c.tp)
   315  	}
   316  	return si, nil
   317  }