// Copyright 2014 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Flags: --allow-natives-syntax

(function TestSuperNamedLoads() {
  function Base() { }
  function fBase() { }
  Base.prototype = {
    f() {
      return "Base " + this.toString();
    },
    x: 15,
    toString() {
      return "this is Base";
    }
  };

  function Derived() {
    this.derivedDataProperty = "xxx";
  }
  Derived.prototype = {
    __proto__: Base.prototype,
    toString() { return "this is Derived"; },
    x: 27,
    f() {
      assertEquals("Base this is Derived", super.f());
      var a = super.x;
      assertEquals(15, a);
      assertEquals(15, super.x);
      assertEquals(27, this.x);
      return "Derived";
    }
  };

  assertEquals("Base this is Base", new Base().f());
  assertEquals("Derived", new Derived().f());
}());


(function TestSuperKeyedLoads() {
  'use strict';

  var x = 'x';
  var derivedDataProperty = 'derivedDataProperty';
  var f = 'f';

  class Base {
    f() {
      return "Base " + this.toString();
    }
    toString() {
      return "this is Base";
    }
  }

  Base.prototype[x] = 15;

  function Derived() {
    this[derivedDataProperty] = "xxx";
  }
  Derived.prototype = {
    __proto__: Base.prototype,
    toString() { return "this is Derived"; },
    x: 27,
    f() {
      assertEquals("Base this is Derived", super[f]());
      var a = super[x];
      assertEquals(15, a);
      assertEquals(15, super[x]);
      assertEquals(27, this[x]);
      return "Derived";
    }
  };

  assertEquals("Base this is Base", new Base().f());
  assertEquals("Derived", new Derived().f());
}());


(function TestSuperKeywordNonMethod() {
  'use strict';

  class C {
    f() {
      super.unknown();
    }
  }

  assertThrows(function() {
    new C().f();
  }, TypeError);
}());


(function TestGetter() {
  function Base() {}
  var derived;
  Base.prototype = {
    constructor: Base,
    get x() {
      assertSame(this, derived);
      return this._x;
    },
    _x: 'base'
  };

  function Derived() {}
  Derived.__proto__ = Base;
  Derived.prototype = {
    __proto__: Base.prototype,
    constructor: Derived,
    _x: 'derived',
    testGetter() {
      return super.x;
    },
    testGetterStrict() {
      'use strict';
      return super.x;
    }
  };

  derived = new Derived();
  assertEquals('derived', derived.testGetter());
  derived = new Derived();
  assertEquals('derived', derived.testGetterStrict());
}());


(function TestGetterKeyed() {
  var x = 'x';
  function Base() {}
  var derived;
  Base.prototype = {
    constructor: Base,
    get x() {
      assertSame(this, derived);
      return this._x;
    },
    _x: 'base'
  };

  function Derived() {}
  Derived.__proto__ = Base;
  Derived.prototype = {
    __proto__: Base.prototype,
    constructor: Derived,
    _x: 'derived',
    testGetter() {
      return super[x];
    },
    testGetterStrict() {
      'use strict';
      return super[x];
    },
    testGetterWithToString() {
      var toStringCalled;
      var o = { toString: function() {
        toStringCalled++;
        return 'x';
      } };

      toStringCalled = 0;
      assertEquals('derived', super[o]);
      assertEquals(1, toStringCalled);

      var eToThrow = new Error();
      var oThrowsInToString = { toString: function() {
        throw eToThrow;
      } };

      var ex = null;
      try {
        super[oThrowsInToString];
      } catch(e) { ex = e }
      assertEquals(eToThrow, ex);

      var oReturnsNumericString = { toString: function() {
        return "1";
      } };

      assertEquals(undefined, super[oReturnsNumericString]);
      assertEquals(undefined, super[1]);
    }
  };

  derived = new Derived();
  assertEquals('derived', derived.testGetter());
  derived = new Derived();
  assertEquals('derived', derived.testGetterStrict());
  derived = new Derived();
  derived.testGetterWithToString();
}());


(function TestGetterNumericKeyed() {
  var x = 42;
  function Base() {}
  var derived;
  Base.prototype = {
    constructor: Base,
    _x: 'base'
  };

  Object.defineProperty(Base.prototype, x, { get: function() {
      assertSame(this, derived);
      return this._x;
  }});

  function Derived() {}
  Derived.__proto__ = Base;
  Derived.prototype = {
    __proto__: Base.prototype,
    constructor: Derived,
    _x: 'derived',
    testGetter() {
      return super[x];
    },
    testGetterStrict() {
      'use strict';
      return super[x];
    },
    testGetterWithToString() {
      var toStringCalled;
      var o = {
        toString: function() {
          toStringCalled++;
          return '42';
        }
      };

      toStringCalled = 0;
      assertEquals('derived', super[o]);
      assertEquals(1, toStringCalled);

      var eToThrow = new Error();
      var oThrowsInToString = {
        toString: function() {
          throw eToThrow;
        }
      };

      var ex = null;
      try {
        super[oThrowsInToString];
      } catch(e) { ex = e }
      assertEquals(eToThrow, ex);

      var oReturnsNumericString = {
        toString: function() {
          return "42";
        }
      };

      assertEquals('derived', super[oReturnsNumericString]);
      assertEquals('derived', super[42]);
    }
  };

  derived = new Derived();
  assertEquals('derived', derived.testGetter());
  derived = new Derived();
  assertEquals('derived', derived.testGetterStrict());
  derived = new Derived();
  derived.testGetterWithToString();
}());


(function TestSetter() {
  function Base() {}
  Base.prototype = {
    constructor: Base,
    get x() {
      return this._x;
    },
    set x(v) {
      this._x = v;
    },
    _x: 'base'
  };

  function Derived() {}
  Derived.__proto__ = Base;
  Derived.prototype = {
    __proto__: Base.prototype,
    constructor: Derived,
    _x: 'derived',
    testSetter() {
      assertEquals('foobar', super.x = 'foobar');
      assertEquals('foobarabc', super.x += 'abc');
    },
    testSetterStrict() {
      'use strict';
      assertEquals('foobar', super.x = 'foobar');
      assertEquals('foobarabc', super.x += 'abc');
    }
  };

  var d = new Derived();
  d.testSetter();
  assertEquals('base', Base.prototype._x);
  assertEquals('foobarabc', d._x);
  d._x = '';

  d.testSetterStrict();
  assertEquals('base', Base.prototype._x);
  assertEquals('foobarabc', d._x);
}());


(function TestSetterNumericKeyed() {
  var x = 42;
  function Base() {}
  Base.prototype = {
    constructor: Base,
    _x: 'base'
  };

  Object.defineProperty(Base.prototype, x,
    { get: function() { return this._x; },
      set: function(v) { this._x = v; }
    });

  function Derived() {}
  Derived.__proto__ = Base;
  Derived.prototype = {
    __proto__: Base.prototype,
    constructor: Derived,
    _x: 'derived',
    testSetter() {
      assertEquals('foobar', super[x] = 'foobar');
      assertEquals('foobarabc', super[x] += 'abc');
    },
    testSetterStrict() {
      'use strict';
      assertEquals('foobar', super[x] = 'foobar');
      assertEquals('foobarabc', super[x] += 'abc');
    },
    testSetterWithToString() {
      var toStringCalled;
      var o = {
        toString: function() {
          toStringCalled++;
          return x;
        }
      };

      toStringCalled = 0;
      super[o] = 'set';
      assertEquals(1, toStringCalled);
      assertEquals('set', this._x);

      var eToThrow = new Error();
      var oThrowsInToString = {
        toString: function() {
          throw eToThrow;
        }
      };

      var ex = null;
      try {
        super[oThrowsInToString] = 'xyz';
      } catch(e) { ex = e }
      assertEquals(eToThrow, ex);
      assertEquals('set', this._x);
    }
  };

  var d = new Derived();
  d.testSetter();
  assertEquals('base', Base.prototype._x);
  assertEquals('foobarabc', d._x);
  d._x = '';

  d.testSetterStrict();
  assertEquals('base', Base.prototype._x);
  assertEquals('foobarabc', d._x);

  d = new Derived();
  d.testSetterWithToString();
}());


