(module
  (rec
    (type $super (sub (descriptor $super.desc) (struct)))
    (type $super.desc (sub (describes $super) (struct)))

    (type $sub (sub $super (descriptor $sub.desc) (struct)))
    (type $sub.desc (sub $super.desc (describes $sub) (struct)))
  )

  (global $super.desc1 (ref (exact $super.desc)) (struct.new $super.desc))
  (global $super.desc2 (ref (exact $super.desc)) (struct.new $super.desc))
  (global $super1 (ref $super) (struct.new_desc $super (global.get $super.desc1)))

  (global $sub.desc (ref (exact $sub.desc)) (struct.new $sub.desc))
  (global $sub (ref $sub) (struct.new_desc $sub (global.get $sub.desc)))

  ;; ref.cast_desc_eq (ref null ht)

  (func $ref.cast_desc_eq-null-unreachable (result anyref)
    (unreachable)
    (ref.cast_desc_eq (ref null $super))
  )
  (func $ref.cast_desc_eq-null-null (param anyref) (result anyref)
    (ref.cast_desc_eq (ref null $super)
      (ref.null none)
      (ref.null none)
    )
  )
  (func $ref.cast_desc_eq-null-upcast (param $sub (ref null $sub)) (param $super.desc (ref null $super.desc)) (result (ref null $super))
    (ref.cast_desc_eq (ref null $super)
      (local.get $sub)
      (local.get $super.desc)
    )
  )
  (func $ref.cast_desc_eq-null-exact (param $any anyref) (param $super.desc (ref null (exact $super.desc))) (result (ref null (exact $super)))
    ;; The cast type is exact because the descriptor is exact.
    (ref.cast_desc_eq (ref null (exact $super))
      (local.get $any)
      (local.get $super.desc)
    )
  )
  (func (export "cast-success")
    (drop
      (ref.cast_desc_eq (ref null $super)
        (global.get $super1)
        (global.get $super.desc1)
      )
    )
  )
  (func (export "cast-success-supertype")
    (drop
      (ref.cast_desc_eq (ref null $super)
        (global.get $sub)
        (global.get $sub.desc)
      )
    )
  )
  (func (export "cast-success-null")
    (drop
      (ref.cast_desc_eq (ref null $super)
        (ref.null none)
        (global.get $super.desc1)
      )
    )
  )
  (func (export "cast-fail-wrong-desc")
    (drop
      (ref.cast_desc_eq (ref null $super)
        (global.get $super1)
        (global.get $super.desc2)
      )
    )
  )
  (func (export "cast-fail-null-desc")
    (drop
      (ref.cast_desc_eq (ref null $super)
        (global.get $super1)
        (ref.null none)
      )
    )
  )

  ;; ref.cast_desc_eq (ref ht)

  (func $ref.cast_desc_eq-nn-unreachable (result anyref)
    (unreachable)
    (ref.cast_desc_eq (ref $super))
  )
  (func $ref.cast_desc_eq-nn-null (param anyref) (result anyref)
    (ref.cast_desc_eq (ref $super)
      (ref.null none)
      (ref.null none)
    )
  )
  (func $ref.cast_desc_eq-nn-upcast (param $sub (ref null $sub)) (param $super.desc (ref null $super.desc)) (result (ref null $super))
    (ref.cast_desc_eq (ref $super)
      (local.get $sub)
      (local.get $super.desc)
    )
  )
  (func $ref.cast_desc_eq-nn-exact (param $any anyref) (param $super.desc (ref null (exact $super.desc))) (result (ref null (exact $super)))
    ;; The cast type is exact because the descriptor is exact.
    (ref.cast_desc_eq (ref (exact $super))
      (local.get $any)
      (local.get $super.desc)
    )
  )
  (func (export "cast-nn-success")
    (drop
      (ref.cast_desc_eq (ref $super)
        (global.get $super1)
        (global.get $super.desc1)
      )
    )
  )
  (func (export "cast-nn-success-supertype")
    (drop
      (ref.cast_desc_eq (ref $super)
        (global.get $sub)
        (global.get $sub.desc)
      )
    )
  )
  (func (export "cast-nn-fail-null")
    (drop
      (ref.cast_desc_eq (ref $super)
        (ref.null none)
        (global.get $super.desc1)
      )
    )
  )
  (func (export "cast-nn-fail-wrong-desc")
    (drop
      (ref.cast_desc_eq (ref $super)
        (global.get $super1)
        (global.get $super.desc2)
      )
    )
  )
  (func (export "cast-nn-fail-null-desc")
    (drop
      (ref.cast_desc_eq (ref $super)
        (global.get $super1)
        (ref.null none)
      )
    )
  )

  (func (export "cast-branch-ref") (result i32)
    (drop
      (ref.cast_desc_eq (ref $super)
        (return (i32.const 1))
        (ref.null none)
      )
    )
    (i32.const 0)
  )
  (func (export "cast-branch-desc") (result i32)
    (drop
      (ref.cast_desc_eq (ref $super)
        (ref.null none)
        (return (i32.const 1))
      )
    )
    (i32.const 0)
  )

  (func (export "cast-i31ref")
    (drop
      (ref.cast_desc_eq (ref $super)
        (ref.i31 (i32.const 0))
        (struct.new $super.desc)
      )
    )
  )
)

