alandonovan | 32f3451 | 2019-07-02 18:37:51 -0400 | [diff] [blame] | 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 | |
| 13 | // UnpackArgs unpacks the positional and keyword arguments into the |
| 14 | // supplied parameter variables. pairs is an alternating list of names |
| 15 | // and pointers to variables. |
| 16 | // |
| 17 | // If the variable is a bool, int, string, *List, *Dict, Callable, |
| 18 | // Iterable, or user-defined implementation of Value, |
| 19 | // UnpackArgs performs the appropriate type check. |
| 20 | // An int uses the AsInt32 check. |
| 21 | // If the parameter name ends with "?", |
| 22 | // it and all following parameters are optional. |
| 23 | // |
| 24 | // If the variable implements Value, UnpackArgs may call |
| 25 | // its Type() method while constructing the error message. |
| 26 | // |
| 27 | // Beware: an optional *List, *Dict, Callable, Iterable, or Value variable that is |
| 28 | // not assigned is not a valid Starlark Value, so the caller must |
| 29 | // explicitly handle such cases by interpreting nil as None or some |
| 30 | // computed default. |
| 31 | func UnpackArgs(fnname string, args Tuple, kwargs []Tuple, pairs ...interface{}) error { |
| 32 | nparams := len(pairs) / 2 |
| 33 | var defined intset |
| 34 | defined.init(nparams) |
| 35 | |
| 36 | paramName := func(x interface{}) string { // (no free variables) |
| 37 | name := x.(string) |
| 38 | if name[len(name)-1] == '?' { |
| 39 | name = name[:len(name)-1] |
| 40 | } |
| 41 | return name |
| 42 | } |
| 43 | |
| 44 | // positional arguments |
| 45 | if len(args) > nparams { |
| 46 | return fmt.Errorf("%s: got %d arguments, want at most %d", |
| 47 | fnname, len(args), nparams) |
| 48 | } |
| 49 | for i, arg := range args { |
| 50 | defined.set(i) |
| 51 | if err := unpackOneArg(arg, pairs[2*i+1]); err != nil { |
| 52 | name := paramName(pairs[2*i]) |
| 53 | return fmt.Errorf("%s: for parameter %s: %s", fnname, name, err) |
| 54 | } |
| 55 | } |
| 56 | |
| 57 | // keyword arguments |
| 58 | kwloop: |
| 59 | for _, item := range kwargs { |
| 60 | name, arg := item[0].(String), item[1] |
| 61 | for i := 0; i < nparams; i++ { |
| 62 | if paramName(pairs[2*i]) == string(name) { |
| 63 | // found it |
| 64 | if defined.set(i) { |
| 65 | return fmt.Errorf("%s: got multiple values for keyword argument %s", |
| 66 | fnname, name) |
| 67 | } |
| 68 | ptr := pairs[2*i+1] |
| 69 | if err := unpackOneArg(arg, ptr); err != nil { |
| 70 | return fmt.Errorf("%s: for parameter %s: %s", fnname, name, err) |
| 71 | } |
| 72 | continue kwloop |
| 73 | } |
| 74 | } |
| 75 | return fmt.Errorf("%s: unexpected keyword argument %s", fnname, name) |
| 76 | } |
| 77 | |
| 78 | // Check that all non-optional parameters are defined. |
| 79 | // (We needn't check the first len(args).) |
| 80 | for i := len(args); i < nparams; i++ { |
| 81 | name := pairs[2*i].(string) |
| 82 | if strings.HasSuffix(name, "?") { |
| 83 | break // optional |
| 84 | } |
| 85 | if !defined.get(i) { |
| 86 | return fmt.Errorf("%s: missing argument for %s", fnname, name) |
| 87 | } |
| 88 | } |
| 89 | |
| 90 | return nil |
| 91 | } |
| 92 | |
| 93 | // UnpackPositionalArgs unpacks the positional arguments into |
| 94 | // corresponding variables. Each element of vars is a pointer; see |
| 95 | // UnpackArgs for allowed types and conversions. |
| 96 | // |
| 97 | // UnpackPositionalArgs reports an error if the number of arguments is |
| 98 | // less than min or greater than len(vars), if kwargs is nonempty, or if |
| 99 | // any conversion fails. |
| 100 | func UnpackPositionalArgs(fnname string, args Tuple, kwargs []Tuple, min int, vars ...interface{}) error { |
| 101 | if len(kwargs) > 0 { |
| 102 | return fmt.Errorf("%s: unexpected keyword arguments", fnname) |
| 103 | } |
| 104 | max := len(vars) |
| 105 | if len(args) < min { |
| 106 | var atleast string |
| 107 | if min < max { |
| 108 | atleast = "at least " |
| 109 | } |
| 110 | return fmt.Errorf("%s: got %d arguments, want %s%d", fnname, len(args), atleast, min) |
| 111 | } |
| 112 | if len(args) > max { |
| 113 | var atmost string |
| 114 | if max > min { |
| 115 | atmost = "at most " |
| 116 | } |
| 117 | return fmt.Errorf("%s: got %d arguments, want %s%d", fnname, len(args), atmost, max) |
| 118 | } |
| 119 | for i, arg := range args { |
| 120 | if err := unpackOneArg(arg, vars[i]); err != nil { |
| 121 | return fmt.Errorf("%s: for parameter %d: %s", fnname, i+1, err) |
| 122 | } |
| 123 | } |
| 124 | return nil |
| 125 | } |
| 126 | |
| 127 | func unpackOneArg(v Value, ptr interface{}) error { |
| 128 | // On failure, don't clobber *ptr. |
| 129 | switch ptr := ptr.(type) { |
| 130 | case *Value: |
| 131 | *ptr = v |
| 132 | case *string: |
| 133 | s, ok := AsString(v) |
| 134 | if !ok { |
| 135 | return fmt.Errorf("got %s, want string", v.Type()) |
| 136 | } |
| 137 | *ptr = s |
| 138 | case *bool: |
| 139 | b, ok := v.(Bool) |
| 140 | if !ok { |
| 141 | return fmt.Errorf("got %s, want bool", v.Type()) |
| 142 | } |
| 143 | *ptr = bool(b) |
| 144 | case *int: |
| 145 | i, err := AsInt32(v) |
| 146 | if err != nil { |
| 147 | return err |
| 148 | } |
| 149 | *ptr = i |
| 150 | case **List: |
| 151 | list, ok := v.(*List) |
| 152 | if !ok { |
| 153 | return fmt.Errorf("got %s, want list", v.Type()) |
| 154 | } |
| 155 | *ptr = list |
| 156 | case **Dict: |
| 157 | dict, ok := v.(*Dict) |
| 158 | if !ok { |
| 159 | return fmt.Errorf("got %s, want dict", v.Type()) |
| 160 | } |
| 161 | *ptr = dict |
| 162 | case *Callable: |
| 163 | f, ok := v.(Callable) |
| 164 | if !ok { |
| 165 | return fmt.Errorf("got %s, want callable", v.Type()) |
| 166 | } |
| 167 | *ptr = f |
| 168 | case *Iterable: |
| 169 | it, ok := v.(Iterable) |
| 170 | if !ok { |
| 171 | return fmt.Errorf("got %s, want iterable", v.Type()) |
| 172 | } |
| 173 | *ptr = it |
| 174 | default: |
| 175 | // v must have type *V, where V is some subtype of starlark.Value. |
| 176 | ptrv := reflect.ValueOf(ptr) |
| 177 | if ptrv.Kind() != reflect.Ptr { |
| 178 | log.Panicf("internal error: not a pointer: %T", ptr) |
| 179 | } |
| 180 | paramVar := ptrv.Elem() |
| 181 | if !reflect.TypeOf(v).AssignableTo(paramVar.Type()) { |
| 182 | // The value is not assignable to the variable. |
| 183 | |
| 184 | // Detect a possible bug in the Go program that called Unpack: |
| 185 | // If the variable *ptr is not a subtype of Value, |
| 186 | // no value of v can possibly work. |
| 187 | if !paramVar.Type().AssignableTo(reflect.TypeOf(new(Value)).Elem()) { |
| 188 | log.Panicf("pointer element type does not implement Value: %T", ptr) |
| 189 | } |
| 190 | |
| 191 | // Report Starlark dynamic type error. |
| 192 | // |
| 193 | // We prefer the Starlark Value.Type name over |
| 194 | // its Go reflect.Type name, but calling the |
| 195 | // Value.Type method on the variable is not safe |
| 196 | // in general. If the variable is an interface, |
| 197 | // the call will fail. Even if the variable has |
| 198 | // a concrete type, it might not be safe to call |
| 199 | // Type() on a zero instance. Thus we must use |
| 200 | // recover. |
| 201 | |
| 202 | // Default to Go reflect.Type name |
| 203 | paramType := paramVar.Type().String() |
| 204 | |
| 205 | // Attempt to call Value.Type method. |
| 206 | func() { |
| 207 | defer func() { recover() }() |
| 208 | paramType = paramVar.MethodByName("Type").Call(nil)[0].String() |
| 209 | }() |
| 210 | return fmt.Errorf("got %s, want %s", v.Type(), paramType) |
| 211 | } |
| 212 | paramVar.Set(reflect.ValueOf(v)) |
| 213 | } |
| 214 | return nil |
| 215 | } |
| 216 | |
| 217 | type intset struct { |
| 218 | small uint64 // bitset, used if n < 64 |
| 219 | large map[int]bool // set, used if n >= 64 |
| 220 | } |
| 221 | |
| 222 | func (is *intset) init(n int) { |
| 223 | if n >= 64 { |
| 224 | is.large = make(map[int]bool) |
| 225 | } |
| 226 | } |
| 227 | |
| 228 | func (is *intset) set(i int) (prev bool) { |
| 229 | if is.large == nil { |
| 230 | prev = is.small&(1<<uint(i)) != 0 |
| 231 | is.small |= 1 << uint(i) |
| 232 | } else { |
| 233 | prev = is.large[i] |
| 234 | is.large[i] = true |
| 235 | } |
| 236 | return |
| 237 | } |
| 238 | |
| 239 | func (is *intset) get(i int) bool { |
| 240 | if is.large == nil { |
| 241 | return is.small&(1<<uint(i)) != 0 |
| 242 | } |
| 243 | return is.large[i] |
| 244 | } |
| 245 | |
| 246 | func (is *intset) len() int { |
| 247 | if is.large == nil { |
| 248 | // Suboptimal, but used only for error reporting. |
| 249 | len := 0 |
| 250 | for i := 0; i < 64; i++ { |
| 251 | if is.small&(1<<uint(i)) != 0 { |
| 252 | len++ |
| 253 | } |
| 254 | } |
| 255 | return len |
| 256 | } |
| 257 | return len(is.large) |
| 258 | } |