(function TestSetterKeyed() {
  var x = 'x';
  function Base() {}
  Base.prototype = {
    constructor: Base,
    get x() {
      return this._x;
    },
    set x(v) {
      this._x = v;
    },
    _x: 'base'
  };

  function Derived() {}
  Derived.__proto__ = Base;
  Derived.prototype = {
    __proto__: Base.prototype,
    constructor: Derived,
    _x: 'derived',
    testSetter() {
      assertEquals('foobar', super[x] = 'foobar');
      assertEquals('foobarabc', super[x] += 'abc');
    },
    testSetterStrict() {
      'use strict';
      assertEquals('foobar', super[x] = 'foobar');
      assertEquals('foobarabc', super[x] += 'abc');
    },
    testSetterWithToString() {
      var toStringCalled;
      var o = {
        toString: function() {
          toStringCalled++;
          return 'x';
        }
      };

      toStringCalled = 0;
      super[o] = 'set';
      assertEquals(1, toStringCalled);
      assertEquals('set', this._x);

      var eToThrow = new Error();
      var oThrowsInToString = {
        toString: function() {
          throw eToThrow;
        }
      };

      var ex = null;
      try {
        super[oThrowsInToString] = 'xyz';
      } catch(e) { ex = e }
      assertEquals(eToThrow, ex);
      assertEquals('set', this._x);

      var oReturnsNumericString = {
        toString: function() {
          return "1";
        }
      };

      assertEquals('abc', super[oReturnsNumericString] = 'abc');

      assertEquals('set', this._x);

      assertEquals(10,  super[1] = 10);
    }
  };

  var d = new Derived();
  d.testSetter();
  assertEquals('base', Base.prototype._x);
  assertEquals('foobarabc', d._x);
  d._x = '';
  d.testSetterStrict();
  assertEquals('base', Base.prototype._x);
  assertEquals('foobarabc', d._x);

  d = new Derived();
  d.testSetterWithToString();
}());


(function TestSetterDataProperties() {
  function Base() {}
  Base.prototype = {
    constructor: Base,
    x: 'x from Base'
  };

  function Derived() {}
  Derived.prototype = {
    __proto__: Base.prototype,
    constructor: Derived,
    testSetter() {
      assertEquals('x from Base', super.x);
      super.x = 'data property';
      assertEquals('x from Base', super.x);
      assertEquals('data property', this.x);
    }
  };

  new Derived().testSetter();
}());


(function TestKeyedSetterDataProperties() {
  var x = 'x';
  function Base() {}
  Base.prototype = {
    constructor: Base,
    x: 'x from Base'
  };

  function Derived() {}
  Derived.prototype = {
    __proto__: Base.prototype,
    constructor: Derived,
    testSetter() {
      assertEquals('x from Base', super[x]);
      super[x] = 'data property';
      assertEquals('x from Base', super[x]);
      assertEquals('data property', this[x]);
    }
  };

  new Derived().testSetter();
}());


(function TestKeyedNumericSetterDataProperties() {
  var x = 42;
  function Base() {}
  Base.prototype = {
    constructor: Base,
    42: 'x from Base'
  };

  function Derived() {}
  Derived.prototype = {
    __proto__: Base.prototype,
    constructor: Derived,
    testSetter() {
      assertEquals('x from Base', super[x]);
      super[x] = 'data property';
      assertEquals('x from Base', super[x]);
      assertEquals('data property', this[x]);
    }
  };

  new Derived().testSetter();
}());


(function TestAccessorsOnPrimitives() {
  var getCalled = 0;
  var setCalled = 0;
  function Base() {}
  Base.prototype = {
    constructor: Base,
    get x() {
      getCalled++;
      return 1;
    },
    set x(v) {
      setCalled++;
      return v;
    },
  };

  function Derived() {}
  Derived.prototype = {
    __proto__: Base.prototype,
    constructor: Derived,
    testSetter() {
      setCalled = 0;
      getCalled = 0;
      assertEquals('object', typeof this);
      assertInstanceof(this, Number)
      assertEquals(42, this.valueOf());
      assertEquals(1, super.x);
      assertEquals(1, getCalled);
      assertEquals(0, setCalled);

      assertEquals(5, super.x = 5);
      assertEquals(1, getCalled);
      assertEquals(1, setCalled);

      assertEquals(6, super.x += 5);
      assertEquals(2, getCalled);
      assertEquals(2, setCalled);

      super.newProperty = 15;
      assertEquals(15, this.newProperty);
      assertEquals(undefined, super.newProperty);
    },
    testSetterStrict() {
      'use strict';
      getCalled = 0;
      setCalled = 0;
      assertTrue(42 === this);

      assertEquals(1, super.x);
      assertEquals(1, getCalled);
      assertEquals(0, setCalled);

      assertEquals(5, super.x = 5);
      assertEquals(1, getCalled);
      assertEquals(1, setCalled);

      assertEquals(6, super.x += 5);
      assertEquals(2, getCalled);
      assertEquals(2, setCalled);

      var ex;
      try {
        super.newProperty = 15;
      } catch (e) { ex = e; }
      assertInstanceof(ex, TypeError);
    }
  }

  Derived.prototype.testSetter.call(42);
  Derived.prototype.testSetterStrict.call(42);

  function DerivedFromString() {}
  DerivedFromString.prototype = {
    __proto__: String.prototype,
    f() {
      'use strict';
      assertTrue(42 === this);
      assertEquals(String.prototype.toString, super.toString);
      var ex;
      try {
        super.toString();
      } catch(e) { ex = e; }

      assertInstanceof(ex, TypeError);
    }
  };

  DerivedFromString.prototype.f.call(42);
}());