(assert_return (invoke "cast-success"))
(assert_return (invoke "cast-success-supertype"))
(assert_return (invoke "cast-success-null"))
(assert_trap (invoke "cast-fail-wrong-desc") "cast error")
(assert_trap (invoke "cast-fail-null-desc") "null descriptor")
(assert_return (invoke "cast-nn-success"))
(assert_return (invoke "cast-nn-success-supertype"))
(assert_trap (invoke "cast-nn-fail-null") "cast error")
(assert_trap (invoke "cast-nn-fail-wrong-desc") "cast error")
(assert_trap (invoke "cast-nn-fail-null-desc") "null descriptor")
(assert_return (invoke "cast-branch-ref") (i32.const 1))
(assert_return (invoke "cast-branch-desc") (i32.const 1))
(assert_trap (invoke "cast-i31ref") "cast error")

(assert_malformed
  ;; Cast type must be a reference.
  (module quote "(module (func (unreachable) (ref.cast_desc_eq i32) (unreachable)))")
  "expected reftype"
)

(assert_invalid
  (module
    (type (struct))
    (func (result anyref)
      (unreachable)
      ;; Cannot do a descriptor cast to a type without a descriptor.
      (ref.cast_desc_eq (ref null 0))
    )
  )
  "cast target must have descriptor"
)

(assert_invalid
  (module
    (type (struct))
    (func (result anyref)
      (unreachable)
      ;; Cannot do a descriptor cast to a type without a descriptor.
      (ref.cast_desc_eq (ref 0))
    )
  )
  "cast target must have descriptor"
)

(assert_invalid
  (module
    (rec
      (type $super (sub (descriptor $super.desc) (struct)))
      (type $super.desc (sub (describes $super) (struct)))

      (type $sub (sub $super (descriptor $sub.desc) (struct)))
      (type $sub.desc (sub $super.desc (describes $sub) (struct)))
    )
    (func (param $super.desc (ref null $super.desc)) (result anyref)
      (ref.cast_desc_eq (ref null $sub)
        (local.get 0)
        ;; This should be a $sub.desc but it is a $super.desc.
        (local.get $super.desc)
      )
    )
  )
  "invalid type on stack"
)

(assert_invalid
  (module
    (rec
      (type $super (sub (descriptor $super.desc) (struct)))
      (type $super.desc (sub (describes $super) (struct)))

      (type $sub (sub $super (descriptor $sub.desc) (struct)))
      (type $sub.desc (sub $super.desc (describes $sub) (struct)))
    )
    (func (param $super.desc (ref null $super.desc)) (result anyref)
      (ref.cast_desc_eq (ref $sub)
        (local.get 0)
        ;; This should be a $sub.desc but it is a $super.desc.
        (local.get $super.desc)
      )
    )
  )
  "invalid type on stack"
)

(assert_invalid
  (module
    (rec
      (type $struct (descriptor $desc) (struct))
      (type $desc (describes $struct) (struct))
    )
    (func (param $any anyref) (param $desc (ref null $desc)) (result anyref)
      ;; The cast type cannot be exact because the descriptor is not exact.
      (ref.cast_desc_eq (ref null (exact $struct))
        (local.get $any)
        (local.get $desc)
      )
    )
  )
  "invalid type on stack"
)

(assert_invalid
  (module
    (rec
      (type $struct (descriptor $desc) (struct))
      (type $desc (describes $struct) (struct))
    )
    (func (param $any anyref) (param $desc (ref null $desc)) (result anyref)
      ;; The cast type cannot be exact because the descriptor is not exact.
      (ref.cast_desc_eq (ref (exact $struct))
        (local.get $any)
        (local.get $desc)
      )
    )
  )
  "invalid type on stack"
)
