// |jit-test| skip-if: !wasmGcEnabled()

function simpleTypeSection(types) {
  return types.map((x, i) => `(type \$${i} ${x})`).join('\n');
}

function assertSubtype(superType, subType, types) {
  types = types || [];
  wasmEvalText(`(module
    ${types}
    (func
      unreachable
      (block (param ${subType})
        (block (param ${superType})
          drop
        )
      )
    )
  )`);
}

function assertNotSubtype(superType, subType, types) {
  assertErrorMessage(() => {
    assertSubtype(superType, subType, types);
  }, WebAssembly.CompileError, /type mismatch/);
}

// Primitive trivial subtyping
assertSubtype('i32', 'i32');
assertSubtype('i64', 'i64');
assertSubtype('f32', 'f32');
assertSubtype('f64', 'f64');
assertSubtype('eqref', 'eqref');
assertSubtype('funcref', 'funcref');

// No subtyping relation between funcref, anyref, externref. These are our top
// types.
assertNotSubtype('funcref', 'anyref');
assertNotSubtype('anyref', 'funcref');
assertNotSubtype('funcref', 'externref');
assertNotSubtype('externref', 'funcref');
assertNotSubtype('externref', 'anyref');
assertNotSubtype('anyref', 'externref');

// eqref is a subtype of anyref
assertSubtype('anyref', 'eqref');

// structref is a subtype of eqref and anyref
assertSubtype('anyref', 'structref');
assertSubtype('eqref', 'structref');

// arrayref is a subtype of eqref and anyref
assertSubtype('anyref', 'arrayref');
assertSubtype('eqref', 'arrayref');

// Structs are subtypes of anyref, eqref, and structref
assertSubtype(
 'anyref',
 '(ref 0)',
 simpleTypeSection(['(struct)']));
assertSubtype(
 'eqref',
 '(ref 0)',
 simpleTypeSection(['(struct)']));
assertSubtype(
 'structref',
 '(ref 0)',
 simpleTypeSection(['(struct)']));

// Struct identity
assertSubtype(
 '(ref 0)',
 '(ref 1)',
 simpleTypeSection(['(struct)', '(struct)']));
assertSubtype(
 '(ref 1)',
 '(ref 0)',
 simpleTypeSection(['(struct)', '(struct)']));

// Self referential struct
assertSubtype(
 '(ref 1)',
 '(ref 0)',
 simpleTypeSection(['(struct (ref 0))', '(struct (ref 1))']));

// Mutually referential structs
assertSubtype(
 '(ref 2)',
 '(ref 0)',
 `(rec
    (type (struct (ref 1)))
    (type (struct (ref 0)))
  )
  (rec
    (type (struct (ref 3)))
    (type (struct (ref 2)))
  )`);

// Struct subtypes can have extra fields
assertSubtype(
  '(ref 0)',
  '(ref 1)',
  `(type (struct))
   (type (sub 0 (struct (field i32))))`);
assertSubtype(
  '(ref 0)',
  '(ref 1)',
  `(type (struct))
   (type (sub 0 (struct (field i32) (field i32))))`);

// Struct supertypes cannot have extra fields
assertNotSubtype(
  '(ref 0)',
  '(ref 1)',
  simpleTypeSection([
    '(struct (field i32))',
    '(struct)']));

// Struct field mutability must match
assertSubtype(
  '(ref 0)',
  '(ref 1)',
  simpleTypeSection([
    '(struct (field (mut i32)))',
    '(struct (field (mut i32)))']));
assertSubtype(
  '(ref 0)',
  '(ref 1)',
  simpleTypeSection([
    '(struct (field i32))',
    '(struct (field i32))']));
assertNotSubtype(
  '(ref 0)',
  '(ref 1)',
  simpleTypeSection([
    '(struct (field (mut i32)))',
    '(struct (field i32))']));
assertNotSubtype(
  '(ref 0)',
  '(ref 1)',
  simpleTypeSection([
    '(struct (field i32))',
    '(struct (field (mut i32)))']));

// Struct fields are invariant when mutable
assertSubtype(
  '(ref 2)',
  '(ref 3)',
  simpleTypeSection([
    '(struct)',
    '(struct)',
    '(struct (field (mut (ref 0))))',
    '(struct (field (mut (ref 1))))']));
assertNotSubtype(
  '(ref 2)',
  '(ref 3)',
  simpleTypeSection([
    '(struct)',
    '(struct (field i32))',
    '(struct (field (mut (ref 0))))',
    '(struct (field (mut (ref 1))))']));

// Struct fields are covariant when immutable
assertSubtype(
  '(ref 2)',
  '(ref 3)',
  `(type (struct))
   (type (sub 0 (struct (field i32))))
   (type (struct (field (ref 0))))
   (type (sub 2 (struct (field (ref 1)))))`);