(function TestKeyedAccessorsOnPrimitives() {
  var x = 'x';
  var newProperty = 'newProperty';
  var toString = 'toString';
  var getCalled = 0;
  var setCalled = 0;
  function Base() {}
  Base.prototype = {
    constructor: Base,
    get x() {
      getCalled++;
      return 1;
    },
    set x(v) {
      setCalled++;
      return v;
    },
  };

  function Derived() {}
  Derived.prototype = {
    __proto__: Base.prototype,
    constructor: Derived,
    testSetter() {
      setCalled = 0;
      getCalled = 0;
      assertEquals('object', typeof this);
      assertInstanceof(this, Number)
      assertEquals(42, this.valueOf());
      assertEquals(1, super[x]);
      assertEquals(1, getCalled);
      assertEquals(0, setCalled);

      assertEquals(5, super[x] = 5);
      assertEquals(1, getCalled);
      assertEquals(1, setCalled);

      assertEquals(6, super[x] += 5);
      assertEquals(2, getCalled);
      assertEquals(2, setCalled);

      super[newProperty] = 15;
      assertEquals(15, this[newProperty]);
      assertEquals(undefined, super[newProperty]);
    },
    testSetterStrict() {
      'use strict';
      getCalled = 0;
      setCalled = 0;
      assertTrue(42 === this);

      assertEquals(1, super[x]);
      assertEquals(1, getCalled);
      assertEquals(0, setCalled);

      assertEquals(5, super[x] = 5);
      assertEquals(1, getCalled);
      assertEquals(1, setCalled);

      assertEquals(6, super[x] += 5);
      assertEquals(2, getCalled);
      assertEquals(2, setCalled);

      var ex;
      try {
        super[newProperty] = 15;
      } catch (e) { ex = e; }
      assertInstanceof(ex,TypeError);
    }
  };

  Derived.prototype.testSetter.call(42);
  Derived.prototype.testSetterStrict.call(42);

  function DerivedFromString() {}
  DerivedFromString.prototype = {
    __proto__: String.prototype,
    f() {
      'use strict';
      assertTrue(42 === this);
      assertEquals(String.prototype.toString, super[toString]);
      var ex;
      try {
        super[toString]();
      } catch(e) { ex = e; }

      assertInstanceof(ex, TypeError);
    }
  };
  DerivedFromString.prototype.f.call(42);
}());


(function TestNumericKeyedAccessorsOnPrimitives() {
  var x = 42;
  var newProperty = 43;
  var getCalled = 0;
  var setCalled = 0;
  function Base() {}
  Base.prototype = {
    constructor: Base,
  };

  Object.defineProperty(Base.prototype, x, {
    get: function() {
      getCalled++;
      return 1;
    },
    set: function(v) {
      setCalled++;
      return v;
    }
  });

  function Derived() {}
  Derived.prototype = {
    __proto__: Base.prototype,
    constructor: Derived,
    testSetter() {
      setCalled = 0;
      getCalled = 0;
      assertEquals('object', typeof this);
      assertInstanceof(this, Number)
      assertEquals(42, this.valueOf());
      assertEquals(1, super[x]);
      assertEquals(1, getCalled);
      assertEquals(0, setCalled);

      assertEquals(5, super[x] = 5);
      assertEquals(1, getCalled);
      assertEquals(1, setCalled);

      assertEquals(6, super[x] += 5);
      assertEquals(2, getCalled);
      assertEquals(2, setCalled);

      super[newProperty] = 15;
      assertEquals(15, this[newProperty]);
      assertEquals(undefined, super[newProperty]);
    },
    testSetterStrict() {
      'use strict';
      getCalled = 0;
      setCalled = 0;
      assertTrue(42 === this);

      assertEquals(1, super[x]);
      assertEquals(1, getCalled);
      assertEquals(0, setCalled);

      assertEquals(5, super[x] = 5);
      assertEquals(1, getCalled);
      assertEquals(1, setCalled);

      assertEquals(6, super[x] += 5);
      assertEquals(2, getCalled);
      assertEquals(2, setCalled);

      var ex;
      try {
        super[newProperty] = 15;
      } catch (e) { ex = e; }
      assertInstanceof(ex, TypeError);
    }
  };

  Derived.prototype.testSetter.call(42);
  Derived.prototype.testSetterStrict.call(42);
}());


(function TestKeyedNumericSetterOnExotics() {
  function Base() {}
  function Derived() {}
  Derived.prototype = {
    __proto__: Base.prototype,
    callSetterOnArray() {
      super[42] = 1;
    },
    callStrictSetterOnString() {
      'use strict';
      assertEquals('string', typeof this);
      assertTrue('abcdef' === this);
      var ex = null;
      try {
        super[5] = 'q';
      } catch(e) { ex = e; }
      assertInstanceof(ex, TypeError);

      ex = null;
      try {
        super[1024] = 'q';
      } catch(e) { ex = e; }
      assertInstanceof(ex, TypeError);
    }
  };

  var x = [];
  assertEquals(0, x.length);
  Derived.prototype.callSetterOnArray.call(x);
  assertEquals(43, x.length);
  assertEquals(1, x[42]);

  var s = 'abcdef';
  Derived.prototype.callStrictSetterOnString.call(s)
}());


(function TestSetterUndefinedProperties() {
  function Base() {}
  function Derived() {}
  Derived.prototype = {
    __proto__: Base.prototype,
    mSloppy() {
      assertEquals(undefined, super.x);
      assertEquals(undefined, this.x);
      super.x = 10;
      assertEquals(10, this.x);
      assertEquals(undefined, super.x);
    },
    mStrict() {
      'use strict';
      assertEquals(undefined, super.x);
      assertEquals(undefined, this.x);
      super.x = 10;
      assertEquals(10, this.x);
      assertEquals(undefined, super.x);
    }
  };

  var d = new Derived();
  d.mSloppy();
  assertEquals(10, d.x);
  var d1 = new Derived();
  d1.mStrict();
  assertEquals(10, d.x);
}());


(function TestKeyedSetterUndefinedProperties() {
  var x = 'x';
  function Base() {}
  function Derived() {}
  Derived.prototype = {
    __proto__: Base.prototype,
    mSloppy() {
      assertEquals(undefined, super[x]);
      assertEquals(undefined, this[x]);
      super[x] = 10;
      assertEquals(10, this[x]);
      assertEquals(undefined, super[x]);
    },
    mStrict() {
      'use strict';
      assertEquals(undefined, super[x]);
      assertEquals(undefined, this[x]);
      super[x] = 10;
      assertEquals(10, this[x]);
      assertEquals(undefined, super[x]);
    }
  };
  var d = new Derived();
  d.mSloppy();
  assertEquals(10, d.x);
  var d1 = new Derived();
  d1.mStrict();
  assertEquals(10, d.x);
}());


(function TestKeyedNumericSetterUndefinedProperties() {
  var x = 42;
  function Base() {}
  function Derived() {}
  Derived.prototype = {
    __proto__: Base.prototype,
    mSloppy() {
      assertEquals(undefined, super[x]);
      assertEquals(undefined, this[x]);
      super[x] = 10;
      assertEquals(10, this[x]);
      assertEquals(undefined, super[x]);
    },
    mStrict() {
      'use strict';
      assertEquals(undefined, super[x]);
      assertEquals(undefined, this[x]);
      super[x] = 10;
      assertEquals(10, this[x]);
      assertEquals(undefined, super[x]);
    }
  };
  var d = new Derived();
  d.mSloppy();
  assertEquals(10, d[x]);
  var d1 = new Derived();
  d1.mStrict();
  assertEquals(10, d[x]);
}());


