1package starlark 2 3// This file defines the Unpack helper functions used by 4// built-in functions to interpret their call arguments. 5 6import ( 7 "fmt" 8 "log" 9 "reflect" 10 "strings" 11) 12 13// An Unpacker defines custom argument unpacking behavior. 14// See UnpackArgs. 15type Unpacker interface { 16 Unpack(v Value) error 17} 18 19// UnpackArgs unpacks the positional and keyword arguments into the 20// supplied parameter variables. pairs is an alternating list of names 21// and pointers to variables. 22// 23// If the variable is a bool, integer, string, *List, *Dict, Callable, 24// Iterable, or user-defined implementation of Value, 25// UnpackArgs performs the appropriate type check. 26// Predeclared Go integer types uses the AsInt check. 27// If the parameter name ends with "?", 28// it and all following parameters are optional. 29// 30// If the variable implements Unpacker, its Unpack argument 31// is called with the argument value, allowing an application 32// to define its own argument validation and conversion. 33// 34// If the variable implements Value, UnpackArgs may call 35// its Type() method while constructing the error message. 36// 37// Examples: 38// 39// var ( 40// a Value 41// b = MakeInt(42) 42// c Value = starlark.None 43// ) 44// 45// // 1. mixed parameters, like def f(a, b=42, c=None). 46// err := UnpackArgs("f", args, kwargs, "a", &a, "b?", &b, "c?", &c) 47// 48// // 2. keyword parameters only, like def f(*, a, b, c=None). 49// if len(args) > 0 { 50// return fmt.Errorf("f: unexpected positional arguments") 51// } 52// err := UnpackArgs("f", args, kwargs, "a", &a, "b?", &b, "c?", &c) 53// 54// // 3. positional parameters only, like def f(a, b=42, c=None, /) in Python 3.8. 55// err := UnpackPositionalArgs("f", args, kwargs, 1, &a, &b, &c) 56// 57// More complex forms such as def f(a, b=42, *args, c, d=123, **kwargs) 58// require additional logic, but their need in built-ins is exceedingly rare. 59// 60// In the examples above, the declaration of b with type Int causes UnpackArgs 61// to require that b's argument value, if provided, is also an int. 62// To allow arguments of any type, while retaining the default value of 42, 63// declare b as a Value: 64// 65// var b Value = MakeInt(42) 66// 67// The zero value of a variable of type Value, such as 'a' in the 68// examples above, is not a valid Starlark value, so if the parameter is 69// optional, the caller must explicitly handle the default case by 70// interpreting nil as None or some computed default. The same is true 71// for the zero values of variables of type *List, *Dict, Callable, or 72// Iterable. For example: 73// 74// // def myfunc(d=None, e=[], f={}) 75// var ( 76// d Value 77// e *List 78// f *Dict 79// ) 80// err := UnpackArgs("myfunc", args, kwargs, "d?", &d, "e?", &e, "f?", &f) 81// if d == nil { d = None; } 82// if e == nil { e = new(List); } 83// if f == nil { f = new(Dict); } 84// 85func UnpackArgs(fnname string, args Tuple, kwargs []Tuple, pairs ...interface{}) error { 86 nparams := len(pairs) / 2 87 var defined intset 88 defined.init(nparams) 89 90 paramName := func(x interface{}) string { // (no free variables) 91 name := x.(string) 92 if name[len(name)-1] == '?' { 93 name = name[:len(name)-1] 94 } 95 return name 96 } 97 98 // positional arguments 99 if len(args) > nparams { 100 return fmt.Errorf("%s: got %d arguments, want at most %d", 101 fnname, len(args), nparams) 102 } 103 for i, arg := range args { 104 defined.set(i) 105 if err := unpackOneArg(arg, pairs[2*i+1]); err != nil { 106 name := paramName(pairs[2*i]) 107 return fmt.Errorf("%s: for parameter %s: %s", fnname, name, err) 108 } 109 } 110 111 // keyword arguments 112kwloop: 113 for _, item := range kwargs { 114 name, arg := item[0].(String), item[1] 115 for i := 0; i < nparams; i++ { 116 if paramName(pairs[2*i]) == string(name) { 117 // found it 118 if defined.set(i) { 119 return fmt.Errorf("%s: got multiple values for keyword argument %s", 120 fnname, name) 121 } 122 ptr := pairs[2*i+1] 123 if err := unpackOneArg(arg, ptr); err != nil { 124 return fmt.Errorf("%s: for parameter %s: %s", fnname, name, err) 125 } 126 continue kwloop 127 } 128 } 129 return fmt.Errorf("%s: unexpected keyword argument %s", fnname, name) 130 } 131 132 // Check that all non-optional parameters are defined. 133 // (We needn't check the first len(args).) 134 for i := len(args); i < nparams; i++ { 135 name := pairs[2*i].(string) 136 if strings.HasSuffix(name, "?") { 137 break // optional 138 } 139 if !defined.get(i) { 140 return fmt.Errorf("%s: missing argument for %s", fnname, name) 141 } 142 } 143 144 return nil 145} 146 147// UnpackPositionalArgs unpacks the positional arguments into 148// corresponding variables. Each element of vars is a pointer; see 149// UnpackArgs for allowed types and conversions. 150// 151// UnpackPositionalArgs reports an error if the number of arguments is 152// less than min or greater than len(vars), if kwargs is nonempty, or if 153// any conversion fails. 154// 155// See UnpackArgs for general comments. 156func UnpackPositionalArgs(fnname string, args Tuple, kwargs []Tuple, min int, vars ...interface{}) error { 157 if len(kwargs) > 0 { 158 return fmt.Errorf("%s: unexpected keyword arguments", fnname) 159 } 160 max := len(vars) 161 if len(args) < min { 162 var atleast string 163 if min < max { 164 atleast = "at least " 165 } 166 return fmt.Errorf("%s: got %d arguments, want %s%d", fnname, len(args), atleast, min) 167 } 168 if len(args) > max { 169 var atmost string 170 if max > min { 171 atmost = "at most " 172 } 173 return fmt.Errorf("%s: got %d arguments, want %s%d", fnname, len(args), atmost, max) 174 } 175 for i, arg := range args { 176 if err := unpackOneArg(arg, vars[i]); err != nil { 177 return fmt.Errorf("%s: for parameter %d: %s", fnname, i+1, err) 178 } 179 } 180 return nil 181} 182 183func unpackOneArg(v Value, ptr interface{}) error { 184 // On failure, don't clobber *ptr. 185 switch ptr := ptr.(type) { 186 case Unpacker: 187 return ptr.Unpack(v) 188 case *Value: 189 *ptr = v 190 case *string: 191 s, ok := AsString(v) 192 if !ok { 193 return fmt.Errorf("got %s, want string", v.Type()) 194 } 195 *ptr = s 196 case *bool: 197 b, ok := v.(Bool) 198 if !ok { 199 return fmt.Errorf("got %s, want bool", v.Type()) 200 } 201 *ptr = bool(b) 202 case *int, *int8, *int16, *int32, *int64, 203 *uint, *uint8, *uint16, *uint32, *uint64, *uintptr: 204 return AsInt(v, ptr) 205 case *float64: 206 f, ok := v.(Float) 207 if !ok { 208 return fmt.Errorf("got %s, want float", v.Type()) 209 } 210 *ptr = float64(f) 211 case **List: 212 list, ok := v.(*List) 213 if !ok { 214 return fmt.Errorf("got %s, want list", v.Type()) 215 } 216 *ptr = list 217 case **Dict: 218 dict, ok := v.(*Dict) 219 if !ok { 220 return fmt.Errorf("got %s, want dict", v.Type()) 221 } 222 *ptr = dict 223 case *Callable: 224 f, ok := v.(Callable) 225 if !ok { 226 return fmt.Errorf("got %s, want callable", v.Type()) 227 } 228 *ptr = f 229 case *Iterable: 230 it, ok := v.(Iterable) 231 if !ok { 232 return fmt.Errorf("got %s, want iterable", v.Type()) 233 } 234 *ptr = it 235 default: 236 // v must have type *V, where V is some subtype of starlark.Value. 237 ptrv := reflect.ValueOf(ptr) 238 if ptrv.Kind() != reflect.Ptr { 239 log.Panicf("internal error: not a pointer: %T", ptr) 240 } 241 paramVar := ptrv.Elem() 242 if !reflect.TypeOf(v).AssignableTo(paramVar.Type()) { 243 // The value is not assignable to the variable. 244 245 // Detect a possible bug in the Go program that called Unpack: 246 // If the variable *ptr is not a subtype of Value, 247 // no value of v can possibly work. 248 if !paramVar.Type().AssignableTo(reflect.TypeOf(new(Value)).Elem()) { 249 log.Panicf("pointer element type does not implement Value: %T", ptr) 250 } 251 252 // Report Starlark dynamic type error. 253 // 254 // We prefer the Starlark Value.Type name over 255 // its Go reflect.Type name, but calling the 256 // Value.Type method on the variable is not safe 257 // in general. If the variable is an interface, 258 // the call will fail. Even if the variable has 259 // a concrete type, it might not be safe to call 260 // Type() on a zero instance. Thus we must use 261 // recover. 262 263 // Default to Go reflect.Type name 264 paramType := paramVar.Type().String() 265 266 // Attempt to call Value.Type method. 267 func() { 268 defer func() { recover() }() 269 paramType = paramVar.MethodByName("Type").Call(nil)[0].String() 270 }() 271 return fmt.Errorf("got %s, want %s", v.Type(), paramType) 272 } 273 paramVar.Set(reflect.ValueOf(v)) 274 } 275 return nil 276} 277 278type intset struct { 279 small uint64 // bitset, used if n < 64 280 large map[int]bool // set, used if n >= 64 281} 282 283func (is *intset) init(n int) { 284 if n >= 64 { 285 is.large = make(map[int]bool) 286 } 287} 288 289func (is *intset) set(i int) (prev bool) { 290 if is.large == nil { 291 prev = is.small&(1<<uint(i)) != 0 292 is.small |= 1 << uint(i) 293 } else { 294 prev = is.large[i] 295 is.large[i] = true 296 } 297 return 298} 299 300func (is *intset) get(i int) bool { 301 if is.large == nil { 302 return is.small&(1<<uint(i)) != 0 303 } 304 return is.large[i] 305} 306 307func (is *intset) len() int { 308 if is.large == nil { 309 // Suboptimal, but used only for error reporting. 310 len := 0 311 for i := 0; i < 64; i++ { 312 if is.small&(1<<uint(i)) != 0 { 313 len++ 314 } 315 } 316 return len 317 } 318 return len(is.large) 319} 320