1# Tests of Starlark 'function'
2# option:set
3
4# TODO(adonovan):
5# - add some introspection functions for looking at function values
6# and test that functions have correct position, free vars, names of locals, etc.
7# - move the hard-coded tests of parameter passing from eval_test.go to here.
8
9load("assert.star", "assert", "freeze")
10
11# Test lexical scope and closures:
12def outer(x):
13 def inner(y):
14 return x + x + y # multiple occurrences of x should create only 1 freevar
15 return inner
16
17z = outer(3)
18assert.eq(z(5), 11)
19assert.eq(z(7), 13)
20z2 = outer(4)
21assert.eq(z2(5), 13)
22assert.eq(z2(7), 15)
23assert.eq(z(5), 11)
24assert.eq(z(7), 13)
25
26# Function name
27assert.eq(str(outer), '<function outer>')
28assert.eq(str(z), '<function inner>')
29assert.eq(str(str), '<built-in function str>')
30assert.eq(str("".startswith), '<built-in method startswith of string value>')
31
32# Stateful closure
33def squares():
34 x = [0]
35 def f():
36 x[0] += 1
37 return x[0] * x[0]
38 return f
39
40sq = squares()
41assert.eq(sq(), 1)
42assert.eq(sq(), 4)
43assert.eq(sq(), 9)
44assert.eq(sq(), 16)
45
46# Freezing a closure
47sq2 = freeze(sq)
48assert.fails(sq2, "frozen list")
49
50# recursion detection, simple
51def fib(x):
52 if x < 2:
53 return x
54 return fib(x-2) + fib(x-1)
55assert.fails(lambda: fib(10), "function fib called recursively")
56
57# recursion detection, advanced
58#
59# A simplistic recursion check that looks for repeated calls to the
60# same function value will not detect recursion using the Y
61# combinator, which creates a new closure at each step of the
62# recursion. To truly prohibit recursion, the dynamic check must look
63# for repeated calls of the same syntactic function body.
64Y = lambda f: (lambda x: x(x))(lambda y: f(lambda *args: y(y)(*args)))
65fibgen = lambda fib: lambda x: (x if x<2 else fib(x-1)+fib(x-2))
66fib2 = Y(fibgen)
67assert.fails(lambda: [fib2(x) for x in range(10)], "function lambda called recursively")
68
69# However, this stricter check outlaws many useful programs
70# that are still bounded, and creates a hazard because
71# helper functions such as map below cannot be used to
72# call functions that themselves use map:
73def map(f, seq): return [f(x) for x in seq]
74def double(x): return x+x
75assert.eq(map(double, [1, 2, 3]), [2, 4, 6])
76assert.eq(map(double, ["a", "b", "c"]), ["aa", "bb", "cc"])
77def mapdouble(x): return map(double, x)
78assert.fails(lambda: map(mapdouble, ([1, 2, 3], ["a", "b", "c"])),
79 'function map called recursively')
80# With the -recursion option it would yield [[2, 4, 6], ["aa", "bb", "cc"]].
81
82# call of function not through its name
83# (regression test for parsing suffixes of primary expressions)
84hf = hasfields()
85hf.x = [len]
86assert.eq(hf.x[0]("abc"), 3)
87def f():
88 return lambda: 1
89assert.eq(f()(), 1)
90assert.eq(["abc"][0][0].upper(), "A")
91
92# functions may be recursively defined,
93# so long as they don't dynamically recur.
94calls = []
95def yin(x):
96 calls.append("yin")
97 if x:
98 yang(False)
99
100def yang(x):
101 calls.append("yang")
102 if x:
103 yin(False)
104
105yin(True)
106assert.eq(calls, ["yin", "yang"])
107
108calls.clear()
109yang(True)
110assert.eq(calls, ["yang", "yin"])
111
112
113# builtin_function_or_method use identity equivalence.
114closures = set(["".count for _ in range(10)])
115assert.eq(len(closures), 10)
116
117---
118# Default values of function parameters are mutable.
119load("assert.star", "assert", "freeze")
120
121def f(x=[0]):
122 return x
123
124assert.eq(f(), [0])
125
126f().append(1)
127assert.eq(f(), [0, 1])
128
129# Freezing a function value freezes its parameter defaults.
130freeze(f)
131assert.fails(lambda: f().append(2), "cannot append to frozen list")
132
133---
134# This is a well known corner case of parsing in Python.
135load("assert.star", "assert")
136
137f = lambda x: 1 if x else 0
138assert.eq(f(True), 1)
139assert.eq(f(False), 0)
140
141x = True
142f2 = (lambda x: 1) if x else 0
143assert.eq(f2(123), 1)
144
145tf = lambda: True, lambda: False
146assert.true(tf[0]())
147assert.true(not tf[1]())
148
149---
150# Missing parameters are correctly reported
151# in functions of more than 64 parameters.
152# (This tests a corner case of the implementation:
153# we avoid a map allocation for <64 parameters)
154
155load("assert.star", "assert")
156
157def f(a, b, c, d, e, f, g, h,
158 i, j, k, l, m, n, o, p,
159 q, r, s, t, u, v, w, x,
160 y, z, A, B, C, D, E, F,
161 G, H, I, J, K, L, M, N,
162 O, P, Q, R, S, T, U, V,
163 W, X, Y, Z, aa, bb, cc, dd,
164 ee, ff, gg, hh, ii, jj, kk, ll,
165 mm):
166 pass
167
168assert.fails(lambda: f(
169 1, 2, 3, 4, 5, 6, 7, 8,
170 9, 10, 11, 12, 13, 14, 15, 16,
171 17, 18, 19, 20, 21, 22, 23, 24,
172 25, 26, 27, 28, 29, 30, 31, 32,
173 33, 34, 35, 36, 37, 38, 39, 40,
174 41, 42, 43, 44, 45, 46, 47, 48,
175 49, 50, 51, 52, 53, 54, 55, 56,
176 57, 58, 59, 60, 61, 62, 63, 64), "missing 1 argument \\(mm\\)")
177
178assert.fails(lambda: f(
179 1, 2, 3, 4, 5, 6, 7, 8,
180 9, 10, 11, 12, 13, 14, 15, 16,
181 17, 18, 19, 20, 21, 22, 23, 24,
182 25, 26, 27, 28, 29, 30, 31, 32,
183 33, 34, 35, 36, 37, 38, 39, 40,
184 41, 42, 43, 44, 45, 46, 47, 48,
185 49, 50, 51, 52, 53, 54, 55, 56,
186 57, 58, 59, 60, 61, 62, 63, 64, 65,
187 mm = 100), 'multiple values for parameter "mm"')
188
189---
190# Regression test for github.com/google/starlark-go/issues/21,
191# which concerns dynamic checks.
192# Related: https://github.com/bazelbuild/starlark/issues/21,
193# which concerns static checks.
194
195load("assert.star", "assert")
196
197def f(*args, **kwargs):
198 return args, kwargs
199
200assert.eq(f(x=1, y=2), ((), {"x": 1, "y": 2}))
201assert.fails(lambda: f(x=1, **dict(x=2)), 'multiple values for parameter "x"')
202
203def g(x, y):
204 return x, y
205
206assert.eq(g(1, y=2), (1, 2))
207assert.fails(lambda: g(1, y=2, **{'y': 3}), 'multiple values for parameter "y"')
208
209---
210# Regression test for a bug in CALL_VAR_KW.
211
212load("assert.star", "assert")
213
214def f(a, b, x, y):
215 return a+b+x+y
216
217assert.eq(f(*("a", "b"), **dict(y="y", x="x")) + ".", 'abxy.')
218---
219# Order of evaluation of function arguments.
220# Regression test for github.com/google/skylark/issues/135.
221load("assert.star", "assert")
222
223r = []
224
225def id(x):
226 r.append(x)
227 return x
228
229def f(*args, **kwargs):
230 return (args, kwargs)
231
232y = f(id(1), id(2), x=id(3), *[id(4)], **dict(z=id(5)))
233assert.eq(y, ((1, 2, 4), dict(x=3, z=5)))
234
235# This matches Python2 and Starlark-in-Java, but not Python3 [1 2 4 3 6].
236# *args and *kwargs are evaluated last.
237# (Python[23] also allows keyword arguments after *args.)
238# See github.com/bazelbuild/starlark#13 for spec change.
239assert.eq(r, [1, 2, 3, 4, 5])
240
241---
242# option:recursion
243# See github.com/bazelbuild/starlark#170
244load("assert.star", "assert")
245
246def a():
247 list = []
248 def b(n):
249 list.append(n)
250 if n > 0:
251 b(n - 1) # recursive reference to b
252
253 b(3)
254 return list
255
256assert.eq(a(), [3, 2, 1, 0])
257
258def c():
259 list = []
260 x = 1
261 def d():
262 list.append(x) # this use of x observes both assignments
263 d()
264 x = 2
265 d()
266 return list
267
268assert.eq(c(), [1, 2])
269
270def e():
271 def f():
272 return x # forward reference ok: x is a closure cell
273 x = 1
274 return f()
275
276assert.eq(e(), 1)
277
278---
279load("assert.star", "assert")
280
281def e():
282 x = 1
283 def f():
284 print(x) # this reference to x fails
285 x = 3 # because this assignment makes x local to f
286 f()
287
288assert.fails(e, "local variable x referenced before assignment")
289
290def f():
291 def inner():
292 return x
293 if False:
294 x = 0
295 return x # fails (x is an uninitialized cell of this function)
296
297assert.fails(f, "local variable x referenced before assignment")
298
299def g():
300 def inner():
301 return x # fails (x is an uninitialized cell of the enclosing function)
302 if False:
303 x = 0
304 return inner()
305
306assert.fails(g, "local variable x referenced before assignment")
307
308---
309# A trailing comma is allowed in any function definition or call.
310# This reduces the need to edit neighboring lines when editing defs
311# or calls splayed across multiple lines.
312
313def a(x,): pass
314def b(x, y=None, ): pass
315def c(x, y=None, *args, ): pass
316def d(x, y=None, *args, z=None, ): pass
317def e(x, y=None, *args, z=None, **kwargs, ): pass
318
319a(1,)
320b(1, y=2, )
321#c(1, *[], )
322#d(1, *[], z=None, )
323#e(1, *[], z=None, *{}, )
324
325---
326# Unpack provides spell check for argument names.
327load("assert.star", "assert")
328
329assert.fails(lambda: min([], keg=1), ".+did you mean key\\?")
View as plain text