(function TestSetterCreatingOwnPropertiesReconfigurable() {
  function Base() {}
  function Derived() {}
  Derived.prototype = {
    __proto__: Base.prototype,
    mSloppy() {
      assertEquals(42, this.ownReadOnly);
      super.ownReadOnly = 55;
      assertSame(undefined, super.ownReadOnly);
      assertEquals(42, this.ownReadOnly);
      assertFalse(Base.prototype.hasOwnProperty('ownReadOnly'));

      assertEquals(15, this.ownReadonlyAccessor);
      super.ownReadonlyAccessor = 25;
      assertSame(undefined, super.ownReadonlyAccessor);
      assertEquals(15, this.ownReadonlyAccessor);
      assertFalse(Base.prototype.hasOwnProperty('ownReadonlyAccessor'));

      super.ownSetter = 35;
      assertSame(undefined, super.ownSetter);
      var descr = Object.getOwnPropertyDescriptor(this, 'ownSetter');
      assertTrue('set' in descr);
      assertFalse(Base.prototype.hasOwnProperty('ownSetter'));
    },
    mStrict() {
      'use strict';
      assertEquals(42, this.ownReadOnly);
      assertThrows(() => {super.ownReadOnly = 55}, TypeError);
      assertSame(undefined, super.ownReadOnly);
      assertEquals(42, this.ownReadOnly);
      assertFalse(Base.prototype.hasOwnProperty('ownReadOnly'));

      assertEquals(15, this.ownReadonlyAccessor);
      assertThrows(() => {super.ownReadonlyAccessor = 25}, TypeError);
      assertSame(undefined, super.ownReadonlyAccessor);
      assertEquals(15, this.ownReadonlyAccessor);
      assertFalse(Base.prototype.hasOwnProperty('ownReadonlyAccessor'));

      assertThrows(() => {super.ownSetter = 35}, TypeError);
      assertSame(undefined, super.ownSetter);
      var descr = Object.getOwnPropertyDescriptor(this, 'ownSetter');
      assertTrue('set' in descr);
      assertFalse(Base.prototype.hasOwnProperty('ownSetter'));
    },
  };

  var d = new Derived();
  Object.defineProperty(d, 'ownReadOnly', {
    value: 42,
    writable: false,
    configurable: true
  });
  Object.defineProperty(d, 'ownSetter', {
    set: function() { assertUnreachable(); },
    configurable: true
  });
  Object.defineProperty(d, 'ownReadonlyAccessor', {
    get: function() { return 15; },
    configurable: true
  });

  d.mSloppy();

  var d = new Derived();
  Object.defineProperty(d, 'ownReadOnly', {
    value: 42,
    writable: false,
    configurable: true
  });
  Object.defineProperty(d, 'ownSetter', {
    set: function() { assertUnreachable(); },
    configurable: true
  });
  Object.defineProperty(d, 'ownReadonlyAccessor', {
    get: function() { return 15; },
    configurable: true
  });
  d.mStrict();
}());


(function TestSetterCreatingOwnPropertiesNonConfigurable() {
  function Base() {}
  function Derived() {}
  Derived.prototype = {
    __proto__: Base.prototype,
    mSloppy() {
      assertEquals(42, this.ownReadOnly);
      super.ownReadOnly = 55;
      assertEquals(42, this.ownReadOnly);
      var descr = Object.getOwnPropertyDescriptor(this, 'ownReadOnly');
      assertEquals(42, descr.value);
      assertFalse(descr.configurable);
      assertFalse(descr.enumerable);
      assertFalse(descr.writable);
      assertFalse(Base.prototype.hasOwnProperty('ownReadOnly'));

      assertEquals(15, this.ownReadonlyAccessor);
      super.ownReadonlyAccessor = 25;
      assertSame(undefined, super.ownReadonlyAccessor);
      assertEquals(15, this.ownReadonlyAccessor);
      var descr = Object.getOwnPropertyDescriptor(this, 'ownReadonlyAccessor');
      assertFalse(descr.configurable);
      assertFalse(descr.enumerable);
      assertFalse(Base.prototype.hasOwnProperty('ownReadonlyAccessor'));

      super.ownSetter = 35;
      var descr = Object.getOwnPropertyDescriptor(this, 'ownSetter');
      assertFalse(descr.configurable);
      assertFalse(descr.enumerable);
      assertFalse(Base.prototype.hasOwnProperty('ownSetter'));
    },
    mStrict() {
      'use strict';
      var ex;
      assertEquals(42, this.ownReadOnly);
      try {
        super.ownReadOnly = 55;
      } catch (e) {
        ex = e;
      }
      assertInstanceof(ex, TypeError);
      assertEquals(
          "Cannot assign to read only property 'ownReadOnly' of object '#<Base>'",
          ex.message);
      assertEquals(42, this.ownReadOnly);

      ex = null;
      assertEquals(15, this.ownReadonlyAccessor);
      try {
        super.ownReadonlyAccessor = 25;
      } catch (e) {
        ex = e;
      }
      assertInstanceof(ex, TypeError);
      assertEquals('Cannot redefine property: ownReadonlyAccessor', ex.message);
      assertEquals(15, this.ownReadonlyAccessor);

      ex = null;
      try {
        super.ownSetter = 35;
      } catch (e) {
        ex = e;
      }
      assertInstanceof(ex, TypeError);
      assertEquals('Cannot redefine property: ownSetter', ex.message);
    }
  };

  var d = new Derived();
  Object.defineProperty(d, 'ownReadOnly', { value : 42, writable : false });
  Object.defineProperty(d, 'ownSetter',
      { set : function() { assertUnreachable(); } });
  Object.defineProperty(d, 'ownReadonlyAccessor',
      { get : function() { return 15; }});
  d.mSloppy();
  d.mStrict();
}());


(function TestSetterInForIn() {
  var setCalled = 0;
  var getCalled = 0;
  function Base() {}
  Base.prototype = {
    constructor: Base,
    get x() {
      getCalled++;
      return 1;
    },
    set x(v) {
      setCalled++;
      this.x_.push(v);
    },
  };

  function Derived() {
    this.x_ = [];
  }
  Derived.prototype = {
    __proto__: Base.prototype,
    constructor: Derived,
    testIter() {
      setCalled = 0;
      getCalled = 0;
      for (super.x in [1,2,3]) {}
      assertEquals(0, getCalled);
      assertEquals(3, setCalled);
      assertEquals(["0", "1", "2"], this.x_);
    },
    testIterKeyed() {
      setCalled = 0;
      getCalled = 0;
      for (super[x] in [1,2,3]) {}
      assertEquals(0, getCalled);
      assertEquals(3, setCalled);
      assertEquals(["0","1","2"], this.x_);

      this.x_ = [];
      setCalled = 0;
      getCalled = 0;
      var toStringCalled = 0;
      var o = {toString: function () { toStringCalled++; return x }};
      for (super[o] in [1,2,3]) {}
      assertEquals(0, getCalled);
      assertEquals(3, setCalled);
      assertEquals(3, toStringCalled);
      assertEquals(["0","1","2"], this.x_);
    }
  };

  new Derived().testIter();

  var x = 'x';

  new Derived().testIterKeyed();
}());


