blob: 49f78721fb5c3e2121946fac01f9af35414753f6 [file] [log] [blame]
# Tests of Starlark 'function'
# 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")
# 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"])
# hash(builtin_function_or_method) should be deterministic.
closures = set(["".count for _ in range(10)])
assert.eq(len(closures), 10)
hashes = set([hash("".count) for _ in range(10)])
assert.eq(len(hashes), 1)
---
# 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), "takes exactly 65 positional arguments .64 given.")
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 keyword argument "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 keyword argument "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 keyword argument "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)], y=id(5), **dict(z=id(6)))
assert.eq(y, ((1, 2, 4), dict(x=3, y=5, z=6)))
# This matches Python2, but not Starlark-in-Java:
# *args and *kwargs are evaluated last.
# See github.com/bazelbuild/starlark#13 for pending spec change.
assert.eq(r, [1, 2, 3, 5, 4, 6])