blob: 425395b993d8c3a3b993f2856e8c7ee1568a8b81 [file]
# 2-step function call
x = [1, 2, 3]
y = x.clear
assert_eq(x, [1, 2, 3])
z = y()
assert_eq(z, None)
assert_eq(x, [])
---
x = {1: 2}
y = x.pop
assert_eq(x, {1: 2})
z = y(1)
assert_eq(z, 2)
assert_eq(x, {})
---
x = "hello"
y = x.upper
z = y()
assert_eq(z, "HELLO")
assert_eq(x, "hello")
---
x = "abc"
y = x.index
z = y("b")
assert_eq(z, 1)
assert_eq(x, "abc")
---
y = {}
assert_eq(y.clear == y.clear, False)
assert_eq([].clear == [].clear, False)
assert_eq(type([].clear), "builtin_function_or_method")
assert_eq(str([].clear), "<built-in method clear of list value>")
assert_eq(str({}.clear), "<built-in method clear of dict value>")
assert_eq(str(len), "<built-in function len>")
assert_fails({}.pop, "missing 1 required positional argument: key")
# Arguments are evaluated in left-to-right order.
# See https://github.com/bazelbuild/starlark/issues/13.
order = []
def id(x):
order.append(x)
return x
def f(*args, **kwargs):
return args, kwargs
assert_eq(
f(id(1), id(2), x=id(3), *[id(4)], **dict(z=id(5))),
((1, 2, 4), {"x": 3, "z": 5}))
assert_eq(order, [1, 2, 3, 4, 5])
---
# getattr
assert_eq(getattr("abc", "upper")(), "ABC")
assert_eq(getattr({'a': True}, "pop")('a'), True)
assert_eq(getattr({}, "hello", "default"), "default")
y = [1, 2, 3]
x = getattr(y, "clear")
assert_eq(y, [1, 2, 3])
x()
assert_eq(y, [])
assert_fails(lambda: getattr("", "abc"), "'string' value has no field or method 'abc'")
assert_fails(lambda: getattr("", "pop", "clear")(), "'string' object is not callable")
# Regression test for a type mismatch crash (b/168743413).
assert_fails(lambda: getattr(1, []), "parameter 'name' got value of type 'list', want 'string'")
# assert_fails evaluates an expression (passed unevaluated in the form of
# a lambda) and asserts that evaluation fails with the given error.
assert_fails(lambda: 1//0, 'integer division by zero')
---
# Test of nested def statements.
def adder(x):
def add(x, y): return x + y # no free vars
def adder(y): return add(x, y) # freevars={x, add}
return adder
add3 = adder(3)
assert_eq(add3(1), 4)
assert_eq(add3(-1), 2)
addlam = adder("lam")
assert_eq(addlam("bda"), "lambda")
assert_eq(addlam("bada"), "lambada")
# Same, with lambda
def adder2(x):
return lambda y: x+y
assert_eq(adder2(3)(1), 4)
assert_eq(adder2("lam")("bda"), "lambda")
# Test of stateful function values.
def makerand(seed=0):
"makerand returns a stateful generator of small pseudorandom numbers."
state = [seed]
def rand():
"rand returns the next pseudorandom number in the sequence."
state[0] = ((state[0] + 7207) * 9941) & 0xfff
return state[0]
return rand
rand1 = makerand(123)
rand2 = makerand(123)
assert_eq([rand1() for _ in range(10)], [3786, 133, 796, 1215, 862, 1961, 3088, 4035, 1458, 3981])
assert_eq([rand2() for _ in range(10)], [3786, 133, 796, 1215, 862, 1961, 3088, 4035, 1458, 3981])
# different seed
rand3 = makerand()
assert_eq([rand3() for _ in range(10)], [1651, 1570, 3261, 3508, 1335, 1846, 2657, 3880, 699, 3594])
# Attempt to mutate frozen closure state.
freeze()
assert_fails(rand3, "trying to mutate a frozen list value")
---
# recursion is disallowed
def fib(x):
return x if x < 2 else fib(x-1)+fib(x-2)
assert_fails(lambda: fib(10), "function 'fib' called recursively")
---
# The recursion check breaks function encapsulation:
# A function g that internally uses a higher-order helper function
# such as 'call' (or Python's map and reduce) cannot itself be
# called from within an active call of that helper.
def call(f): f()
def g(): call(list)
assert_fails(lambda: call(g), "function 'call' called recursively")
---
# The recursion check is based on the syntactic equality
# (same def statement), not function value equivalence.
def eta(f):
return lambda: f()
def nop(): pass
# fn1 and fn2 are both created by the same lambda,
# but they are distinct and close over different values...
fn1 = eta(nop)
fn2 = eta(fn1)
assert_eq(str(fn1), '<function lambda>')
assert_eq(str(fn2), '<function lambda>')
assert_(fn1 != fn2)
# ...yet both cannot be called in the same thread:
assert_fails(fn2, "function 'lambda' called recursively")
# This rule prevents users from writing the Y combinator,
# which creates a new closure at each step of the recursion.
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")
---
# Trivial test of lambda.
def map(f, list):
return [f(x) for x in list]
assert_eq(map(lambda x: len(x), ["one", "two", "three"]),
[3, 3, 5])
assert_eq(type(lambda: 0), "function")
assert_eq(str(lambda: 0), "<function lambda>")
---
# builder returns a string builder:
# an opaque, stateful value with methods and open recursion.
def builder():
chunks = []
self = None
def append(x):
chunks.append("%s" % x)
return self
def build():
return "".join(chunks)
self = struct(append = append, build = build)
return self
assert_eq(builder().append(1).append(" + ").append(2).build(), "1 + 2")