// The shape teleporting optimization can be disabled for an object that's used
// as prototype when either it's involved in prototype changes or it had a property
// shadowed on another prototype object.

let usingDictionary = getPrefValue("experimental.dictionary_teleporting");

if (usingDictionary) {
  // Use this to cause us to exceed the reshape limit to ensure we test the
  // invalidation path.
  function exceed_dictionary_limit() {
    let o = Object.create(null);
    o = Object.create(o);
    o = Object.create(o);
    let l = o;
    o = Object.create(o);
    let u = o;
    o = Object.create(o);
    o = Object.create(o);

    print("Attempt")
    for (let i = 0; i < 10_000; i++) {
      Object.setPrototypeOf(u, null);
      Object.setPrototypeOf(u, l);
    }
  }
  exceed_dictionary_limit();
}

function changeProps(o) {
  Object.assign(o, { x: 1, y: 2, z: 3 });
  o.foo = 4;
  delete o.x;
}

function testProtoChange() {
  var receiver = {};
  var A = Object.create(null);
  var B = Object.create(A);

  // Change |receiver|'s proto: receiver => B => A => null
  // Because |receiver| is not used as a prototype object, this doesn't affect
  // teleporting.
  Object.setPrototypeOf(receiver, B);
  assertEq(hasInvalidatedTeleporting(receiver), false);
  assertEq(hasInvalidatedTeleporting(A), false);
  assertEq(hasInvalidatedTeleporting(B), false);

  // Change B's proto to C: receiver => B => C => null
  // Because B is used as prototype object, both A and B invalidate teleporting.
  var C = Object.create(null);
  Object.setPrototypeOf(B, C);
  assertEq(hasInvalidatedTeleporting(receiver), false);
  assertEq(hasInvalidatedTeleporting(A), true);
  assertEq(hasInvalidatedTeleporting(B), true);
  assertEq(hasInvalidatedTeleporting(C), false);

  // Change B's proto a second time: receiver => B => D => null
  // Now C has teleporting invalidated too.
  var D = Object.create(null);
  Object.setPrototypeOf(B, D);
  assertEq(hasInvalidatedTeleporting(receiver), false);
  assertEq(hasInvalidatedTeleporting(A), true);
  assertEq(hasInvalidatedTeleporting(B), true);
  assertEq(hasInvalidatedTeleporting(C), true);
  assertEq(hasInvalidatedTeleporting(D), false);

  // Changing properties (without shadowing) must not affect teleporting state.
  changeProps(C);
  changeProps(D);
  assertEq(hasInvalidatedTeleporting(C), true);
  assertEq(hasInvalidatedTeleporting(D), false);
}
testProtoChange();

function testShadowingProp() {
  // receiver => C => B => A => null
  var A = Object.create(null);
  var B = Object.create(A);
  var C = Object.create(B);
  var receiver = Object.create(C);

  // Adding non-shadowing properties doesn't affect teleporting.
  A.a = 1;
  B.b = 1;
  C.c = 1;
  receiver.receiver = 1;
  assertEq(hasInvalidatedTeleporting(receiver), false);
  assertEq(hasInvalidatedTeleporting(C), false);
  assertEq(hasInvalidatedTeleporting(B), false);
  assertEq(hasInvalidatedTeleporting(A), false);

  // Objects not used as prototype can shadow properties without affecting
  // teleporting.
  receiver.a = 1;
  receiver.b = 2;
  receiver.c = 3;
  assertEq(hasInvalidatedTeleporting(receiver), false);
  assertEq(hasInvalidatedTeleporting(C), false);
  assertEq(hasInvalidatedTeleporting(B), false);
  assertEq(hasInvalidatedTeleporting(A), false);

  // Shadowing a property of B on C invalidates teleporting for B.
  C.b = 1;
  assertEq(hasInvalidatedTeleporting(receiver), false);
  assertEq(hasInvalidatedTeleporting(C), false);
  assertEq(hasInvalidatedTeleporting(B), true);
  assertEq(hasInvalidatedTeleporting(A), false);

  // Shadowing a property of A on C invalidates teleporting for A.
  C.a = 2;
  assertEq(hasInvalidatedTeleporting(receiver), false);
  assertEq(hasInvalidatedTeleporting(C), false);
  assertEq(hasInvalidatedTeleporting(B), true);
  assertEq(hasInvalidatedTeleporting(A), true);

  // Changing properties (without shadowing) must not affect teleporting state.
  changeProps(C);
  changeProps(B);
  assertEq(hasInvalidatedTeleporting(C), false);
  assertEq(hasInvalidatedTeleporting(B), true);
}
testShadowingProp();

function testShadowingPropStopsAtFirst() {
  // receiver => C => B{x,y} => A{x,y,z} => null
  var A = Object.create(null);
  A.x = 1;
  A.y = 2;
  A.z = 3;
  var B = Object.create(A);
  B.x = 1;
  B.y = 2;
  var C = Object.create(B);
  var receiver = Object.create(C);

  // Teleporting is supported.
  assertEq(hasInvalidatedTeleporting(receiver), false);
  assertEq(hasInvalidatedTeleporting(C), false);
  assertEq(hasInvalidatedTeleporting(B), false);
  assertEq(hasInvalidatedTeleporting(A), false);

  // Shadowing a property of B (and A) on C.
  // This invalidates teleporting for B but not for A, because the search stops
  // at B.
  C.x = 1;
  assertEq(hasInvalidatedTeleporting(receiver), false);
  assertEq(hasInvalidatedTeleporting(C), false);
  assertEq(hasInvalidatedTeleporting(B), true);
  assertEq(hasInvalidatedTeleporting(A), false);

  // "y" is similar.
  C.y = 2;
  assertEq(hasInvalidatedTeleporting(receiver), false);
  assertEq(hasInvalidatedTeleporting(C), false);
  assertEq(hasInvalidatedTeleporting(B), true);
  assertEq(hasInvalidatedTeleporting(A), false);

  // "z" is only defined on A, so now A is affected.
  C.z = 3;
  assertEq(hasInvalidatedTeleporting(receiver), false);
  assertEq(hasInvalidatedTeleporting(C), false);
  assertEq(hasInvalidatedTeleporting(B), true);
  assertEq(hasInvalidatedTeleporting(A), true);
}
testShadowingPropStopsAtFirst();

// Ensure teleporting properties on Object.prototype is still possible.
assertEq(hasInvalidatedTeleporting(Object.prototype), false);
