// Print types of elements too, because e.g. true and Boolean(true) print // out the same normally ("true"). Also print non-existent properties // explicitly. function printArray(t) { var tmp = []; for (i = 0; i < t.length; i++) { var exists = t.hasOwnProperty(String(i)); if (exists) { tmp.push(typeof t[i] + ':' + t[i]); } else { tmp.push('nonexistent'); } } print(typeof t + ' ' + t.length + ' ' + tmp.join(',')); } /*=== basic TypeError TypeError TypeError TypeError object 5 object:true,number:1,number:2,string:foo,string:bar object 5 object:false,number:1,number:2,string:foo,string:bar object 5 object:123,number:1,number:2,string:foo,string:bar object 5 object:foo,number:1,number:2,string:foo,string:bar object 6 number:1,number:2,number:1,number:2,string:foo,string:bar object 5 object:[object Object],number:1,number:2,string:foo,string:bar object 4 number:1,number:2,number:3,number:4 object 101 1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,3 object 101 4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,5,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,6 object 202 number:1,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,number:2,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,number:3,number:4,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,number:5,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,number:6 object 101 [object Object] object 101 [object Object] object 2 object:[object Object],object:[object Object] object 4 object:true,number:1,number:2,number:3 object 4 object:[object Object],number:1,number:2,number:3 object 4 object:[object Object],number:1,number:2,number:3 object 5 string:foo,string:bar,number:1,number:2,number:3 object 4 nonexistent,nonexistent,nonexistent,string:elem object 5 number:1,nonexistent,nonexistent,nonexistent,string:elem object 4 string:foo,string:bar,nonexistent,nonexistent object 8 number:1,number:1,number:2,string:foo,string:bar,nonexistent,nonexistent,number:4 object 4 string:foo,string:bar,nonexistent,nonexistent object 5 number:1,number:1,number:2,string:foo,string:bar ===*/ print('basic'); function basicTest() { var t1, t2; function test(this_value, args) { var t; try { if (args.length == 0) { t = Array.prototype.concat.call(this_value); } else if (args.length == 1) { t = Array.prototype.concat.call(this_value, args[0]); } else if (args.length == 2) { t = Array.prototype.concat.call(this_value, args[0], args[1]); } else if (args.length == 3) { t = Array.prototype.concat.call(this_value, args[0], args[1], args[2]); } else { t = Array.prototype.concat.call(this_value, args[0], args[1], args[2], args[3]); } printArray(t); } catch (e) { print(e.name); } } // actual number of call arguments matter; undefined is treated // differently from an argument not given at all test(undefined, []); test(undefined, [ undefined ]); // basic types test // (note that 'this' argument is always ToObject() coerced, even for // strict functions) test(undefined, [ 1, 2, [ 'foo', 'bar' ] ]); test(null, [ 1, 2, [ 'foo', 'bar' ] ]); test(true, [ 1, 2, [ 'foo', 'bar' ] ]); test(false, [ 1, 2, [ 'foo', 'bar' ] ]); test(123, [ 1, 2, [ 'foo', 'bar' ] ]); test('foo', [ 1, 2, [ 'foo', 'bar' ] ]); test([1,2], [ 1, 2, [ 'foo', 'bar' ] ]); test({ foo: 1, bar: 2 }, [ 1, 2, [ 'foo', 'bar' ] ]); // concatenating two dense arrays t1 = [ 1, 2 ]; t2 = [ 3, 4 ]; test(t1, [ t2 ]); // concatenating two sparse arrays t1 = [ 1 ]; t1[100] = 3; t1[50] = 2; t2 = [ 4 ]; t2[100] = 6; t2[50] = 5; print(typeof t1, t1.length, t1); print(typeof t2, t2.length, t2); test(t1, [ t2 ]); // concatenating two non-arrays; here the objects don't fall into the // special handling in step 5.b and are put into the result array as is t1 = { '0': 1, '100': 2, length: 101 }; t2 = { '0': 3, '100': 4, length: 101 }; print(typeof t1, t1.length, t1); print(typeof t2, t2.length, t2); test(t1, [ t2 ]); // 'this' is ToObject() coerced and becomes the first element to be // processed in the loop of E5.1 Section 15.4.4.4, step 5. If 'this' // is an array it falls into the special handling of step 5.b (this // is the typical case); otherwise it is used as the first element // as-is. // // (V8 seems to coerce 'true' to a primitive boolean value, not a // Boolean object.) test(true, [ 1, 2, 3 ]); // ToObject(true) -> Boolean(true) test({ foo: 1, bar: 2 }, [ 1, 2, 3 ]); // already an object test({ '0': 'foo', '1': 'bar', length: 2 }, [ 1, 2, 3 ]); // no special treatment even if "array like" test([ 'foo', 'bar' ], [ 1, 2, 3 ]); // Array -> "flattened" // An array in the argument list is "flattened". The algorithm in E5.1 // Section 15.4.4.4 won't insert missing elements into the result array // (the 'n' counter is incremented). If no elements are inserted after // flattening, trailing non-existent elements won't increase the result // "length". // // Both V8 and Rhino seem to deviate from this: they will update the // result length even for trailing non-existent elements. t = []; t[3] = 'elem'; printArray(t); test([1], [ t ]); t = [ 'foo', 'bar' ]; t.length = 4; // two "non-existent" elements printArray(t); test([1], [ 1, 2, t, 4 ]); // '4' will update final length -> 8 t = [ 'foo', 'bar' ]; t.length = 4; // two "non-existent" elements printArray(t); test([1], [ 1, 2, t ]); // nothing follows non-existent elements, so final length should be 5, not 7 // Rhino and V8 will have final result length 7 } try { basicTest(); } catch (e) { print(e); } /*=== implant object 2 object:true,string:foo ===*/ print('implant'); function implantTest() { 'use strict'; var t; var proto; // when concat() is invoked, this binding will the primitive 'true' // value, which is then coerced to an object in concat() step 1. Boolean.prototype.concat = Array.prototype.concat; t = true.concat('foo'); printArray(t); // It would be nice to test a case where an implanted concat was used // with a "sub-class" of Array. However, because of E5.1 Section // 15.4.4.4 step 5.b, the 'this' binding would not be treated as an // Array anyway, and the 'this' binding would thus go as is into the // result array, as happens above for the Boolean test. } try { implantTest(); } catch (e) { print(e); }