;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.

;; RUN: foreach %s %t wasm-opt -all --closed-world --preserve-type-order \
;; RUN:     --unsubtyping --remove-unused-types -all -S -o - | filecheck %s

(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $super (sub (func)))
  (type $super (sub (func)))
  ;; CHECK:       (type $sub (sub $super (func)))
  (type $sub (sub $super (func)))
  ;; CHECK:       (type $cont (cont $super))
  (type $cont (cont $super))

  ;; CHECK:      (elem declare func $cont-new)

  ;; CHECK:      (func $cont-new (type $sub)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (cont.new $cont
  ;; CHECK-NEXT:    (ref.func $cont-new)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $cont-new (type $sub)
    (drop
      ;; This requires $sub <: $super.
      (cont.new $cont
        (ref.func $cont-new)
      )
    )
  )
)

(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $super (sub (struct)))
  (type $super (sub (struct)))
  ;; CHECK:       (type $sub (sub $super (struct)))
  (type $sub (sub $super (struct)))

  ;; CHECK:       (type $one (func (param (ref $super))))
  (type $one (func (param (ref $super))))
  ;; CHECK:       (type $none (func))
  (type $none (func))

  ;; CHECK:       (type $cont-one (cont $one))
  (type $cont-one (cont $one))
  ;; CHECK:       (type $cont-none (cont $none))
  (type $cont-none (cont $none))

  ;; CHECK:      (func $cont-bind (type $none)
  ;; CHECK-NEXT:  (local $one (ref null $cont-one))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (cont.bind $cont-one $cont-none
  ;; CHECK-NEXT:    (struct.new_default $sub)
  ;; CHECK-NEXT:    (local.get $one)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $cont-bind
    (local $one (ref null $cont-one))
    (drop
      ;; This requires $sub <: $super.
      (cont.bind $cont-one $cont-none
        (struct.new $sub)
        (local.get $one)
      )
    )
  )
)

(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $super (sub (struct)))
  (type $super (sub (struct)))
  ;; CHECK:       (type $sub (sub $super (struct)))
  (type $sub (sub $super (struct)))

  ;; CHECK:       (type $2 (func (param (ref null $super))))

  ;; CHECK:       (type $3 (func))

  ;; CHECK:      (tag $e (type $2) (param (ref null $super)))
  (tag $e (param (ref null $super)))

  ;; CHECK:      (func $suspend (type $3)
  ;; CHECK-NEXT:  (suspend $e
  ;; CHECK-NEXT:   (struct.new_default $sub)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $suspend
    ;; This requires $sub <: $super.
    (suspend $e
      (struct.new $sub)
    )
  )
)

(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $super (sub (struct)))
  (type $super (sub (struct)))
  ;; CHECK:       (type $sub (sub $super (struct)))
  (type $sub (sub $super (struct)))

  ;; CHECK:       (type $f (func (param (ref null $super))))
  (type $f (func (param (ref null $super))))
  ;; CHECK:       (type $cont (cont $f))
  (type $cont (cont $f))

  ;; CHECK:       (type $4 (func))

  ;; CHECK:      (func $resume-param (type $4)
  ;; CHECK-NEXT:  (local $cont (ref null $cont))
  ;; CHECK-NEXT:  (resume $cont
  ;; CHECK-NEXT:   (struct.new_default $sub)
  ;; CHECK-NEXT:   (local.get $cont)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $resume-param
    (local $cont (ref null $cont))
    ;; This requires $sub <: $super.
    (resume $cont
      (struct.new $sub)
      (local.get $cont)
    )
  )
)

