# Assignment # ---------- # * Assignment # * Compound Assignment # * Destructuring Assignment # * Context Property (@) Assignment # * Existential Assignment (?=) # * Assignment to variables similar to generated variables test "context property assignment (using @)", -> nonce = {} addMethod = -> @method = -> nonce this eq nonce, addMethod.call({}).method() test "unassignable values", -> nonce = {} for nonref in ['', '""', '0', 'f()'].concat CoffeeScript.RESERVED eq nonce, (try CoffeeScript.compile "#{nonref} = v" catch e then nonce) # Compound Assignment test "boolean operators", -> nonce = {} a = 0 a or= nonce eq nonce, a b = 1 b or= nonce eq 1, b c = 0 c and= nonce eq 0, c d = 1 d and= nonce eq nonce, d # ensure that RHS is treated as a group e = f = false e and= f or true eq false, e test "compound assignment as a sub expression", -> [a, b, c] = [1, 2, 3] eq 6, (a + b += c) eq 1, a eq 5, b eq 3, c # *note: this test could still use refactoring* test "compound assignment should be careful about caching variables", -> count = 0 list = [] list[++count] or= 1 eq 1, list[1] eq 1, count list[++count] ?= 2 eq 2, list[2] eq 2, count list[count++] and= 6 eq 6, list[2] eq 3, count base = -> ++count base base().four or= 4 eq 4, base.four eq 4, count base().five ?= 5 eq 5, base.five eq 5, count eq 5, base().five ?= 6 eq 6, count test "compound assignment with implicit objects", -> obj = undefined obj ?= one: 1 eq 1, obj.one obj and= two: 2 eq undefined, obj.one eq 2, obj.two test "compound assignment (math operators)", -> num = 10 num -= 5 eq 5, num num *= 10 eq 50, num num /= 10 eq 5, num num %= 3 eq 2, num test "more compound assignment", -> a = {} val = undefined val ||= a val ||= true eq a, val b = {} val &&= true eq val, true val &&= b eq b, val c = {} val = null val ?= c val ?= true eq c, val test "#1192: assignment starting with object literals", -> doesNotThrow (-> CoffeeScript.run "{}.p = 0") doesNotThrow (-> CoffeeScript.run "{}.p++") doesNotThrow (-> CoffeeScript.run "{}[0] = 1") doesNotThrow (-> CoffeeScript.run """{a: 1, 'b', "#{1}": 2}.p = 0""") doesNotThrow (-> CoffeeScript.run "{a:{0:{}}}.a[0] = 0") # Destructuring Assignment test "empty destructuring assignment", -> {} = {} [] = [] test "chained destructuring assignments", -> [a] = {0: b} = {'0': c} = [nonce={}] eq nonce, a eq nonce, b eq nonce, c test "variable swapping to verify caching of RHS values when appropriate", -> a = nonceA = {} b = nonceB = {} c = nonceC = {} [a, b, c] = [b, c, a] eq nonceB, a eq nonceC, b eq nonceA, c [a, b, c] = [b, c, a] eq nonceC, a eq nonceA, b eq nonceB, c fn = -> [a, b, c] = [b, c, a] arrayEq [nonceA,nonceB,nonceC], fn() eq nonceA, a eq nonceB, b eq nonceC, c test "#713: destructuring assignment should return right-hand-side value", -> nonces = [nonceA={},nonceB={}] eq nonces, [a, b] = [c, d] = nonces eq nonceA, a eq nonceA, c eq nonceB, b eq nonceB, d test "#4787 destructuring of objects within arrays", -> arr = [1, {a:1, b:2}] [...,{a, b}] = arr eq a, 1 eq b, arr[1].b deepEqual {a, b}, arr[1] test "destructuring assignment with splats", -> a = {}; b = {}; c = {}; d = {}; e = {} [x,y...,z] = [a,b,c,d,e] eq a, x arrayEq [b,c,d], y eq e, z # Should not trigger implicit call, e.g. rest ... => rest(...) [x,y ...,z] = [a,b,c,d,e] eq a, x arrayEq [b,c,d], y eq e, z test "deep destructuring assignment with splats", -> a={}; b={}; c={}; d={}; e={}; f={}; g={}; h={}; i={} [u, [v, w..., x], y..., z] = [a, [b, c, d, e], f, g, h, i] eq a, u eq b, v arrayEq [c,d], w eq e, x arrayEq [f,g,h], y eq i, z test "destructuring assignment with objects", -> a={}; b={}; c={} obj = {a,b,c} {a:x, b:y, c:z} = obj eq a, x eq b, y eq c, z test "deep destructuring assignment with objects", -> a={}; b={}; c={}; d={} obj = { a b: { 'c': { d: [ b {e: c, f: d} ] } } } {a: w, 'b': {c: d: [x, {'f': z, e: y}]}} = obj eq a, w eq b, x eq c, y eq d, z test "destructuring assignment with objects and splats", -> a={}; b={}; c={}; d={} obj = a: b: [a, b, c, d] {a: b: [y, z...]} = obj eq a, y arrayEq [b,c,d], z # Should not trigger implicit call, e.g. rest ... => rest(...) {a: b: [y, z ...]} = obj eq a, y arrayEq [b,c,d], z test "destructuring assignment against an expression", -> a={}; b={} [y, z] = if true then [a, b] else [b, a] eq a, y eq b, z test "bracket insertion when necessary", -> [a] = [0] ? [1] eq a, 0 # for implicit destructuring assignment in comprehensions, see the comprehension tests test "destructuring assignment with context (@) properties", -> a={}; b={}; c={}; d={}; e={} obj = fn: () -> local = [a, {b, c}, d, e] [@a, {b: @b, c: @c}, @d, @e] = local eq undefined, obj[key] for key in ['a','b','c','d','e'] obj.fn() eq a, obj.a eq b, obj.b eq c, obj.c eq d, obj.d eq e, obj.e test "#1024: destructure empty assignments to produce javascript-like results", -> eq 2 * [] = 3 + 5, 16 test "#1005: invalid identifiers allowed on LHS of destructuring assignment", -> disallowed = ['eval', 'arguments'].concat CoffeeScript.RESERVED throwsCompileError "[#{disallowed.join ', '}] = x", null, null, 'all disallowed' throwsCompileError "[#{disallowed.join '..., '}...] = x", null, null, 'all disallowed as splats' t = tSplat = null for v in disallowed when v isnt 'class' # `class` by itself is an expression throwsCompileError t, null, null, t = "[#{v}] = x" throwsCompileError tSplat, null, null, tSplat = "[#{v}...] = x" for v in disallowed doesNotThrowCompileError "[a.#{v}] = x" doesNotThrowCompileError "[a.#{v}...] = x" doesNotThrowCompileError "[@#{v}] = x" doesNotThrowCompileError "[@#{v}...] = x" test "#2055: destructuring assignment with `new`", -> {length} = new Array eq 0, length test "#156: destructuring with expansion", -> array = [1..5] [first, ..., last] = array eq 1, first eq 5, last [..., lastButOne, last] = array eq 4, lastButOne eq 5, last [first, second, ..., last] = array eq 2, second [..., last] = 'strings as well -> x' eq 'x', last throwsCompileError "[1, ..., 3]", null, null, "prohibit expansion outside of assignment" throwsCompileError "[..., a, b...] = c", null, null, "prohibit expansion and a splat" throwsCompileError "[...] = c", null, null, "prohibit lone expansion" test "destructuring with dynamic keys", -> {"#{'a'}": a, """#{'b'}""": b, c} = {a: 1, b: 2, c: 3} eq 1, a eq 2, b eq 3, c throwsCompileError '{"#{a}"} = b' test "simple array destructuring defaults", -> [a = 1] = [] eq 1, a [a = 2] = [undefined] eq 2, a [a = 3] = [null] eq null, a # Breaking change in CS2: per ES2015, default values are applied for `undefined` but not for `null`. [a = 4] = [0] eq 0, a arr = [a = 5] eq 5, a arrayEq [5], arr test "simple object destructuring defaults", -> {b = 1} = {} eq b, 1 {b = 2} = {b: undefined} eq b, 2 {b = 3} = {b: null} eq b, null # Breaking change in CS2: per ES2015, default values are applied for `undefined` but not for `null`. {b = 4} = {b: 0} eq b, 0 {b: c = 1} = {} eq c, 1 {b: c = 2} = {b: undefined} eq c, 2 {b: c = 3} = {b: null} eq c, null # Breaking change in CS2: per ES2015, default values are applied for `undefined` but not for `null`. {b: c = 4} = {b: 0} eq c, 0 test "multiple array destructuring defaults", -> [a = 1, b = 2, c] = [undefined, 12, 13] eq a, 1 eq b, 12 eq c, 13 [a, b = 2, c = 3] = [undefined, 12, 13] eq a, undefined eq b, 12 eq c, 13 [a = 1, b, c = 3] = [11, 12] eq a, 11 eq b, 12 eq c, 3 test "multiple object destructuring defaults", -> {a = 1, b: bb = 2, 'c': c = 3, "#{0}": d = 4} = {"#{'b'}": 12} eq a, 1 eq bb, 12 eq c, 3 eq d, 4 test "array destructuring defaults with splats", -> [..., a = 9] = [] eq a, 9 [..., b = 9] = [19] eq b, 19 test "deep destructuring assignment with defaults", -> [a, [{b = 1, c = 3}] = [c: 2]] = [0] eq a, 0 eq b, 1 eq c, 2 test "destructuring assignment with context (@) properties and defaults", -> a={}; b={}; c={}; d={}; e={} obj = fn: () -> local = [a, {b, c: undefined}, d] [@a, {b: @b = b, @c = c}, @d, @e = e] = local eq undefined, obj[key] for key in ['a','b','c','d','e'] obj.fn() eq a, obj.a eq b, obj.b eq c, obj.c eq d, obj.d eq e, obj.e test "destructuring assignment with defaults single evaluation", -> callCount = 0 fn = -> callCount++ [a = fn()] = [] eq 0, a eq 1, callCount [a = fn()] = [10] eq 10, a eq 1, callCount {a = fn(), b: c = fn()} = {a: 20, b: undefined} eq 20, a eq c, 1 eq callCount, 2 # Existential Assignment test "existential assignment", -> nonce = {} a = false a ?= nonce eq false, a b = undefined b ?= nonce eq nonce, b c = null c ?= nonce eq nonce, c test "#1627: prohibit conditional assignment of undefined variables", -> throwsCompileError "x ?= 10", null, null, "prohibit (x ?= 10)" throwsCompileError "x ||= 10", null, null, "prohibit (x ||= 10)" throwsCompileError "x or= 10", null, null, "prohibit (x or= 10)" throwsCompileError "do -> x ?= 10", null, null, "prohibit (do -> x ?= 10)" throwsCompileError "do -> x ||= 10", null, null, "prohibit (do -> x ||= 10)" throwsCompileError "do -> x or= 10", null, null, "prohibit (do -> x or= 10)" doesNotThrowCompileError "x = null; x ?= 10", null, "allow (x = null; x ?= 10)" doesNotThrowCompileError "x = null; x ||= 10", null, "allow (x = null; x ||= 10)" doesNotThrowCompileError "x = null; x or= 10", null, "allow (x = null; x or= 10)" doesNotThrowCompileError "x = null; do -> x ?= 10", null, "allow (x = null; do -> x ?= 10)" doesNotThrowCompileError "x = null; do -> x ||= 10", null, "allow (x = null; do -> x ||= 10)" doesNotThrowCompileError "x = null; do -> x or= 10", null, "allow (x = null; do -> x or= 10)" throwsCompileError "-> -> -> x ?= 10", null, null, "prohibit (-> -> -> x ?= 10)" doesNotThrowCompileError "x = null; -> -> -> x ?= 10", null, "allow (x = null; -> -> -> x ?= 10)" test "more existential assignment", -> global.temp ?= 0 eq global.temp, 0 global.temp or= 100 eq global.temp, 100 delete global.temp test "#1348, #1216: existential assignment compilation", -> nonce = {} a = nonce b = (a ?= 0) eq nonce, b #the first ?= compiles into a statement; the second ?= compiles to a ternary expression eq a ?= b ?= 1, nonce if a then a ?= 2 else a = 3 eq a, nonce test "#1591, #1101: splatted expressions in destructuring assignment must be assignable", -> nonce = {} for nonref in ['', '""', '0', 'f()', '(->)'].concat CoffeeScript.RESERVED eq nonce, (try CoffeeScript.compile "[#{nonref}...] = v" catch e then nonce) test "#1643: splatted accesses in destructuring assignments should not be declared as variables", -> nonce = {} accesses = ['o.a', 'o["a"]', '(o.a)', '(o.a).a', '@o.a', 'C::a', 'f().a', 'o?.a', 'o?.a.b', 'f?().a'] for access in accesses for i,j in [1,2,3] #position can matter code = """ nonce = {}; nonce2 = {}; nonce3 = {}; @o = o = new (class C then a:{}); f = -> o [#{new Array(i).join('x,')}#{access}...] = [#{new Array(i).join('0,')}nonce, nonce2, nonce3] unless #{access}[0] is nonce and #{access}[1] is nonce2 and #{access}[2] is nonce3 then throw new Error('[...]') """ eq nonce, unless (try CoffeeScript.run code, bare: true catch e then true) then nonce # subpatterns like `[[a]...]` and `[{a}...]` subpatterns = ['[sub, sub2, sub3]', '{0: sub, 1: sub2, 2: sub3}'] for subpattern in subpatterns for i,j in [1,2,3] code = """ nonce = {}; nonce2 = {}; nonce3 = {}; [#{new Array(i).join('x,')}#{subpattern}...] = [#{new Array(i).join('0,')}nonce, nonce2, nonce3] unless sub is nonce and sub2 is nonce2 and sub3 is nonce3 then throw new Error('[sub...]') """ eq nonce, unless (try CoffeeScript.run code, bare: true catch e then true) then nonce test "#1838: Regression with variable assignment", -> name = 'dave' eq name, 'dave' test '#2211: splats in destructured parameters', -> doesNotThrowCompileError '([a...]) ->' doesNotThrowCompileError '([a...],b) ->' doesNotThrowCompileError '([a...],[b...]) ->' throwsCompileError '([a...,[a...]]) ->' doesNotThrowCompileError '([a...,[b...]]) ->' test '#2213: invocations within destructured parameters', -> throwsCompileError '([a()])->' throwsCompileError '([a:b()])->' throwsCompileError '([a:b.c()])->' throwsCompileError '({a()})->' throwsCompileError '({a:b()})->' throwsCompileError '({a:b.c()})->' test '#2532: compound assignment with terminator', -> doesNotThrowCompileError """ a = "hello" a += " world ! " """ test "#2613: parens on LHS of destructuring", -> a = {} [(a).b] = [1, 2, 3] eq a.b, 1 test "#2181: conditional assignment as a subexpression", -> a = false false && a or= true eq false, a eq false, not a or= true test "#1500: Assignment to variables similar to generated variables", -> len = 0 x = ((results = null; n) for n in [1, 2, 3]) arrayEq [1, 2, 3], x eq 0, len for x in [1, 2, 3] f = -> i = 0 f() eq 'undefined', typeof i ref = 2 x = ref * 2 ? 1 eq x, 4 eq 'undefined', typeof ref1 x = {} base = -> x name = -1 base()[-name] ?= 2 eq x[1], 2 eq base(), x eq name, -1 f = (@a, a) -> [@a, a] arrayEq [1, 2], f.call scope = {}, 1, 2 eq 1, scope.a try throw 'foo' catch error eq error, 'foo' eq error, 'foo' doesNotThrowCompileError '(@slice...) ->' test "Assignment to variables similar to helper functions", -> f = (slice...) -> slice arrayEq [1, 2, 3], f 1, 2, 3 eq 'undefined', typeof slice1 class A class B extends A extend = 3 hasProp = 4 value: 5 method: (bind, bind1) => [bind, bind1, extend, hasProp, @value] {method} = new B arrayEq [1, 2, 3, 4, 5], method 1, 2 modulo = -1 %% 3 eq 2, modulo indexOf = [1, 2, 3] ok 2 in indexOf test "#4566: destructuring with nested default values", -> {a: {b = 1}} = a: {} eq 1, b {c: {d} = {}} = c: d: 3 eq 3, d {e: {f = 5} = {}} = {} eq 5, f test "#4878: Compile error when using destructuring with a splat or expansion in an array", -> arr = ['a', 'b', 'c', 'd'] f1 = (list) -> [first, ..., last] = list f2 = (list) -> [first..., last] = list f3 = (list) -> ([first, ...] = list); first f4 = (list) -> ([first, rest...] = list); rest arrayEq f1(arr), arr arrayEq f2(arr), arr arrayEq f3(arr), 'a' arrayEq f4(arr), ['b', 'c', 'd'] foo = (list) -> ret = if list?.length > 0 [first, ..., last] = list [first, last] else [] arrayEq foo(arr), ['a', 'd'] bar = (list) -> ret = if list?.length > 0 [first, rest...] = list [first, rest] else [] arrayEq bar(arr), ['a', ['b', 'c', 'd']] test "destructuring assignment with an empty array in object", -> obj = a1: [1, 2] b1: 3 {a1:[], b1} = obj eq 'undefined', typeof a1 eq b1, 3 obj = a2: b2: [1, 2] c2: 3 {a2: {b2:[]}, c2} = obj eq 'undefined', typeof b2 eq c2, 3 test "#5004: array destructuring with accessors", -> obj = arr: ['a', 'b', 'c', 'd'] list: {} f1: -> [@first, @rest...] = @arr f2: -> [@second, @third..., @last] = @rest f3: -> [@list.a, @list.middle..., @list.d] = @arr obj.f1() eq obj.first, 'a' arrayEq obj.rest, ['b', 'c', 'd'] obj.f2() eq obj.second, 'b' arrayEq obj.third, ['c'] eq obj.last, 'd' obj.f3() eq obj.list.a, 'a' arrayEq obj.list.middle, ['b', 'c'] eq obj.list.d, 'd' [obj.list.middle..., d] = obj.arr eq d, 'd' arrayEq obj.list.middle, ['a', 'b', 'c'] test "#4884: destructured object splat", -> [{length}...] = [1, 2, 3] eq length, 3 [{length: len}...] = [1, 2, 3] eq len, 3 [{length}..., three] = [1, 2, 3] eq length, 2 eq three, 3 [{length: len}..., three] = [1, 2, 3] eq len, 2 eq three, 3 x = [{length}..., three] = [1, 2, 3] eq length, 2 eq three, 3 eq x[2], 3 x = [{length: len}..., three] = [1, 2, 3] eq len, 2 eq three, 3 eq x[2], 3 test "#4884: destructured array splat", -> [[one, two, three]...] = [1, 2, 3] eq one, 1 eq two, 2 eq three, 3 [[one, two]..., three] = [1, 2, 3] eq one, 1 eq two, 2 eq three, 3 x = [[one, two]..., three] = [1, 2, 3] eq one, 1 eq two, 2 eq three, 3 eq x[2], 3