xref: /aosp_15_r20/external/starlark-go/starlark/testdata/function.star (revision 4947cdc739c985f6d86941e22894f5cefe7c9e9a)
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