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, *{}, )