diff options
author | Sasha Smundak <asmundak@google.com> | 2021-02-24 06:08:02 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2021-02-24 06:08:02 +0000 |
commit | 12ead7fd68291cf9660c2a4b468218bb84954df3 (patch) | |
tree | 2efd7da6ec6557b9aaed7932d19e07e1beade288 /starlark/testdata/function.star | |
parent | 7b6d2ef30437199c2b437649bc92fdf6f8379d09 (diff) | |
parent | b1ac553fdc1a97171249fac35e4c743554cb8de4 (diff) | |
download | starlark-go-12ead7fd68291cf9660c2a4b468218bb84954df3.tar.gz |
Import from upstream. am: 2ba90398f8 am: 0710991d7f am: 20e766c836 am: b1ac553fdc
Original change: https://android-review.googlesource.com/c/platform/external/starlark-go/+/1592915
MUST ONLY BE SUBMITTED BY AUTOMERGER
Change-Id: I8423ec7f82305d39c1f882355fb4eacf43634184
Diffstat (limited to 'starlark/testdata/function.star')
-rw-r--r-- | starlark/testdata/function.star | 323 |
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, *{}, ) |