...

Text file src/go.starlark.net/starlark/testdata/function.star

Documentation: go.starlark.net/starlark/testdata

     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
   325---
   326# Unpack provides spell check for argument names.
   327load("assert.star", "assert")
   328
   329assert.fails(lambda: min([], keg=1), ".+did you mean key\\?")

View as plain text