function TestKeyedSetterCreatingOwnPropertiesReconfigurable(ownReadOnly,
    ownReadonlyAccessor, ownSetter) {
  function Base() {}
  function Derived() {}
  Derived.prototype = {
    __proto__: Base.prototype,
    mSloppy() {
      assertEquals(42, this[ownReadOnly]);
      super[ownReadOnly] = 55;
      assertSame(undefined, super[ownReadOnly]);
      assertEquals(42, this[ownReadOnly]);
      assertFalse(Base.prototype.hasOwnProperty(ownReadOnly));

      assertEquals(15, this[ownReadonlyAccessor]);
      super[ownReadonlyAccessor] = 25;
      assertSame(undefined, super[ownReadonlyAccessor]);
      assertEquals(15, this[ownReadonlyAccessor]);
      assertFalse(Base.prototype.hasOwnProperty(ownReadonlyAccessor));

      super[ownSetter] = 35;
      assertSame(undefined, super[ownSetter]);
      var descr = Object.getOwnPropertyDescriptor(this, ownSetter);
      assertTrue('set' in descr);
      assertFalse(Base.prototype.hasOwnProperty(ownSetter));
    },
    mStrict() {
      'use strict';
      assertEquals(42, this[ownReadOnly]);
      assertThrows(() => {super[ownReadOnly] = 55}, TypeError);
      assertSame(undefined, super[ownReadOnly]);
      assertEquals(42, this[ownReadOnly]);
      assertFalse(Base.prototype.hasOwnProperty(ownReadOnly));

      assertEquals(15, this[ownReadonlyAccessor]);
      assertThrows(() => {super[ownReadonlyAccessor] = 25}, TypeError);
      assertSame(undefined, super[ownReadonlyAccessor]);
      assertEquals(15, this[ownReadonlyAccessor]);
      assertFalse(Base.prototype.hasOwnProperty(ownReadonlyAccessor));

      assertThrows(() => {super[ownSetter] = 35}, TypeError);
      assertSame(undefined, super[ownSetter]);
      var descr = Object.getOwnPropertyDescriptor(this, ownSetter);
      assertTrue('set' in descr);
      assertFalse(Base.prototype.hasOwnProperty(ownSetter));
    },
  };

  var d = new Derived();
  Object.defineProperty(d, ownReadOnly, {
    value: 42,
    writable: false,
    configurable: true
  });
  Object.defineProperty(d, ownSetter, {
    set: function() { assertUnreachable(); },
    configurable: true
  });
  Object.defineProperty(d, ownReadonlyAccessor, {
    get: function() { return 15; },
    configurable: true
  });

  d.mSloppy();

  var d = new Derived();
  Object.defineProperty(d, ownReadOnly, {
    value: 42,
    writable: false,
    configurable: true
  });
  Object.defineProperty(d, ownSetter, {
    set: function() { assertUnreachable(); },
    configurable: true
  });
  Object.defineProperty(d, ownReadonlyAccessor, {
    get: function() { return 15; },
    configurable: true
  });
  d.mStrict();
}
TestKeyedSetterCreatingOwnPropertiesReconfigurable('ownReadOnly',
                                                   'ownReadonlyAccessor',
                                                   'ownSetter');
TestKeyedSetterCreatingOwnPropertiesReconfigurable(42, 43, 44);


function TestKeyedSetterCreatingOwnPropertiesNonConfigurable(
    ownReadOnly, ownReadonlyAccessor, ownSetter) {
  function Base() {}
  function Derived() {}
  Derived.prototype = {
    __proto__: Base.prototype,
    mSloppy() {
      assertEquals(42, this[ownReadOnly]);
      super[ownReadOnly] = 55;
      assertEquals(42, this[ownReadOnly]);
      var descr = Object.getOwnPropertyDescriptor(this, ownReadOnly);
      assertEquals(42, descr.value);
      assertFalse(descr.configurable);
      assertFalse(descr.enumerable);
      assertFalse(descr.writable);
      assertFalse(Base.prototype.hasOwnProperty(ownReadOnly));

      assertEquals(15, this[ownReadonlyAccessor]);
      super[ownReadonlyAccessor] = 25;
      assertSame(undefined, super[ownReadonlyAccessor]);
      assertEquals(15, this[ownReadonlyAccessor]);
      var descr = Object.getOwnPropertyDescriptor(this, ownReadonlyAccessor);
      assertFalse(descr.configurable);
      assertFalse(descr.enumerable);
      assertFalse(Base.prototype.hasOwnProperty(ownReadonlyAccessor));

      super[ownSetter] = 35;
      var descr = Object.getOwnPropertyDescriptor(this, ownSetter);
      assertFalse(descr.configurable);
      assertFalse(descr.enumerable);
      assertFalse(Base.prototype.hasOwnProperty(ownSetter));
    },
    mStrict() {
      'use strict';
      var ex;
      assertEquals(42, this[ownReadOnly]);
      try {
        super[ownReadOnly] = 55;
      } catch (e) {
        ex = e;
      }
      assertInstanceof(ex, TypeError);
      assertEquals(
          "Cannot assign to read only property '" + ownReadOnly +
              "' of object '#<Base>'",
          ex.message);
      assertEquals(42, this[ownReadOnly]);

      ex = null;
      assertEquals(15, this[ownReadonlyAccessor]);
      try {
        super[ownReadonlyAccessor] = 25;
      } catch (e) {
        ex = e;
      }
      assertInstanceof(ex, TypeError);
      assertEquals('Cannot redefine property: ' + ownReadonlyAccessor,
                   ex.message);
      assertEquals(15, this[ownReadonlyAccessor]);

      ex = null;
      try {
        super[ownSetter] = 35;
      } catch (e) {
        ex = e;
      }
      assertInstanceof(ex, TypeError);
      assertEquals('Cannot redefine property: ' + ownSetter, ex.message);
    }
  };

  var d = new Derived();
  Object.defineProperty(d, ownReadOnly, { value : 42, writable : false });
  Object.defineProperty(d, ownSetter,
      { set : function() { assertUnreachable(); } });
  Object.defineProperty(d, ownReadonlyAccessor,
      { get : function() { return 15; }});
  d.mSloppy();
  d.mStrict();
}
TestKeyedSetterCreatingOwnPropertiesNonConfigurable('ownReadOnly',
    'ownReadonlyAccessor', 'ownSetter');
TestKeyedSetterCreatingOwnPropertiesNonConfigurable(42, 43, 44);


(function TestSetterNoProtoWalk() {
  function Base() {}
  function Derived() {}
  var getCalled;
  var setCalled;
  Derived.prototype = {
    __proto__: Base.prototype,
    get x() { getCalled++; return 42; },
    set x(v) { setCalled++; },
    mSloppy() {
      setCalled = 0;
      getCalled = 0;
      assertEquals(42, this.x);
      assertEquals(1, getCalled);
      assertEquals(0, setCalled);

      getCalled = 0;
      setCalled = 0;
      this.x = 43;
      assertEquals(0, getCalled);
      assertEquals(1, setCalled);

      getCalled = 0;
      setCalled = 0;
      super.x = 15;
      assertEquals(0, setCalled);
      assertEquals(0, getCalled);

      assertEquals(15, this.x);
      assertEquals(0, getCalled);
      assertEquals(0, setCalled);
    },
    mStrict() {
      'use strict';
      setCalled = 0;
      getCalled = 0;
      assertEquals(42, this.x);
      assertEquals(1, getCalled);
      assertEquals(0, setCalled);

      getCalled = 0;
      setCalled = 0;
      this.x = 43;
      assertEquals(0, getCalled);
      assertEquals(1, setCalled);

      getCalled = 0;
      setCalled = 0;
      super.x = 15;
      assertEquals(0, setCalled);
      assertEquals(0, getCalled);

      assertEquals(15, this.x);
      assertEquals(0, getCalled);
      assertEquals(0, setCalled);
    }
  };

  new Derived().mSloppy();
  new Derived().mStrict();
}());


