github.com/lab47/exprcore@v0.0.0-20210525052339-fb7d6bd9331e/exprcore/testdata/function.star (about)

     1  # Tests of Starlark 'function'
     2  # option:nesteddef 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  
     9  load("assert.star", "assert", "freeze")
    10  
    11  # Test lexical scope and closures:
    12  def outer(x) {
    13     def inner(y) {
    14       return x + x + y # multiple occurrences of x should create only 1 freevar
    15     }
    16     return inner
    17  }
    18  
    19  z = outer(3)
    20  assert.eq(z(5), 11)
    21  assert.eq(z(7), 13)
    22  z2 = outer(4)
    23  assert.eq(z2(5), 13)
    24  assert.eq(z2(7), 15)
    25  assert.eq(z(5), 11)
    26  assert.eq(z(7), 13)
    27  
    28  # Function name
    29  assert.eq(str(outer), '<function outer>')
    30  assert.eq(str(z), '<function inner>')
    31  assert.eq(str(str), '<built-in function str>')
    32  assert.eq(str("".startswith), '<built-in method startswith of string value>')
    33  
    34  # Stateful closure
    35  def squares() {
    36      x = [0]
    37      def f() {
    38        x[0] += 1
    39        return x[0] * x[0]
    40      }
    41      return f
    42  }
    43  
    44  sq = squares()
    45  assert.eq(sq(), 1)
    46  assert.eq(sq(), 4)
    47  assert.eq(sq(), 9)
    48  assert.eq(sq(), 16)
    49  
    50  # Freezing a closure
    51  sq2 = freeze(sq)
    52  assert.fails(sq2, "frozen list")
    53  
    54  # recursion detection, simple
    55  def fib(x) {
    56    if x < 2 {
    57      return x
    58    }
    59    return fib(x-2) + fib(x-1)
    60  }
    61  assert.fails(=> fib(10), "function fib called recursively")
    62  
    63  # recursion detection, advanced
    64  #
    65  # A simplistic recursion check that looks for repeated calls to the
    66  # same function value will not detect recursion using the Y
    67  # combinator, which creates a new closure at each step of the
    68  # recursion.  To truly prohibit recursion, the dynamic check must look
    69  # for repeated calls of the same syntactic function body.
    70  Y = f => {
    71    (x => x(x))(y => f((*args) => y(y)(*args)))
    72  }
    73  fibgen = fib => x => (x if x<2 else fib(x-1)+fib(x-2))
    74  fib2 = Y(fibgen)
    75  assert.fails(=> [fib2(x) for x in range(10)], "function lambda called recursively")
    76  
    77  # However, this stricter check outlaws many useful programs
    78  # that are still bounded, and creates a hazard because
    79  # helper functions such as map below cannot be used to
    80  # call functions that themselves use map:
    81  def map(f, seq) { return [f(x) for x in seq] }
    82  def double(x) { return x+x }
    83  assert.eq(map(double, [1, 2, 3]), [2, 4, 6])
    84  assert.eq(map(double, ["a", "b", "c"]), ["aa", "bb", "cc"])
    85  def mapdouble(x) { return map(double, x) }
    86  assert.fails(=> map(mapdouble, ([1, 2, 3], ["a", "b", "c"])),
    87               'function map called recursively')
    88  # With the -recursion option it would yield [[2, 4, 6], ["aa", "bb", "cc"]].
    89  
    90  # call of function not through its name
    91  # (regression test for parsing suffixes of primary expressions)
    92  hf = hasfields()
    93  hf.x = [len]
    94  assert.eq(hf.x[0]("abc"), 3)
    95  def f() {
    96     return => 1
    97    }
    98  assert.eq(f()(), 1)
    99  assert.eq(["abc"][0][0].upper(), "A")
   100  
   101  # functions may be recursively defined,
   102  # so long as they don't dynamically recur.
   103  calls = []
   104  def yin(x) {
   105    calls.append("yin")
   106    if x {
   107      yang(False)
   108    }
   109  }
   110  
   111  def yang(x) {
   112    calls.append("yang")
   113    if x {
   114      yin(False)
   115    }
   116  }
   117  
   118  yin(True)
   119  assert.eq(calls, ["yin", "yang"])
   120  
   121  calls.clear()
   122  yang(True)
   123  assert.eq(calls, ["yang", "yin"])
   124  
   125  
   126  # builtin_function_or_method use identity equivalence.
   127  closures = set(["".count for _ in range(10)])
   128  assert.eq(len(closures), 10)
   129  
   130  ---
   131  # Default values of function parameters are mutable.
   132  load("assert.star", "assert", "freeze")
   133  
   134  def f(x=[0]) {
   135    return x
   136  }
   137  
   138  assert.eq(f(), [0])
   139  
   140  f().append(1)
   141  assert.eq(f(), [0, 1])
   142  
   143  # Freezing a function value freezes its parameter defaults.
   144  freeze(f)
   145  assert.fails(=> f().append(2), "cannot append to frozen list")
   146  
   147  ---
   148  # This is a well known corner case of parsing in Python.
   149  load("assert.star", "assert")
   150  
   151  f = lambda x: 1 if x else 0
   152  assert.eq(f(True), 1)
   153  assert.eq(f(False), 0)
   154  
   155  x = True
   156  f2 = (lambda x: 1) if x else 0
   157  assert.eq(f2(123), 1)
   158  
   159  tf = => True, => False
   160  assert.true(tf[0]())
   161  assert.true(not tf[1]())
   162  
   163  ---
   164  # Missing parameters are correctly reported
   165  # in functions of more than 64 parameters.
   166  # (This tests a corner case of the implementation:
   167  # we avoid a map allocation for <64 parameters)
   168  
   169  load("assert.star", "assert")
   170  
   171  def f(a, b, c, d, e, f, g, h,
   172        i, j, k, l, m, n, o, p,
   173        q, r, s, t, u, v, w, x,
   174        y, z, A, B, C, D, E, F,
   175        G, H, I, J, K, L, M, N,
   176        O, P, Q, R, S, T, U, V,
   177        W, X, Y, Z, aa, bb, cc, dd,
   178        ee, ff, gg, hh, ii, jj, kk, ll,
   179        mm) {
   180    pass
   181  }
   182  
   183  assert.fails(=> f(
   184      1, 2, 3, 4, 5, 6, 7, 8,
   185      9, 10, 11, 12, 13, 14, 15, 16,
   186      17, 18, 19, 20, 21, 22, 23, 24,
   187      25, 26, 27, 28, 29, 30, 31, 32,
   188      33, 34, 35, 36, 37, 38, 39, 40,
   189      41, 42, 43, 44, 45, 46, 47, 48,
   190      49, 50, 51, 52, 53, 54, 55, 56,
   191      57, 58, 59, 60, 61, 62, 63, 64), "missing 1 argument \\(mm\\)")
   192  
   193  assert.fails(=> f(
   194      1, 2, 3, 4, 5, 6, 7, 8,
   195      9, 10, 11, 12, 13, 14, 15, 16,
   196      17, 18, 19, 20, 21, 22, 23, 24,
   197      25, 26, 27, 28, 29, 30, 31, 32,
   198      33, 34, 35, 36, 37, 38, 39, 40,
   199      41, 42, 43, 44, 45, 46, 47, 48,
   200      49, 50, 51, 52, 53, 54, 55, 56,
   201      57, 58, 59, 60, 61, 62, 63, 64, 65,
   202      mm: 100), 'multiple values for parameter "mm"')
   203  
   204  ---
   205  # Regression test for github.com/google/starlark-go/issues/21,
   206  # which concerns dynamic checks.
   207  # Related: https://github.com/bazelbuild/starlark/issues/21,
   208  # which concerns static checks.
   209  
   210  load("assert.star", "assert")
   211  
   212  def f(*args, **kwargs) {
   213    return args, kwargs
   214  }
   215  
   216  assert.eq(f(x:1, y:2), ((), %{"x": 1, "y": 2}))
   217  assert.fails(=> f(x:1, **dict(x:2)), 'multiple values for parameter "x"')
   218  
   219  def g(x, y) {
   220    return x, y
   221  }
   222  
   223  assert.eq(g(1, y:2), (1, 2))
   224  assert.fails(=> g(1, y:2, **%{'y': 3}), 'multiple values for parameter "y"')
   225  
   226  ---
   227  # Regression test for a bug in CALL_VAR_KW.
   228  
   229  load("assert.star", "assert")
   230  
   231  def f(a, b, x, y) {
   232    return a+b+x+y
   233  }
   234  
   235  assert.eq(f(*("a", "b"), **dict(y:"y", x:"x")) + ".", 'abxy.')
   236  ---
   237  # Order of evaluation of function arguments.
   238  # Regression test for github.com/google/skylark/issues/135.
   239  load("assert.star", "assert")
   240  
   241  r = []
   242  
   243  def id(x) {
   244         r.append(x)
   245         return x
   246  }
   247  
   248  def f(*args, **kwargs) {
   249    return (args, kwargs)
   250  }
   251  
   252  y = f(id(1), id(2), x:id(3), *[id(4)], y:id(5), **dict(z:id(6)))
   253  assert.eq(y, ((1, 2, 4), dict(x:3, y:5, z:6)))
   254  
   255  # This matches Python2, but not Starlark-in-Java:
   256  # *args and *kwargs are evaluated last.
   257  # See github.com/bazelbuild/starlark#13 for pending spec change.
   258  assert.eq(r, [1, 2, 3, 5, 4, 6])
   259  
   260  
   261  ---
   262  # option:nesteddef option:recursion
   263  # See github.com/bazelbuild/starlark#170
   264  load("assert.star", "assert")
   265  
   266  def a() {
   267      list = []
   268      def b(n) {
   269          list.append(n)
   270          if n > 0 {
   271              b(n - 1) # recursive reference to b
   272          }
   273      }
   274  
   275      b(3)
   276      return list
   277  }
   278  
   279  assert.eq(a(), [3, 2, 1, 0])
   280  
   281  def c() {
   282      list = []
   283      x = 1
   284      def d() {
   285        list.append(x) # this use of x observes both assignments
   286      }
   287      d()
   288      x = 2
   289      d()
   290      return list
   291  }
   292  
   293  assert.eq(c(), [1, 2])
   294  
   295  def e() {
   296      def f() {
   297        return x # forward reference ok: x is a closure cell
   298      }
   299      x = 1
   300      return f()
   301  }
   302  assert.eq(e(), 1)
   303  
   304  ---
   305  # option:nesteddef
   306  load("assert.star", "assert")
   307  
   308  def e() {
   309      x = 1
   310      def f() {
   311        print(x) # this reference to x fails
   312        x = 3    # because this assignment makes x local to f
   313      }
   314      f()
   315  }
   316  assert.fails(e, "local variable x referenced before assignment")
   317  
   318  
   319  ---
   320  # A trailing comma is allowed in any function definition or call.
   321  # This reduces the need to edit neighboring lines when editing defs
   322  # or calls splayed across multiple lines.
   323  
   324  def a(x,) { pass }
   325  def b(x, y=None, ) { pass }
   326  def c(x, y=None, *args, ) { pass }
   327  def d(x, y=None, *args, z=None, ) { pass }
   328  def e(x, y=None, *args, z=None, **kwargs, ) { pass }
   329  
   330  a(1,)
   331  b(1, y:2, )
   332  #c(1, *[], )
   333  #d(1, *[], z:None, )
   334  #e(1, *[], z:None, *{}, )