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