(function TestKeyedSetterNoProtoWalk() {
  var x = 'x';
  function Base() {}
  function Derived() {}
  var getCalled;
  var setCalled;
  Derived.prototype = {
    __proto__: Base.prototype,
    get x() { getCalled++; return 42; },
    set x(v) { setCalled++; },
    mSloppy() {
      setCalled = 0;
      getCalled = 0;
      assertEquals(42, this[x]);
      assertEquals(1, getCalled);
      assertEquals(0, setCalled);

      getCalled = 0;
      setCalled = 0;
      this[x] = 43;
      assertEquals(0, getCalled);
      assertEquals(1, setCalled);

      getCalled = 0;
      setCalled = 0;
      super[x] = 15;
      assertEquals(0, setCalled);
      assertEquals(0, getCalled);

      assertEquals(15, this[x]);
      assertEquals(0, getCalled);
      assertEquals(0, setCalled);
    },
    mStrict() {
      'use strict';
      setCalled = 0;
      getCalled = 0;
      assertEquals(42, this[x]);
      assertEquals(1, getCalled);
      assertEquals(0, setCalled);

      getCalled = 0;
      setCalled = 0;
      this[x] = 43;
      assertEquals(0, getCalled);
      assertEquals(1, setCalled);

      getCalled = 0;
      setCalled = 0;
      super[x] = 15;
      assertEquals(0, setCalled);
      assertEquals(0, getCalled);

      assertEquals(15, this[x]);
      assertEquals(0, getCalled);
      assertEquals(0, setCalled);
    }
  };

  new Derived().mSloppy();
  new Derived().mStrict();
}());


(function TestKeyedNumericSetterNoProtoWalk() {
  var x = 42;
  function Base() {}
  function Derived() {}
  var getCalled;
  var setCalled;
  Derived.prototype = {
    __proto__: Base.prototype,
    mSloppy() {
      setCalled = 0;
      getCalled = 0;
      assertEquals(42, this[x]);
      assertEquals(1, getCalled);
      assertEquals(0, setCalled);

      getCalled = 0;
      setCalled = 0;
      this[x] = 43;
      assertEquals(0, getCalled);
      assertEquals(1, setCalled);

      getCalled = 0;
      setCalled = 0;
      super[x] = 15;
      assertEquals(0, setCalled);
      assertEquals(0, getCalled);

      assertEquals(15, this[x]);
      assertEquals(0, getCalled);
      assertEquals(0, setCalled);
    },
    mStrict() {
      'use strict';
      setCalled = 0;
      getCalled = 0;
      assertEquals(42, this[x]);
      assertEquals(1, getCalled);
      assertEquals(0, setCalled);

      getCalled = 0;
      setCalled = 0;
      this[x] = 43;
      assertEquals(0, getCalled);
      assertEquals(1, setCalled);

      getCalled = 0;
      setCalled = 0;
      super[x] = 15;
      assertEquals(0, setCalled);
      assertEquals(0, getCalled);

      assertEquals(15, this[x]);
      assertEquals(0, getCalled);
      assertEquals(0, setCalled);
    }
  };

  Object.defineProperty(Derived.prototype, x, {
    get: function() { getCalled++; return 42; },
    set: function(v) { setCalled++; }
  });

  new Derived().mSloppy();
  new Derived().mStrict();
}());


(function TestSetterDoesNotReconfigure() {
  function Base() {}
  function Derived() {}
  Derived.prototype = {
    __proto__: Derived.prototype,
    mStrict(){
      'use strict';
      super.nonEnumConfig = 5;
      var d1 = Object.getOwnPropertyDescriptor(this, 'nonEnumConfig');
      assertEquals(5, d1.value);
      assertTrue(d1.configurable);
      assertFalse(d1.enumerable);

      super.nonEnumNonConfig = 5;
      var d1 = Object.getOwnPropertyDescriptor(this, 'nonEnumNonConfig');
      assertEquals(5, d1.value);
      assertFalse(d1.configurable);
      assertFalse(d1.enumerable);
    },
    mSloppy(){
      super.nonEnumConfig = 42;
      var d1 = Object.getOwnPropertyDescriptor(this, 'nonEnumConfig');
      assertEquals(42, d1.value);
      assertTrue(d1.configurable);
      assertFalse(d1.enumerable);

      super.nonEnumNonConfig = 42;
      var d1 = Object.getOwnPropertyDescriptor(this, 'nonEnumNonConfig');
      assertEquals(42, d1.value);
      assertFalse(d1.configurable);
      assertFalse(d1.enumerable);
    }
  };

  var d = new Derived();
  Object.defineProperty(d, 'nonEnumConfig',
      { value : 0, enumerable : false, configurable : true, writable : true });
  Object.defineProperty(d, 'nonEnumNonConfig',
      { value : 0, enumerable : false, configurable : false, writable : true });
  d.mStrict();
  d.mSloppy();
}());


(function TestKeyedSetterDoesNotReconfigure() {
  var nonEnumConfig = 'nonEnumConfig';
  var nonEnumNonConfig = 'nonEnumNonConfig';
  function Base() {}
  function Derived() {}

  Derived.prototype = {
    __proto__: Base.prototype,
    mStrict(){
      'use strict';
      super[nonEnumConfig] = 5;
      var d1 = Object.getOwnPropertyDescriptor(this, nonEnumConfig);
      assertEquals(5, d1.value);
      assertTrue(d1.configurable);
      assertFalse(d1.enumerable);

      super[nonEnumNonConfig] = 5;
      var d1 = Object.getOwnPropertyDescriptor(this, nonEnumNonConfig);
      assertEquals(5, d1.value);
      assertFalse(d1.configurable);
      assertFalse(d1.enumerable);
    },
    mSloppy(){
      super[nonEnumConfig] = 42;
      var d1 = Object.getOwnPropertyDescriptor(this, nonEnumConfig);
      assertEquals(42, d1.value);
      assertTrue(d1.configurable);
      assertFalse(d1.enumerable);

      super[nonEnumNonConfig] = 42;
      var d1 = Object.getOwnPropertyDescriptor(this, nonEnumNonConfig);
      assertEquals(42, d1.value);
      assertFalse(d1.configurable);
      assertFalse(d1.enumerable);
    }
  };

  var d = new Derived();
  Object.defineProperty(d, nonEnumConfig,
      { value : 0, enumerable : false, configurable : true, writable : true });
  Object.defineProperty(d, nonEnumNonConfig,
      { value : 0, enumerable : false, configurable : false, writable : true });
  d.mStrict();
  d.mSloppy();
}());


(function TestKeyedNumericSetterDoesNotReconfigure() {
  var nonEnumConfig = 42;
  var nonEnumNonConfig = 43;
  function Base() {}
  function Derived() {}

  Derived.prototype = {
    __proto__: Base.prototype,
    mStrict(){
      'use strict';
      super[nonEnumConfig] = 5;
      var d1 = Object.getOwnPropertyDescriptor(this, nonEnumConfig);
      assertEquals(5, d1.value);
      assertTrue(d1.configurable);
      assertFalse(d1.enumerable);

      super[nonEnumNonConfig] = 5;
      var d1 = Object.getOwnPropertyDescriptor(this, nonEnumNonConfig);
      assertEquals(5, d1.value);
      assertFalse(d1.configurable);
      assertFalse(d1.enumerable);
    },
    mSloppy(){
      super[nonEnumConfig] = 42;
      var d1 = Object.getOwnPropertyDescriptor(this, nonEnumConfig);
      assertEquals(42, d1.value);
      assertTrue(d1.configurable);
      assertFalse(d1.enumerable);

      super[nonEnumNonConfig] = 42;
      var d1 = Object.getOwnPropertyDescriptor(this, nonEnumNonConfig);
      assertEquals(42, d1.value);
      assertFalse(d1.configurable);
      assertFalse(d1.enumerable);
    }
  };

  var d = new Derived();
  Object.defineProperty(d, nonEnumConfig,
      { value : 0, enumerable : false, configurable : true, writable : true });
  Object.defineProperty(d, nonEnumNonConfig,
      { value : 0, enumerable : false, configurable : false, writable : true });
  d.mStrict();
  d.mSloppy();
}());