(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $super (sub (struct)))
  (type $super (sub (struct)))
  ;; CHECK:       (type $sub (sub $super (struct)))
  (type $sub (sub $super (struct)))

  ;; CHECK:       (type $f (func))
  (type $f (func))
  ;; CHECK:       (type $cont (cont $f))
  (type $cont (cont $f))

  ;; CHECK:       (type $4 (func (param (ref null $sub))))

  ;; CHECK:       (type $5 (func (result (ref null $super) (ref null $cont))))

  ;; CHECK:      (tag $e (type $4) (param (ref null $sub)))
  (tag $e (param (ref null $sub)))

  ;; CHECK:      (func $resume-label (type $f)
  ;; CHECK-NEXT:  (local $cont (ref null $cont))
  ;; CHECK-NEXT:  (tuple.drop 2
  ;; CHECK-NEXT:   (block $l (type $5) (result (ref null $super) (ref null $cont))
  ;; CHECK-NEXT:    (resume $cont (on $e $l)
  ;; CHECK-NEXT:     (local.get $cont)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $resume-label
    (local $cont (ref null $cont))
    (tuple.drop 2
      (block $l (result (ref null $super) (ref null $cont))
        ;; Sending the tag parameter to the block requires $sub <: $super.
        (resume $cont (on $e $l)
          (local.get $cont)
        )
        (unreachable)
      )
    )
  )
)

(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $super (sub (struct)))
  (type $super (sub (struct)))
  ;; CHECK:       (type $sub (sub $super (struct)))
  (type $sub (sub $super (struct)))

  ;; CHECK:       (type $f (func))
  (type $f (func))
  ;; CHECK:       (type $next-f (func (param (ref null $sub))))
  (type $next-f (func (param (ref null $sub))))

  ;; CHECK:       (type $cont (cont $f))
  (type $cont (cont $f))
  ;; CHECK:       (type $next-cont (cont $next-f))
  (type $next-cont (cont $next-f))

  ;; CHECK:       (type $6 (func (result (ref null $super))))

  ;; CHECK:      (tag $e (type $6) (result (ref null $super)))
  (tag $e (result (ref null $super)))

  ;; CHECK:      (func $resume-tag (type $f)
  ;; CHECK-NEXT:  (local $cont (ref null $cont))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block $l (result (ref null $next-cont))
  ;; CHECK-NEXT:    (resume $cont (on $e $l)
  ;; CHECK-NEXT:     (local.get $cont)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (ref.null nocont)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $resume-tag
    (local $cont (ref null $cont))
    (drop
      (block $l (result (ref null $next-cont))
        ;; Based on the tag, the continuation expects a $super back. Based on
        ;; the type we give the next continuation, we will send it a $sub back.
        ;; This requires $sub <: $super.
        (resume $cont (on $e $l)
          (local.get $cont)
        )
        (ref.null nocont)
      )
    )
  )
)

(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $super (sub (struct)))
  (type $super (sub (struct)))
  ;; CHECK:       (type $sub (sub $super (struct)))
  (type $sub (sub $super (struct)))

  ;; CHECK:       (type $f (func (result (ref null $sub))))
  (type $f (func (result (ref null $sub))))
  ;; CHECK:       (type $next-f (func (result (ref null $super))))
  (type $next-f (func (result (ref null $super))))

  ;; CHECK:       (type $cont (cont $f))
  (type $cont (cont $f))
  ;; CHECK:       (type $next-cont (cont $next-f))
  (type $next-cont (cont $next-f))

  ;; CHECK:       (type $6 (func))

  ;; CHECK:      (tag $e (type $6))
  (tag $e)

  ;; CHECK:      (func $resume-result (type $6)
  ;; CHECK-NEXT:  (local $cont (ref null $cont))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block $l (result (ref null $next-cont))
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (resume $cont (on $e $l)
  ;; CHECK-NEXT:      (local.get $cont)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (ref.null nocont)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $resume-result
    (local $cont (ref null $cont))
    (drop
      (block $l (result (ref null $next-cont))
        (drop
          ;; The continuation we're resuming returns a $sub. In the next type we
          ;; give it, it returns a $super. This requires $sub <: $super.
          (resume $cont (on $e $l)
            (local.get $cont)
          )
        )
        (ref.null nocont)
      )
    )
  )
)

(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $super (sub (struct)))
  (type $super (sub (struct)))
  ;; CHECK:       (type $sub (sub $super (struct)))
  (type $sub (sub $super (struct)))

  ;; CHECK:       (type $f (func))
  (type $f (func))
  ;; CHECK:       (type $cont (cont $f))
  (type $cont (cont $f))

  ;; CHECK:       (type $4 (func (param (ref null $super))))

  ;; CHECK:      (tag $e (type $4) (param (ref null $super)))
  (tag $e (param (ref null $super)))

  ;; CHECK:      (func $resume-throw-param (type $f)
  ;; CHECK-NEXT:  (local $cont (ref null $cont))
  ;; CHECK-NEXT:  (resume_throw $cont $e
  ;; CHECK-NEXT:   (struct.new_default $sub)
  ;; CHECK-NEXT:   (local.get $cont)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $resume-throw-param
    (local $cont (ref null $cont))
    ;; This requires $sub <: $super
    (resume_throw $cont $e
      (struct.new $sub)
      (local.get $cont)
    )
  )
)

(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $super (sub (struct)))
  (type $super (sub (struct)))
  ;; CHECK:       (type $sub (sub $super (struct)))
  (type $sub (sub $super (struct)))

  ;; CHECK:       (type $f (func))
  (type $f (func))
  ;; CHECK:       (type $cont (cont $f))
  (type $cont (cont $f))

  ;; CHECK:       (type $4 (func (param (ref null $sub))))

  ;; CHECK:       (type $5 (func (result (ref null $super) (ref null $cont))))

  ;; CHECK:      (tag $e (type $4) (param (ref null $sub)))
  (tag $e (param (ref null $sub)))

  ;; CHECK:      (func $resume-throw-label (type $f)
  ;; CHECK-NEXT:  (local $cont (ref null $cont))
  ;; CHECK-NEXT:  (tuple.drop 2
  ;; CHECK-NEXT:   (block $l (type $5) (result (ref null $super) (ref null $cont))
  ;; CHECK-NEXT:    (resume_throw $cont $e (on $e $l)
  ;; CHECK-NEXT:     (ref.null none)
  ;; CHECK-NEXT:     (local.get $cont)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $resume-throw-label
    (local $cont (ref null $cont))
    (tuple.drop 2
      (block $l (result (ref null $super) (ref null $cont))
        ;; Sending the tag parameter to the block requires $sub <: $super.
        (resume_throw $cont $e (on $e $l)
          (ref.null none)
          (local.get $cont)
        )
        (unreachable)
      )
    )
  )
)

(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $super (sub (struct)))
  (type $super (sub (struct)))
  ;; CHECK:       (type $sub (sub $super (struct)))
  (type $sub (sub $super (struct)))

  (rec
    ;; CHECK:       (type $f (func (param (ref null $super) (ref null $ret-cont))))
    (type $f (func (param (ref null $super) (ref null $ret-cont))))
    ;; CHECK:       (type $cont (cont $f))
    (type $cont (cont $f))

    ;; CHECK:       (type $ret-f (func))
    (type $ret-f (func))
    ;; CHECK:       (type $ret-cont (cont $ret-f))
    (type $ret-cont (cont $ret-f))
  )

  ;; CHECK:       (type $6 (func))

  ;; CHECK:      (tag $e (type $6))
  (tag $e)

  ;; CHECK:      (func $switch-param (type $6)
  ;; CHECK-NEXT:  (local $cont (ref null $cont))
  ;; CHECK-NEXT:  (switch $cont $e
  ;; CHECK-NEXT:   (struct.new_default $sub)
  ;; CHECK-NEXT:   (local.get $cont)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $switch-param
    (local $cont (ref null $cont))
    ;; The continuation expects a $super, but we send it a $sub. This requires
    ;; $sub <: $super.
    (switch $cont $e
      (struct.new $sub)
      (local.get $cont)
    )
  )
)

(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $super (sub (struct)))
  (type $super (sub (struct)))
  ;; CHECK:       (type $sub (sub $super (struct)))
  (type $sub (sub $super (struct)))

  (rec
    ;; CHECK:       (type $f (func (param (ref null $ret-cont)) (result (ref null $sub))))
    (type $f (func (param (ref null $ret-cont)) (result (ref null $sub))))
    ;; CHECK:       (type $cont (cont $f))
    (type $cont (cont $f))

    ;; CHECK:       (type $ret-f (func (result (ref null $super))))
    (type $ret-f (func (result (ref null $super))))
    ;; CHECK:       (type $ret-cont (cont $ret-f))
    (type $ret-cont (cont $ret-f))
  )

  ;; CHECK:       (type $6 (func (result (ref null $super))))

  ;; CHECK:       (type $7 (func))

  ;; CHECK:      (tag $e (type $6) (result (ref null $super)))
  (tag $e (result (ref null $super)))

  ;; CHECK:      (func $switch-target-result (type $7)
  ;; CHECK-NEXT:  (local $cont (ref null $cont))
  ;; CHECK-NEXT:  (switch $cont $e
  ;; CHECK-NEXT:   (local.get $cont)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $switch-target-result
    (local $cont (ref null $cont))
    ;; The current continuation returns a $super (as indicated in the tag) and
    ;; the target continuation will return a $sub. This requires $sub <: $super.
    (switch $cont $e
      (local.get $cont)
    )
  )
)

(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $super (sub (struct)))
  (type $super (sub (struct)))
  ;; CHECK:       (type $sub (sub $super (struct)))
  (type $sub (sub $super (struct)))

  (rec
    ;; CHECK:       (type $f (func (param (ref null $ret-cont)) (result (ref null $sub))))
    (type $f (func (param (ref null $ret-cont)) (result (ref null $sub))))
    ;; CHECK:       (type $cont (cont $f))
    (type $cont (cont $f))

    ;; CHECK:       (type $ret-f (func (result (ref null $super))))
    (type $ret-f (func (result (ref null $super))))
    ;; CHECK:       (type $ret-cont (cont $ret-f))
    (type $ret-cont (cont $ret-f))
  )

  ;; CHECK:       (type $6 (func (result (ref null $sub))))

  ;; CHECK:       (type $7 (func))

  ;; CHECK:      (tag $e (type $6) (result (ref null $sub)))
  (tag $e (result (ref null $sub)))

  ;; CHECK:      (func $switch-return-result (type $7)
  ;; CHECK-NEXT:  (local $cont (ref null $cont))
  ;; CHECK-NEXT:  (switch $cont $e
  ;; CHECK-NEXT:   (local.get $cont)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $switch-return-result
    (local $cont (ref null $cont))
    ;; The current continuation returns a $sub (as indicated in the tag) and
    ;; after the switch it will be given the return continuation type returning
    ;; $super. This requires $sub <: $super.
    (switch $cont $e
      (local.get $cont)
    )
  )
)

;; Test we do not error on resume_throw_ref. It adds no subtyping constraints
;; between declared types besides those from the handlers.
(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $f (func))

  ;; CHECK:       (type $k (cont $f))

  ;; CHECK:      (elem declare func $no_handler)

  ;; CHECK:      (tag $e0 (type $f))
  (tag $e0)

  (type $f (func))
  (type $k (cont $f))

  ;; CHECK:      (func $no_handler (type $f)
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
  (func $no_handler
    (unreachable)
  )

  ;; CHECK:      (func $throw_unhandled_ref (type $f)
  ;; CHECK-NEXT:  (resume_throw_ref $k
  ;; CHECK-NEXT:   (block $h (result (ref exn))
  ;; CHECK-NEXT:    (try_table (catch_ref $e0 $h)
  ;; CHECK-NEXT:     (throw $e0)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (cont.new $k
  ;; CHECK-NEXT:    (ref.func $no_handler)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $throw_unhandled_ref
    (block $h (result exnref)
      (try_table (catch_ref $e0 $h) (throw $e0))
      (unreachable)
    )
    (resume_throw_ref $k (cont.new $k (ref.func $no_handler)))
  )
)

;; Test that processResumeHandlers correctly iterates handler tag parameters
;; using the inner loop variable. With two handlers where handler 1 has multiple
;; parameters, the subtyping constraint at param index 0 of handler 1 must be
;; noted (not the constraint at param index 1 repeated).
(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $super (sub (struct)))
  (type $super (sub (struct)))
  ;; CHECK:       (type $sub (sub $super (struct)))
  (type $sub (sub $super (struct)))

  ;; CHECK:       (type $f (func))
  (type $f (func))
  ;; CHECK:       (type $cont (cont $f))
  (type $cont (cont $f))

  ;; First tag has 1 param; second tag has 2 params with $sub at index 0.
  ;; CHECK:       (type $4 (func (param i32)))

  ;; CHECK:       (type $5 (func (param (ref null $sub) i32)))

  ;; CHECK:       (type $6 (func (result (ref null $super) i32 (ref null $cont))))

  ;; CHECK:       (type $7 (func (result i32 (ref null $cont))))

  ;; CHECK:      (tag $e0 (type $4) (param i32))
  (tag $e0 (param i32))
  ;; CHECK:      (tag $e1 (type $5) (param (ref null $sub) i32))
  (tag $e1 (param (ref null $sub) i32))

  ;; CHECK:      (func $resume-handler-multi-param (type $f)
  ;; CHECK-NEXT:  (local $c (ref null $cont))
  ;; CHECK-NEXT:  (tuple.drop 2
  ;; CHECK-NEXT:   (block $l0 (type $7) (result i32 (ref null $cont))
  ;; CHECK-NEXT:    (tuple.drop 3
  ;; CHECK-NEXT:     (block $l1 (type $6) (result (ref null $super) i32 (ref null $cont))
  ;; CHECK-NEXT:      (resume $cont (on $e0 $l0) (on $e1 $l1)
  ;; CHECK-NEXT:       (local.get $c)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:      (unreachable)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $resume-handler-multi-param
    (local $c (ref null $cont))
    (tuple.drop 2
      (block $l0 (result i32 (ref null $cont))
        (tuple.drop 3
          (block $l1 (result (ref null $super) i32 (ref null $cont))
            ;; Handler 1 sends (ref null $sub, i32) to $l1 which expects
            ;; (ref null $super, i32, ref null $cont). This requires
            ;; $sub <: $super at param index 0 of handler 1.
            (resume $cont (on $e0 $l0) (on $e1 $l1)
              (local.get $c)
            )
            (unreachable)
          )
        )
        (unreachable)
      )
    )
  )
)

