// 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);
}