(function TestCountOperations() {
  function Base() {}
  Base.prototype = {
    constructor: Base,
    get x() {
      return this._x;
    },
    set x(v) {
      this._x = v;
    },
    _x: 1
  };

  function Derived() {}
  Derived.__proto__ = Base;
  Derived.prototype = {
    __proto__: Base.prototype,
    constructor: Derived,
    _x: 2,
    testCounts() {
      assertEquals(2, this._x);
      assertEquals(2, super.x);
      super.x++;
      assertEquals(3, super.x);
      ++super.x;
      assertEquals(4, super.x);
      assertEquals(4, super.x++);
      assertEquals(5, super.x);
      assertEquals(6, ++super.x);
      assertEquals(6, super.x);
      assertEquals(6, this._x);

      super.x--;
      assertEquals(5, super.x);
      --super.x;
      assertEquals(4, super.x);
      assertEquals(4, super.x--);
      assertEquals(3, super.x);
      assertEquals(2, --super.x);
      assertEquals(2, super.x);
      assertEquals(2, this._x);
    }
  };
  new Derived().testCounts();
}());


(function TestKeyedCountOperations() {
  var x = 'x';
  function Base() {}
  Base.prototype = {
    constructor: Base,
    get x() {
      return this._x;
    },
    set x(v) {
      this._x = v;
    },
    _x: 1
  };

  function Derived() {}
  Derived.__proto__ = Base;
  Derived.prototype = {
    __proto__: Base.prototype,
    constructor: Derived,
    _x: 2,
    testCounts() {
      assertEquals(2, this._x);
      assertEquals(2, super[x]);
      super[x]++;
      assertEquals(3, super[x]);
      ++super[x];
      assertEquals(4, super[x]);
      assertEquals(4, super[x]++);
      assertEquals(5, super[x]);
      assertEquals(6, ++super[x]);
      assertEquals(6, super[x]);
      assertEquals(6, this._x);

      super[x]--;
      assertEquals(5, super[x]);
      --super[x];
      assertEquals(4, super[x]);
      assertEquals(4, super[x]--);
      assertEquals(3, super[x]);
      assertEquals(2, --super[x]);
      assertEquals(2, super[x]);
      assertEquals(2, this._x);
    }
  };
  new Derived().testCounts();
}());


(function TestKeyedNumericCountOperations() {
  var x = 42;
  function Base() {}
  Base.prototype = {
    constructor: Base,
    _x: 1
  };

  Object.defineProperty(Base.prototype, x, {
    get: function() { return this._x; },
    set: function(v) { this._x = v;; }
  });

  function Derived() {}
  Derived.__proto__ = Base;
  Derived.prototype = {
    __proto__: Base.prototype,
    constructor: Derived,
    _x: 2,
    testCounts() {
      assertEquals(2, this._x);
      assertEquals(2, super[x]);
      super[x]++;
      assertEquals(3, super[x]);
      ++super[x];
      assertEquals(4, super[x]);
      assertEquals(4, super[x]++);
      assertEquals(5, super[x]);
      assertEquals(6, ++super[x]);
      assertEquals(6, super[x]);
      assertEquals(6, this._x);

      super[x]--;
      assertEquals(5, super[x]);
      --super[x];
      assertEquals(4, super[x]);
      assertEquals(4, super[x]--);
      assertEquals(3, super[x]);
      assertEquals(2, --super[x]);
      assertEquals(2, super[x]);
      assertEquals(2, this._x);
    }
  };
  new Derived().testCounts();
}());


(function TestSetterSuperNonWritable() {
  function Base() {}
  Object.defineProperty(Base.prototype, 'x', { value : 27, writable: false });
  function Derived() {}
  Derived.prototype = {
    __proto__: Base.prototype,
    constructor: Derived,
    mSloppy() {
      assertEquals(27, super.x);
      assertEquals(27, this.x);
      super.x = 10;
      assertEquals(27, super.x);
      assertEquals(27, this.x);
    },
    mStrict() {
      'use strict';
      assertEquals(27, super.x);
      assertEquals(27, this.x);
      var ex = null;
      try { super.x = 10; } catch(e) { ex = e; }
      assertInstanceof(ex, TypeError);
      assertEquals(27, super.x);
      assertEquals(27, this.x);
    }
  };
  new Derived().mSloppy();
  new Derived().mStrict();
}());


(function TestSetterKeyedSuperNonWritable() {
  var x = 'xyz';
  function Base() {}
  Object.defineProperty(Base.prototype, x, { value : 27, writable: false });
  function Derived() {}

  Derived.prototype = {
    __proto__: Base.prototype,
    constructor: Derived,
    mSloppy() {
      assertEquals(27, super[x]);
      assertEquals(27, this[x]);
      super[x] = 10;
      assertEquals(27, super[x]);
      assertEquals(27, this[x]);
    },
    mStrict() {
      'use strict';
      assertEquals(27, super[x]);
      assertEquals(27, this[x]);
      var ex = null;
      try { super[x] = 10; } catch(e) { ex = e; }
      assertInstanceof(ex, TypeError);
      assertEquals(27, super[x]);
      assertEquals(27, this[x]);
    }
  };
  new Derived().mSloppy();
  new Derived().mStrict();
}());


(function TestSetterKeyedNumericSuperNonWritable() {
  var x = 42;
  function Base() {}
  Object.defineProperty(Base.prototype, x, { value : 27, writable: false });
  function Derived() {}

  Derived.prototype = {
    __proto__: Base.prototype,
    constructor: Derived,
    mSloppy() {
      assertEquals(27, super[x]);
      assertEquals(27, this[x]);
      super[x] = 10;
      assertEquals(27, super[x]);
      assertEquals(27, this[x]);
    },
    mStrict() {
      'use strict';
      assertEquals(27, super[x]);
      assertEquals(27, this[x]);
      var ex = null;
      try { super[x] = 10; } catch(e) { ex = e; }
      assertInstanceof(ex, TypeError);
      assertEquals(27, super[x]);
      assertEquals(27, this[x]);
    }
  };
  new Derived().mSloppy();
  new Derived().mStrict();
}());


