package starlark // This file defines the Unpack helper functions used by // built-in functions to interpret their call arguments. import ( "fmt" "log" "reflect" "strings" ) // An Unpacker defines custom argument unpacking behavior. // See UnpackArgs. type Unpacker interface { Unpack(v Value) error } // UnpackArgs unpacks the positional and keyword arguments into the // supplied parameter variables. pairs is an alternating list of names // and pointers to variables. // // If the variable is a bool, integer, string, *List, *Dict, Callable, // Iterable, or user-defined implementation of Value, // UnpackArgs performs the appropriate type check. // Predeclared Go integer types uses the AsInt check. // If the parameter name ends with "?", // it and all following parameters are optional. // // If the variable implements Unpacker, its Unpack argument // is called with the argument value, allowing an application // to define its own argument validation and conversion. // // If the variable implements Value, UnpackArgs may call // its Type() method while constructing the error message. // // Examples: // // var ( // a Value // b = MakeInt(42) // c Value = starlark.None // ) // // // 1. mixed parameters, like def f(a, b=42, c=None). // err := UnpackArgs("f", args, kwargs, "a", &a, "b?", &b, "c?", &c) // // // 2. keyword parameters only, like def f(*, a, b, c=None). // if len(args) > 0 { // return fmt.Errorf("f: unexpected positional arguments") // } // err := UnpackArgs("f", args, kwargs, "a", &a, "b?", &b, "c?", &c) // // // 3. positional parameters only, like def f(a, b=42, c=None, /) in Python 3.8. // err := UnpackPositionalArgs("f", args, kwargs, 1, &a, &b, &c) // // More complex forms such as def f(a, b=42, *args, c, d=123, **kwargs) // require additional logic, but their need in built-ins is exceedingly rare. // // In the examples above, the declaration of b with type Int causes UnpackArgs // to require that b's argument value, if provided, is also an int. // To allow arguments of any type, while retaining the default value of 42, // declare b as a Value: // // var b Value = MakeInt(42) // // The zero value of a variable of type Value, such as 'a' in the // examples above, is not a valid Starlark value, so if the parameter is // optional, the caller must explicitly handle the default case by // interpreting nil as None or some computed default. The same is true // for the zero values of variables of type *List, *Dict, Callable, or // Iterable. For example: // // // def myfunc(d=None, e=[], f={}) // var ( // d Value // e *List // f *Dict // ) // err := UnpackArgs("myfunc", args, kwargs, "d?", &d, "e?", &e, "f?", &f) // if d == nil { d = None; } // if e == nil { e = new(List); } // if f == nil { f = new(Dict); } // func UnpackArgs(fnname string, args Tuple, kwargs []Tuple, pairs ...interface{}) error { nparams := len(pairs) / 2 var defined intset defined.init(nparams) paramName := func(x interface{}) string { // (no free variables) name := x.(string) if name[len(name)-1] == '?' { name = name[:len(name)-1] } return name } // positional arguments if len(args) > nparams { return fmt.Errorf("%s: got %d arguments, want at most %d", fnname, len(args), nparams) } for i, arg := range args { defined.set(i) if err := unpackOneArg(arg, pairs[2*i+1]); err != nil { name := paramName(pairs[2*i]) return fmt.Errorf("%s: for parameter %s: %s", fnname, name, err) } } // keyword arguments kwloop: for _, item := range kwargs { name, arg := item[0].(String), item[1] for i := 0; i < nparams; i++ { if paramName(pairs[2*i]) == string(name) { // found it if defined.set(i) { return fmt.Errorf("%s: got multiple values for keyword argument %s", fnname, name) } ptr := pairs[2*i+1] if err := unpackOneArg(arg, ptr); err != nil { return fmt.Errorf("%s: for parameter %s: %s", fnname, name, err) } continue kwloop } } return fmt.Errorf("%s: unexpected keyword argument %s", fnname, name) } // Check that all non-optional parameters are defined. // (We needn't check the first len(args).) for i := len(args); i < nparams; i++ { name := pairs[2*i].(string) if strings.HasSuffix(name, "?") { break // optional } if !defined.get(i) { return fmt.Errorf("%s: missing argument for %s", fnname, name) } } return nil } // UnpackPositionalArgs unpacks the positional arguments into // corresponding variables. Each element of vars is a pointer; see // UnpackArgs for allowed types and conversions. // // UnpackPositionalArgs reports an error if the number of arguments is // less than min or greater than len(vars), if kwargs is nonempty, or if // any conversion fails. // // See UnpackArgs for general comments. func UnpackPositionalArgs(fnname string, args Tuple, kwargs []Tuple, min int, vars ...interface{}) error { if len(kwargs) > 0 { return fmt.Errorf("%s: unexpected keyword arguments", fnname) } max := len(vars) if len(args) < min { var atleast string if min < max { atleast = "at least " } return fmt.Errorf("%s: got %d arguments, want %s%d", fnname, len(args), atleast, min) } if len(args) > max { var atmost string if max > min { atmost = "at most " } return fmt.Errorf("%s: got %d arguments, want %s%d", fnname, len(args), atmost, max) } for i, arg := range args { if err := unpackOneArg(arg, vars[i]); err != nil { return fmt.Errorf("%s: for parameter %d: %s", fnname, i+1, err) } } return nil } func unpackOneArg(v Value, ptr interface{}) error { // On failure, don't clobber *ptr. switch ptr := ptr.(type) { case Unpacker: return ptr.Unpack(v) case *Value: *ptr = v case *string: s, ok := AsString(v) if !ok { return fmt.Errorf("got %s, want string", v.Type()) } *ptr = s case *bool: b, ok := v.(Bool) if !ok { return fmt.Errorf("got %s, want bool", v.Type()) } *ptr = bool(b) case *int, *int8, *int16, *int32, *int64, *uint, *uint8, *uint16, *uint32, *uint64, *uintptr: return AsInt(v, ptr) case *float64: f, ok := v.(Float) if !ok { return fmt.Errorf("got %s, want float", v.Type()) } *ptr = float64(f) case **List: list, ok := v.(*List) if !ok { return fmt.Errorf("got %s, want list", v.Type()) } *ptr = list case **Dict: dict, ok := v.(*Dict) if !ok { return fmt.Errorf("got %s, want dict", v.Type()) } *ptr = dict case *Callable: f, ok := v.(Callable) if !ok { return fmt.Errorf("got %s, want callable", v.Type()) } *ptr = f case *Iterable: it, ok := v.(Iterable) if !ok { return fmt.Errorf("got %s, want iterable", v.Type()) } *ptr = it default: // v must have type *V, where V is some subtype of starlark.Value. ptrv := reflect.ValueOf(ptr) if ptrv.Kind() != reflect.Ptr { log.Panicf("internal error: not a pointer: %T", ptr) } paramVar := ptrv.Elem() if !reflect.TypeOf(v).AssignableTo(paramVar.Type()) { // The value is not assignable to the variable. // Detect a possible bug in the Go program that called Unpack: // If the variable *ptr is not a subtype of Value, // no value of v can possibly work. if !paramVar.Type().AssignableTo(reflect.TypeOf(new(Value)).Elem()) { log.Panicf("pointer element type does not implement Value: %T", ptr) } // Report Starlark dynamic type error. // // We prefer the Starlark Value.Type name over // its Go reflect.Type name, but calling the // Value.Type method on the variable is not safe // in general. If the variable is an interface, // the call will fail. Even if the variable has // a concrete type, it might not be safe to call // Type() on a zero instance. Thus we must use // recover. // Default to Go reflect.Type name paramType := paramVar.Type().String() // Attempt to call Value.Type method. func() { defer func() { recover() }() paramType = paramVar.MethodByName("Type").Call(nil)[0].String() }() return fmt.Errorf("got %s, want %s", v.Type(), paramType) } paramVar.Set(reflect.ValueOf(v)) } return nil } type intset struct { small uint64 // bitset, used if n < 64 large map[int]bool // set, used if n >= 64 } func (is *intset) init(n int) { if n >= 64 { is.large = make(map[int]bool) } } func (is *intset) set(i int) (prev bool) { if is.large == nil { prev = is.small&(1<