// Arrays are subtypes of anyref, eqref, and arrayref
assertSubtype(
 'anyref',
 '(ref 0)',
 simpleTypeSection(['(array i32)']));
assertSubtype(
 'eqref',
 '(ref 0)',
 simpleTypeSection(['(array i32)']));
assertSubtype(
 'arrayref',
 '(ref 0)',
 simpleTypeSection(['(array i32)']));

// Array identity
assertSubtype(
 '(ref 0)',
 '(ref 1)',
 simpleTypeSection(['(array i32)', '(array i32)']));
assertSubtype(
 '(ref 1)',
 '(ref 0)',
 simpleTypeSection(['(array i32)', '(array i32)']));

// Self referential array
assertSubtype(
 '(ref 1)',
 '(ref 0)',
 simpleTypeSection(['(array (ref 0))', '(array (ref 1))']));

// Mutually referential arrays
assertSubtype(
 '(ref 2)',
 '(ref 0)',
 `(rec
   (type (array (ref 1)))
   (type (array (ref 0)))
  )
  (rec
   (type (array (ref 3)))
   (type (array (ref 2)))
  )`);

// Array mutability must match
assertSubtype(
  '(ref 0)',
  '(ref 1)',
  simpleTypeSection([
    '(array (mut i32))',
    '(array (mut i32))']));
assertSubtype(
  '(ref 0)',
  '(ref 1)',
  simpleTypeSection([
    '(array i32)',
    '(array i32)']));
assertNotSubtype(
  '(ref 0)',
  '(ref 1)',
  simpleTypeSection([
    '(array (mut i32))',
    '(array i32)']));
assertNotSubtype(
  '(ref 0)',
  '(ref 1)',
  simpleTypeSection([
    '(array i32)',
    '(array (mut i32))']));

// Array elements are invariant when mutable
assertSubtype(
  '(ref 2)',
  '(ref 3)',
  simpleTypeSection([
   '(struct)',
   '(struct)',
   '(array (mut (ref 0)))',
   '(array (mut (ref 1)))']));
assertNotSubtype(
  '(ref 2)',
  '(ref 3)',
  simpleTypeSection([
   '(struct)',
   '(struct (field i32))',
   '(array (mut (ref 0)))',
   '(array (mut (ref 1)))']));

// Array elements are covariant when immutable
assertSubtype(
  '(ref 2)',
  '(ref 3)',
  `(type (struct))
   (type (sub 0 (struct (field i32))))
   (type (array (ref 0)))
   (type (sub 2 (array (ref 1))))`);

// nullref is a subtype of everything in anyref hierarchy
assertSubtype('anyref', 'nullref');
assertSubtype('eqref', 'nullref');
assertSubtype('structref', 'nullref');
assertSubtype('arrayref', 'nullref');
assertSubtype('(ref null 0)', 'nullref', simpleTypeSection(['(struct)']));
assertSubtype('(ref null 0)', 'nullref', simpleTypeSection(['(array i32)']));

// nullref is not a subtype of any other hierarchy
assertNotSubtype('funcref', 'nullref');
assertNotSubtype('(ref null 0)', 'nullref', simpleTypeSection(['(func)']));
assertNotSubtype('externref', 'nullref');

// nullfuncref is a subtype of everything in funcref hierarchy
assertSubtype('funcref', 'nullfuncref');
assertSubtype('(ref null 0)', 'nullfuncref', simpleTypeSection(['(func)']));

// nullfuncref is not a subtype of any other hierarchy
assertNotSubtype('anyref', 'nullfuncref');
assertNotSubtype('eqref', 'nullfuncref');
assertNotSubtype('structref', 'nullfuncref');
assertNotSubtype('arrayref', 'nullfuncref');
assertNotSubtype('externref', 'nullfuncref');
assertNotSubtype('(ref null 0)', 'nullfuncref', simpleTypeSection(['(struct)']));
assertNotSubtype('(ref null 0)', 'nullfuncref', simpleTypeSection(['(array i32)']));

// nullexternref is a subtype of everything in externref hierarchy
assertSubtype('externref', 'nullexternref');

// nullexternref is not a subtype of any other hierarchy
assertNotSubtype('anyref', 'nullexternref');
assertNotSubtype('eqref', 'nullexternref');
assertNotSubtype('structref', 'nullexternref');
assertNotSubtype('arrayref', 'nullexternref');
assertNotSubtype('funcref', 'nullexternref');
assertNotSubtype('(ref null 0)', 'nullexternref', simpleTypeSection(['(struct)']));
assertNotSubtype('(ref null 0)', 'nullexternref', simpleTypeSection(['(array i32)']));
assertNotSubtype('(ref null 0)', 'nullexternref', simpleTypeSection(['(func)']));