(function TestSuperCall() {
  'use strict';

  var baseCalled = 0;
  var derivedCalled = 0;
  var derivedDerivedCalled = 0;

  class Base {
    constructor() {
      baseCalled++;
    }
  }

  class Derived extends Base {
    constructor() {
      let r = super();
      assertEquals(this, r);
      derivedCalled++;
    }
  }

  assertEquals(Base, Base.prototype.constructor);
  assertEquals(Base.prototype, Derived.prototype.__proto__);

  baseCalled = 0;
  derivedCalled = 0;
  new Derived();
  assertEquals(1, baseCalled);
  assertEquals(1, derivedCalled);

  class DerivedDerived extends Derived {
    constructor() {
      let r = super();
      assertEquals(this, r);
      derivedDerivedCalled++;
    }
  }

  baseCalled = 0;
  derivedCalled = 0;
  derivedDerivedCalled = 0;
  new DerivedDerived();
  assertEquals(1, baseCalled);
  assertEquals(1, derivedCalled);
  assertEquals(1, derivedDerivedCalled);

  class Base2 {
    constructor(v) {
      this.fromBase = v;
    }
  }
  class Derived2 extends Base2 {
    constructor(v1, v2) {
      let r = super(v1);
      assertEquals(this, r);
      this.fromDerived = v2;
    }
  }

  var d = new Derived2("base", "derived");
  assertEquals("base", d.fromBase);
  assertEquals("derived", d.fromDerived);

  var calls = 0;
  class G {
    constructor() {
      calls++;
    }
  }

  class F extends Object {
    constructor() {
      super();
    }
  }
  F.__proto__ = G;
  new F();
  assertEquals(1, calls);
  F.__proto__ = function() {};
  new F();
  assertEquals(1, calls);
}());


(function TestExtendsObject() {
  'use strict';
  class F extends Object { }
  var f = new F(42);

  assertInstanceof(f, F);
  assertInstanceof(f, Object);
}());


(function TestSuperCallErrorCases() {
  'use strict';
  class T extends Object {
    constructor() {
      super();
    }
  }

  T.__proto__ = null;
  assertThrows(function() { new T(); }, TypeError);
}());


(function TestSuperPropertyInEval() {
  'use strict';
  let y = 3;
  class Base {
    m() { return 1; }
    get x() { return 2; }
  }
  class Derived extends Base {
    evalM() {
      assertEquals(1, eval('super.m()'));
    }
    evalX() {
      assertEquals(2, eval('super.x'));
    }
    globalEval1() {
      assertThrows('super.x', SyntaxError);
      assertThrows('super.m()', SyntaxError);
    }
    globalEval2() {
      super.x;
      assertThrows('super.x', SyntaxError);
      assertThrows('super.m()', SyntaxError);
    }
  }
  let d = new Derived();
  d.globalEval1();
  d.globalEval2();
  d.evalM();
  d.evalX();
})();


(function TestSuperPropertyInArrow() {
  'use strict';
  let y = 3;
  class Base {
    m() { return 1; }
    get x() { return 2; }
  }
  class Derived extends Base {
    arrow() {
      assertSame(super.x, (() => super.x)());
      assertSame(super.m(), (() => super.m())());
      return (() => super.m())();
    }
  }
  let d = new Derived();
  assertSame(1, d.arrow());
})();


(function TestSuperInOtherScopes() {
  var p = {x: 99};
  var o0 = {__proto__: p, f() { return eval("'use strict'; super.x") }};
  assertEquals(p.x, o0.f());
  var o1 = {__proto__: p, f() { with ({}) return super.x }};
  assertEquals(p.x, o1.f());
  var o2 = {__proto__: p, f({a}) { return super.x }};
  assertEquals(p.x, o2.f({}));
  var o3 = {__proto__: p, f(...a) { return super.x }};
  assertEquals(p.x, o3.f());
  var o4 = {__proto__: p, f() { 'use strict'; { let x; return super.x } }};
  assertEquals(p.x, o4.f());
})();


(function TestSuperCallInOtherScopes() {
  class C {constructor() { this.x = 99 }}
  class D0 extends C {constructor() { eval("'use strict'; super()") }}
  assertEquals(99, (new D0).x);
  class D2 extends C {constructor({a}) { super() }}
  assertEquals(99, (new D2({})).x);
  class D3 extends C {constructor(...a) { super() }}
  assertEquals(99, (new D3()).x);
  class D4 extends C {constructor() { { let x; super() } }}
  assertEquals(99, (new D4).x);
})();


(function TestSuperCallInEval() {
  'use strict';
  class Base {
    constructor(x) {
      this.x = x;
    }
  }
  class Derived extends Base {
    constructor(x) {
      let r = eval('super(x)');
      assertEquals(this, r);
    }
  }
  let d = new Derived(42);
  assertSame(42, d.x);
})();


(function TestSuperCallInArrow() {
  'use strict';
  class Base {
    constructor(x) {
      this.x = x;
    }
  }
  class Derived extends Base {
    constructor(x) {
      let r = (() => super(x))();
      assertEquals(this, r);
    }
  }
  let d = new Derived(42);
  assertSame(42, d.x);
})();


(function TestSuperCallEscapes() {
  'use strict';
  class Base {
    constructor(x) {
      this.x = x;
    }
  }

  let f;
  class Derived extends Base {
    constructor() {
      f = () => super(2);
    }
  }
  assertThrows(function() {
    new Derived();
  }, ReferenceError);

  let o = f();
  assertEquals(2, o.x);
  assertInstanceof(o, Derived);

  assertThrows(function() {
    f();
  }, ReferenceError);
})();


(function TestSuperCallInLoop() {
  'use strict';
  class Base {
    constructor(x) {
      this.x = x;
    }
  }
  class Derived extends Base {
    constructor(x, n) {
      for (var i = 0; i < n; ++i) {
        super(x);
      }
    }
  }

  let o = new Derived(23, 1);
  assertEquals(23, o.x);
  assertInstanceof(o, Derived);

  assertThrows("new Derived(42, 0)", ReferenceError);
  assertThrows("new Derived(65, 2)", ReferenceError);
})();


(function TestSuperCallReentrant() {
  'use strict';
  class Base {
    constructor(fun) {
      this.x = fun();
    }
  }
  class Derived extends Base {
    constructor(x) {
      let f = () => super(() => x)
      super(f);
    }
  }
  assertThrows("new Derived(23)", ReferenceError);
})();


(function TestSuperCallSpreadInEval() {
  'use strict';
  class Base {
    constructor(x) {
      this.x = x;
    }
  }
  class Derived extends Base {
    constructor(x) {
      let r = eval('super(...[x])');
      assertEquals(this, r);
    }
  }
  let d = new Derived(42);
  assertSame(42, d.x);
})();


(function TestSuperCallSpreadInArrow() {
  'use strict';
  class Base {
    constructor(x) {
      this.x = x;
    }
  }
  class Derived extends Base {
    constructor(x) {
      let r = (() => super(...[x]))();
      assertEquals(this, r);
    }
  }
  let d = new Derived(42);
  assertSame(42, d.x);
})();

(function TestNullSuperPropertyLoad() {
  var obj = {
    __proto__: null,
    named() { return super.x },
    keyed() { return super[5] }
  };
  assertThrows(obj.named, TypeError);
  assertThrows(obj.keyed, TypeError);
  class C extends null {
    named() { return super.x }
    keyed() { return super[5] }
  }
  assertThrows(C.prototype.named, TypeError);
  assertThrows(C.prototype.keyed, TypeError);
})();

(function TestNullSuperPropertyStore() {
  var obj = {
    __proto__: null,
    named() { super.x = 42 },
    keyed() { super[5] = 42 }
  };
  assertThrows(obj.named, TypeError);
  assertThrows(obj.keyed, TypeError);
  class C extends null {
    named() { super.x = 42 }
    keyed() { super[5] = 42 }
  }
  assertThrows(C.prototype.named, TypeError);
  assertThrows(C.prototype.keyed, TypeError);
})();