go.starlark.net@v0.0.0-20231101134539-556fd59b42f6/starlark/testdata/function.star (about) 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 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 return inner 16 17 z = outer(3) 18 assert.eq(z(5), 11) 19 assert.eq(z(7), 13) 20 z2 = outer(4) 21 assert.eq(z2(5), 13) 22 assert.eq(z2(7), 15) 23 assert.eq(z(5), 11) 24 assert.eq(z(7), 13) 25 26 # Function name 27 assert.eq(str(outer), '<function outer>') 28 assert.eq(str(z), '<function inner>') 29 assert.eq(str(str), '<built-in function str>') 30 assert.eq(str("".startswith), '<built-in method startswith of string value>') 31 32 # Stateful closure 33 def squares(): 34 x = [0] 35 def f(): 36 x[0] += 1 37 return x[0] * x[0] 38 return f 39 40 sq = squares() 41 assert.eq(sq(), 1) 42 assert.eq(sq(), 4) 43 assert.eq(sq(), 9) 44 assert.eq(sq(), 16) 45 46 # Freezing a closure 47 sq2 = freeze(sq) 48 assert.fails(sq2, "frozen list") 49 50 # recursion detection, simple 51 def fib(x): 52 if x < 2: 53 return x 54 return fib(x-2) + fib(x-1) 55 assert.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. 64 Y = lambda f: (lambda x: x(x))(lambda y: f(lambda *args: y(y)(*args))) 65 fibgen = lambda fib: lambda x: (x if x<2 else fib(x-1)+fib(x-2)) 66 fib2 = Y(fibgen) 67 assert.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: 73 def map(f, seq): return [f(x) for x in seq] 74 def double(x): return x+x 75 assert.eq(map(double, [1, 2, 3]), [2, 4, 6]) 76 assert.eq(map(double, ["a", "b", "c"]), ["aa", "bb", "cc"]) 77 def mapdouble(x): return map(double, x) 78 assert.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) 84 hf = hasfields() 85 hf.x = [len] 86 assert.eq(hf.x[0]("abc"), 3) 87 def f(): 88 return lambda: 1 89 assert.eq(f()(), 1) 90 assert.eq(["abc"][0][0].upper(), "A") 91 92 # functions may be recursively defined, 93 # so long as they don't dynamically recur. 94 calls = [] 95 def yin(x): 96 calls.append("yin") 97 if x: 98 yang(False) 99 100 def yang(x): 101 calls.append("yang") 102 if x: 103 yin(False) 104 105 yin(True) 106 assert.eq(calls, ["yin", "yang"]) 107 108 calls.clear() 109 yang(True) 110 assert.eq(calls, ["yang", "yin"]) 111 112 113 # builtin_function_or_method use identity equivalence. 114 closures = set(["".count for _ in range(10)]) 115 assert.eq(len(closures), 10) 116 117 --- 118 # Default values of function parameters are mutable. 119 load("assert.star", "assert", "freeze") 120 121 def f(x=[0]): 122 return x 123 124 assert.eq(f(), [0]) 125 126 f().append(1) 127 assert.eq(f(), [0, 1]) 128 129 # Freezing a function value freezes its parameter defaults. 130 freeze(f) 131 assert.fails(lambda: f().append(2), "cannot append to frozen list") 132 133 --- 134 # This is a well known corner case of parsing in Python. 135 load("assert.star", "assert") 136 137 f = lambda x: 1 if x else 0 138 assert.eq(f(True), 1) 139 assert.eq(f(False), 0) 140 141 x = True 142 f2 = (lambda x: 1) if x else 0 143 assert.eq(f2(123), 1) 144 145 tf = lambda: True, lambda: False 146 assert.true(tf[0]()) 147 assert.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 155 load("assert.star", "assert") 156 157 def 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 168 assert.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 178 assert.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 195 load("assert.star", "assert") 196 197 def f(*args, **kwargs): 198 return args, kwargs 199 200 assert.eq(f(x=1, y=2), ((), {"x": 1, "y": 2})) 201 assert.fails(lambda: f(x=1, **dict(x=2)), 'multiple values for parameter "x"') 202 203 def g(x, y): 204 return x, y 205 206 assert.eq(g(1, y=2), (1, 2)) 207 assert.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 212 load("assert.star", "assert") 213 214 def f(a, b, x, y): 215 return a+b+x+y 216 217 assert.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. 221 load("assert.star", "assert") 222 223 r = [] 224 225 def id(x): 226 r.append(x) 227 return x 228 229 def f(*args, **kwargs): 230 return (args, kwargs) 231 232 y = f(id(1), id(2), x=id(3), *[id(4)], **dict(z=id(5))) 233 assert.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. 239 assert.eq(r, [1, 2, 3, 4, 5]) 240 241 --- 242 # option:recursion 243 # See github.com/bazelbuild/starlark#170 244 load("assert.star", "assert") 245 246 def 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 256 assert.eq(a(), [3, 2, 1, 0]) 257 258 def 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 268 assert.eq(c(), [1, 2]) 269 270 def e(): 271 def f(): 272 return x # forward reference ok: x is a closure cell 273 x = 1 274 return f() 275 276 assert.eq(e(), 1) 277 278 --- 279 load("assert.star", "assert") 280 281 def 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 288 assert.fails(e, "local variable x referenced before assignment") 289 290 def 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 297 assert.fails(f, "local variable x referenced before assignment") 298 299 def 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 306 assert.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 313 def a(x,): pass 314 def b(x, y=None, ): pass 315 def c(x, y=None, *args, ): pass 316 def d(x, y=None, *args, z=None, ): pass 317 def e(x, y=None, *args, z=None, **kwargs, ): pass 318 319 a(1,) 320 b(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. 327 load("assert.star", "assert") 328 329 assert.fails(lambda: min([], keg=1), ".+did you mean key\\?")