// |reftest| skip-if(release_or_beta)

const testCasesAnd = [];
const testCasesOr = [];
const testCasesCoalesce = [];


// Assignment to a global variable (JSOp::SetGName).
var globalVar;

function testAnd_GlobalVar(init, value) {
  globalVar = init;
  return [(globalVar &&= value), globalVar];
}
testCasesAnd.push(testAnd_GlobalVar);

function testOr_GlobalVar(init, value) {
  globalVar = init;
  return [(globalVar ||= value), globalVar];
}
testCasesOr.push(testOr_GlobalVar);

function testCoalesce_GlobalVar(init, value) {
  globalVar = init;
  return [(globalVar ??= value), globalVar];
}
testCasesCoalesce.push(testCoalesce_GlobalVar);


// Assignment to a local variable (JSOp::SetLocal).
function testAnd_LocalVar(init, value) {
  let v = init;
  return [(v &&= value), v];
}
testCasesAnd.push(testAnd_LocalVar);

function testOr_LocalVar(init, value) {
  let v = init;
  return [(v ||= value), v];
}
testCasesOr.push(testOr_LocalVar);

function testCoalesce_LocalVar(init, value) {
  let v = init;
  return [(v ??= value), v];
}
testCasesCoalesce.push(testCoalesce_LocalVar);


// Assignment to a parameter (JSOp::SetArg).
function testAnd_Arg(init, value) {
  function f(v) {
    return [(v &&= value), v];
  }
  return f(init);
}
testCasesAnd.push(testAnd_Arg);

function testOr_Arg(init, value) {
  function f(v) {
    return [(v ||= value), v];
  }
  return f(init);
}
testCasesOr.push(testOr_Arg);

function testCoalesce_Arg(init, value) {
  function f(v) {
    return [(v ??= value), v];
  }
  return f(init);
}
testCasesCoalesce.push(testCoalesce_Arg);


// Assignment to a closed over variable (JSOp::SetAliasedVar).
function testAnd_ClosedOver(init, value) {
  let v = init;
  function f() {
    return (v &&= value);
  }
  return [f(), v];
}
testCasesAnd.push(testAnd_ClosedOver);

function testOr_ClosedOver(init, value) {
  let v = init;
  function f() {
    return (v ||= value);
  }
  return [f(), v];
}
testCasesOr.push(testOr_ClosedOver);

function testCoalesce_ClosedOver(init, value) {
  let v = init;
  function f() {
    return (v ??= value);
  }
  return [f(), v];
}
testCasesCoalesce.push(testCoalesce_ClosedOver);


// Assignment to a dynamic name (JSOp::SetName).
function testAnd_DynamicName(init, value) {
  eval("var v = init;");
  return [(v &&= value), v];
}
testCasesAnd.push(testAnd_DynamicName);

function testOr_DynamicName(init, value) {
  eval("var v = init;");
  return [(v ||= value), v];
}
testCasesOr.push(testOr_DynamicName);

function testCoalesce_DynamicName(init, value) {
  eval("var v = init;");
  return [(v ??= value), v];
}
testCasesCoalesce.push(testCoalesce_DynamicName);


// Assignment to a property.
function testAnd_Property(init, value) {
  let obj = {p: init};
  return [(obj.p &&= value), obj.p];
}
testCasesAnd.push(testAnd_Property);

function testOr_Property(init, value) {
  let obj = {p: init};
  return [(obj.p ||= value), obj.p];
}
testCasesOr.push(testOr_Property);

function testCoalesce_Property(init, value) {
  let obj = {p: init};
  return [(obj.p ??= value), obj.p];
}
testCasesCoalesce.push(testCoalesce_Property);


// Assignment to a super property.
function testAnd_SuperProperty(init, value) {
  let proto = {p: init};
  let obj = {__proto__: proto, m() { return (super.p &&= value); }};
  return [obj.m(), obj.p];
}
testCasesAnd.push(testAnd_SuperProperty);

function testOr_SuperProperty(init, value) {
  let proto = {p: init};
  let obj = {__proto__: proto, m() { return (super.p ||= value); }};
  return [obj.m(), obj.p];
}
testCasesOr.push(testOr_SuperProperty);

function testCoalesce_SuperProperty(init, value) {
  let proto = {p: init};
  let obj = {__proto__: proto, m() { return (super.p ??= value); }};
  return [obj.m(), obj.p];
}
testCasesCoalesce.push(testCoalesce_SuperProperty);


// Assignment to an element.
function testAnd_Element(init, value) {
  let p = 123;
  let obj = {[p]: init};
  return [(obj[p] &&= value), obj[p]];
}
testCasesAnd.push(testAnd_Element);

function testOr_Element(init, value) {
  let p = 123;
  let obj = {[p]: init};
  return [(obj[p] ||= value), obj[p]];
}
testCasesOr.push(testOr_Element);

function testCoalesce_Element(init, value) {
  let p = 123;
  let obj = {[p]: init};
  return [(obj[p] ??= value), obj[p]];
}
testCasesCoalesce.push(testCoalesce_Element);


// Assignment to a super element.
function testAnd_SuperElement(init, value) {
  let p = 123;
  let proto = {[p]: init};
  let obj = {__proto__: proto, m() { return (super[p] &&= value); }};
  return [obj.m(), obj[p]];
}
testCasesAnd.push(testAnd_SuperElement);

function testOr_SuperElement(init, value) {
  let p = 123;
  let proto = {[p]: init};
  let obj = {__proto__: proto, m() { return (super[p] ||= value); }};
  return [obj.m(), obj[p]];
}
testCasesOr.push(testOr_SuperElement);

function testCoalesce_SuperElement(init, value) {
  let p = 123;
  let proto = {[p]: init};
  let obj = {__proto__: proto, m() { return (super[p] ??= value); }};
  return [obj.m(), obj[p]];
}
testCasesCoalesce.push(testCoalesce_SuperElement);


// Run the actual tests.

function runTest(testCases, init, value, expected) {
  for (let f of testCases) {
    let [result, newValue] = f(init, value);

    assertEq(result, expected);
    assertEq(newValue, expected);
  }
}

function testAnd(init, value) {
  const expected = init ? value : init;
  runTest(testCasesAnd, init, value, expected);
}

function testOr(init, value) {
  const expected = !init ? value : init;
  runTest(testCasesOr, init, value, expected);
}

function testCoalesce(init, value) {
  const expected = init === undefined || init === null ? value : init;
  runTest(testCasesCoalesce, init, value, expected);
}


// Repeat a number of times to ensure JITs can kick in, too.
for (let i = 0; i < 50; ++i) {
  for (let thruthy of [true, 123, 123n, "hi", [], Symbol()]) {
    testAnd(thruthy, "pass");
    testOr(thruthy, "fail");
    testCoalesce(thruthy, "fail");
  }

  for (let falsy of [false, 0, NaN, 0n, ""]) {
    testAnd(falsy, "fail");
    testOr(falsy, "pass");
    testCoalesce(falsy, "fail");
  }

  for (let nullOrUndefined of [null, undefined]) {
    testAnd(nullOrUndefined, "fail");
    testOr(nullOrUndefined, "pass");
    testCoalesce(nullOrUndefined, "pass");
  }
}


if (typeof reportCompare === "function")
  reportCompare(0, 0);
