• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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