aboutsummaryrefslogtreecommitdiff
path: root/starlark/testdata/function.star
diff options
context:
space:
mode:
Diffstat (limited to 'starlark/testdata/function.star')
-rw-r--r--starlark/testdata/function.star323
1 files changed, 323 insertions, 0 deletions
diff --git a/starlark/testdata/function.star b/starlark/testdata/function.star
new file mode 100644
index 0000000..737df26
--- /dev/null
+++ b/starlark/testdata/function.star
@@ -0,0 +1,323 @@
+# Tests of Starlark 'function'
+# option:set
+
+# TODO(adonovan):
+# - add some introspection functions for looking at function values
+# and test that functions have correct position, free vars, names of locals, etc.
+# - move the hard-coded tests of parameter passing from eval_test.go to here.
+
+load("assert.star", "assert", "freeze")
+
+# Test lexical scope and closures:
+def outer(x):
+ def inner(y):
+ return x + x + y # multiple occurrences of x should create only 1 freevar
+ return inner
+
+z = outer(3)
+assert.eq(z(5), 11)
+assert.eq(z(7), 13)
+z2 = outer(4)
+assert.eq(z2(5), 13)
+assert.eq(z2(7), 15)
+assert.eq(z(5), 11)
+assert.eq(z(7), 13)
+
+# Function name
+assert.eq(str(outer), '<function outer>')
+assert.eq(str(z), '<function inner>')
+assert.eq(str(str), '<built-in function str>')
+assert.eq(str("".startswith), '<built-in method startswith of string value>')
+
+# Stateful closure
+def squares():
+ x = [0]
+ def f():
+ x[0] += 1
+ return x[0] * x[0]
+ return f
+
+sq = squares()
+assert.eq(sq(), 1)
+assert.eq(sq(), 4)
+assert.eq(sq(), 9)
+assert.eq(sq(), 16)
+
+# Freezing a closure
+sq2 = freeze(sq)
+assert.fails(sq2, "frozen list")
+
+# recursion detection, simple
+def fib(x):
+ if x < 2:
+ return x
+ return fib(x-2) + fib(x-1)
+assert.fails(lambda: fib(10), "function fib called recursively")
+
+# recursion detection, advanced
+#
+# A simplistic recursion check that looks for repeated calls to the
+# same function value will not detect recursion using the Y
+# combinator, which creates a new closure at each step of the
+# recursion. To truly prohibit recursion, the dynamic check must look
+# for repeated calls of the same syntactic function body.
+Y = lambda f: (lambda x: x(x))(lambda y: f(lambda *args: y(y)(*args)))
+fibgen = lambda fib: lambda x: (x if x<2 else fib(x-1)+fib(x-2))
+fib2 = Y(fibgen)
+assert.fails(lambda: [fib2(x) for x in range(10)], "function lambda called recursively")
+
+# However, this stricter check outlaws many useful programs
+# that are still bounded, and creates a hazard because
+# helper functions such as map below cannot be used to
+# call functions that themselves use map:
+def map(f, seq): return [f(x) for x in seq]
+def double(x): return x+x
+assert.eq(map(double, [1, 2, 3]), [2, 4, 6])
+assert.eq(map(double, ["a", "b", "c"]), ["aa", "bb", "cc"])
+def mapdouble(x): return map(double, x)
+assert.fails(lambda: map(mapdouble, ([1, 2, 3], ["a", "b", "c"])),
+ 'function map called recursively')
+# With the -recursion option it would yield [[2, 4, 6], ["aa", "bb", "cc"]].
+
+# call of function not through its name
+# (regression test for parsing suffixes of primary expressions)
+hf = hasfields()
+hf.x = [len]
+assert.eq(hf.x[0]("abc"), 3)
+def f():
+ return lambda: 1
+assert.eq(f()(), 1)
+assert.eq(["abc"][0][0].upper(), "A")
+
+# functions may be recursively defined,
+# so long as they don't dynamically recur.
+calls = []
+def yin(x):
+ calls.append("yin")
+ if x:
+ yang(False)
+
+def yang(x):
+ calls.append("yang")
+ if x:
+ yin(False)
+
+yin(True)
+assert.eq(calls, ["yin", "yang"])
+
+calls.clear()
+yang(True)
+assert.eq(calls, ["yang", "yin"])
+
+
+# builtin_function_or_method use identity equivalence.
+closures = set(["".count for _ in range(10)])
+assert.eq(len(closures), 10)
+
+---
+# Default values of function parameters are mutable.
+load("assert.star", "assert", "freeze")
+
+def f(x=[0]):
+ return x
+
+assert.eq(f(), [0])
+
+f().append(1)
+assert.eq(f(), [0, 1])
+
+# Freezing a function value freezes its parameter defaults.
+freeze(f)
+assert.fails(lambda: f().append(2), "cannot append to frozen list")
+
+---
+# This is a well known corner case of parsing in Python.
+load("assert.star", "assert")
+
+f = lambda x: 1 if x else 0
+assert.eq(f(True), 1)
+assert.eq(f(False), 0)
+
+x = True
+f2 = (lambda x: 1) if x else 0
+assert.eq(f2(123), 1)
+
+tf = lambda: True, lambda: False
+assert.true(tf[0]())
+assert.true(not tf[1]())
+
+---
+# Missing parameters are correctly reported
+# in functions of more than 64 parameters.
+# (This tests a corner case of the implementation:
+# we avoid a map allocation for <64 parameters)
+
+load("assert.star", "assert")
+
+def f(a, b, c, d, e, f, g, h,
+ i, j, k, l, m, n, o, p,
+ q, r, s, t, u, v, w, x,
+ y, z, A, B, C, D, E, F,
+ G, H, I, J, K, L, M, N,
+ O, P, Q, R, S, T, U, V,
+ W, X, Y, Z, aa, bb, cc, dd,
+ ee, ff, gg, hh, ii, jj, kk, ll,
+ mm):
+ pass
+
+assert.fails(lambda: f(
+ 1, 2, 3, 4, 5, 6, 7, 8,
+ 9, 10, 11, 12, 13, 14, 15, 16,
+ 17, 18, 19, 20, 21, 22, 23, 24,
+ 25, 26, 27, 28, 29, 30, 31, 32,
+ 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48,
+ 49, 50, 51, 52, 53, 54, 55, 56,
+ 57, 58, 59, 60, 61, 62, 63, 64), "missing 1 argument \\(mm\\)")
+
+assert.fails(lambda: f(
+ 1, 2, 3, 4, 5, 6, 7, 8,
+ 9, 10, 11, 12, 13, 14, 15, 16,
+ 17, 18, 19, 20, 21, 22, 23, 24,
+ 25, 26, 27, 28, 29, 30, 31, 32,
+ 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48,
+ 49, 50, 51, 52, 53, 54, 55, 56,
+ 57, 58, 59, 60, 61, 62, 63, 64, 65,
+ mm = 100), 'multiple values for parameter "mm"')
+
+---
+# Regression test for github.com/google/starlark-go/issues/21,
+# which concerns dynamic checks.
+# Related: https://github.com/bazelbuild/starlark/issues/21,
+# which concerns static checks.
+
+load("assert.star", "assert")
+
+def f(*args, **kwargs):
+ return args, kwargs
+
+assert.eq(f(x=1, y=2), ((), {"x": 1, "y": 2}))
+assert.fails(lambda: f(x=1, **dict(x=2)), 'multiple values for parameter "x"')
+
+def g(x, y):
+ return x, y
+
+assert.eq(g(1, y=2), (1, 2))
+assert.fails(lambda: g(1, y=2, **{'y': 3}), 'multiple values for parameter "y"')
+
+---
+# Regression test for a bug in CALL_VAR_KW.
+
+load("assert.star", "assert")
+
+def f(a, b, x, y):
+ return a+b+x+y
+
+assert.eq(f(*("a", "b"), **dict(y="y", x="x")) + ".", 'abxy.')
+---
+# Order of evaluation of function arguments.
+# Regression test for github.com/google/skylark/issues/135.
+load("assert.star", "assert")
+
+r = []
+
+def id(x):
+ r.append(x)
+ return x
+
+def f(*args, **kwargs):
+ return (args, kwargs)
+
+y = f(id(1), id(2), x=id(3), *[id(4)], **dict(z=id(5)))
+assert.eq(y, ((1, 2, 4), dict(x=3, z=5)))
+
+# This matches Python2 and Starlark-in-Java, but not Python3 [1 2 4 3 6].
+# *args and *kwargs are evaluated last.
+# (Python[23] also allows keyword arguments after *args.)
+# See github.com/bazelbuild/starlark#13 for spec change.
+assert.eq(r, [1, 2, 3, 4, 5])
+
+---
+# option:recursion
+# See github.com/bazelbuild/starlark#170
+load("assert.star", "assert")
+
+def a():
+ list = []
+ def b(n):
+ list.append(n)
+ if n > 0:
+ b(n - 1) # recursive reference to b
+
+ b(3)
+ return list
+
+assert.eq(a(), [3, 2, 1, 0])
+
+def c():
+ list = []
+ x = 1
+ def d():
+ list.append(x) # this use of x observes both assignments
+ d()
+ x = 2
+ d()
+ return list
+
+assert.eq(c(), [1, 2])
+
+def e():
+ def f():
+ return x # forward reference ok: x is a closure cell
+ x = 1
+ return f()
+
+assert.eq(e(), 1)
+
+---
+load("assert.star", "assert")
+
+def e():
+ x = 1
+ def f():
+ print(x) # this reference to x fails
+ x = 3 # because this assignment makes x local to f
+ f()
+
+assert.fails(e, "local variable x referenced before assignment")
+
+def f():
+ def inner():
+ return x
+ if False:
+ x = 0
+ return x # fails (x is an uninitialized cell of this function)
+
+assert.fails(f, "local variable x referenced before assignment")
+
+def g():
+ def inner():
+ return x # fails (x is an uninitialized cell of the enclosing function)
+ if False:
+ x = 0
+ return inner()
+
+assert.fails(g, "local variable x referenced before assignment")
+
+---
+# A trailing comma is allowed in any function definition or call.
+# This reduces the need to edit neighboring lines when editing defs
+# or calls splayed across multiple lines.
+
+def a(x,): pass
+def b(x, y=None, ): pass
+def c(x, y=None, *args, ): pass
+def d(x, y=None, *args, z=None, ): pass
+def e(x, y=None, *args, z=None, **kwargs, ): pass
+
+a(1,)
+b(1, y=2, )
+#c(1, *[], )
+#d(1, *[], z=None, )
+#e(1, *[], z=None, *{}, )