;; 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:     --type-refining -S -o - | filecheck %s

(module
  ;; A struct with three fields. The first will have no writes, the second one
  ;; write of the same type, and the last a write of a subtype, which will allow
  ;; us to specialize that one.
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $struct (sub (struct (field (mut anyref)) (field (mut (ref i31))) (field (mut (ref i31))))))
  (type $struct (sub (struct (field (mut anyref)) (field (mut (ref i31))) (field (mut anyref)))))

  ;; CHECK:       (type $1 (func (param (ref $struct))))

  ;; CHECK:      (func $work (type $1) (param $struct (ref $struct))
  ;; CHECK-NEXT:  (struct.set $struct 1
  ;; CHECK-NEXT:   (local.get $struct)
  ;; CHECK-NEXT:   (ref.i31
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (struct.set $struct 2
  ;; CHECK-NEXT:   (local.get $struct)
  ;; CHECK-NEXT:   (ref.i31
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $struct 2
  ;; CHECK-NEXT:    (local.get $struct)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $work (param $struct (ref $struct))
    (struct.set $struct 1
      (local.get $struct)
      (ref.i31 (i32.const 0))
    )
    (struct.set $struct 2
      (local.get $struct)
      (ref.i31 (i32.const 0))
    )
    (drop
      ;; The type of this struct.get must be updated after the field's type
      ;; changes, or the validator will complain.
      (struct.get $struct 2
        (local.get $struct)
      )
    )
  )
)

(module
  ;; A struct with a nullable field and a write of a non-nullable value. We
  ;; must keep the type nullable, unlike in the previous module, due to the
  ;; default value being null.

  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $struct (sub (struct (field (mut i31ref)))))
  (type $struct (sub (struct (field (mut anyref)))))

  ;; CHECK:       (type $1 (func (param (ref $struct))))

  ;; CHECK:      (func $work (type $1) (param $struct (ref $struct))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new_default $struct)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (struct.set $struct 0
  ;; CHECK-NEXT:   (local.get $struct)
  ;; CHECK-NEXT:   (ref.i31
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $work (param $struct (ref $struct))
    (drop
      (struct.new_default $struct)
    )
    (struct.set $struct 0
      (local.get $struct)
      (ref.i31 (i32.const 0))
    )
  )
)

(module
  ;; Multiple writes to a field, with a LUB that is not equal to any of them.
  ;; We can at least improve from structref to a ref of $struct here. Note also
  ;; that we do so in all three types, not just the parent to which we write
  ;; (the children have no writes, but must still be updated).

  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $struct (sub (struct (field (mut (ref $struct))))))
    (type $struct (sub (struct (field (mut structref)))))

    ;; CHECK:       (type $child-A (sub $struct (struct (field (mut (ref $struct))))))
    (type $child-A (sub $struct (struct (field (mut structref)))))

    ;; CHECK:       (type $child-B (sub $struct (struct (field (mut (ref $struct))))))
    (type $child-B (sub $struct (struct (field (mut structref)))))
  )

  ;; CHECK:       (type $3 (func (param (ref $struct) (ref $child-A) (ref $child-B))))

  ;; CHECK:      (func $work (type $3) (param $struct (ref $struct)) (param $child-A (ref $child-A)) (param $child-B (ref $child-B))
  ;; CHECK-NEXT:  (struct.set $struct 0
  ;; CHECK-NEXT:   (local.get $struct)
  ;; CHECK-NEXT:   (local.get $child-A)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (struct.set $struct 0
  ;; CHECK-NEXT:   (local.get $struct)
  ;; CHECK-NEXT:   (local.get $child-B)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $work (param $struct (ref $struct)) (param $child-A (ref $child-A)) (param $child-B (ref $child-B))
    (struct.set $struct 0
      (local.get $struct)
      (local.get $child-A)
    )
    (struct.set $struct 0
      (local.get $struct)
      (local.get $child-B)
    )
  )
)

(module
  ;; As above, but all writes are of $child-A, which allows more optimization
  ;; up to that type.

  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $struct (sub (struct (field (mut (ref $child-A))))))
    (type $struct (sub (struct (field (mut structref)))))

    ;; CHECK:       (type $child-A (sub $struct (struct (field (mut (ref $child-A))))))
    (type $child-A (sub $struct (struct (field (mut structref)))))

    ;; CHECK:       (type $child-B (sub $struct (struct (field (mut (ref $child-A))))))
    (type $child-B (sub $struct (struct (field (mut structref)))))
  )

  ;; CHECK:       (type $3 (func (param (ref $struct) (ref $child-A))))

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

  ;; CHECK:      (func $work (type $3) (param $struct (ref $struct)) (param $child-A (ref $child-A))
  ;; CHECK-NEXT:  (struct.set $struct 0
  ;; CHECK-NEXT:   (local.get $struct)
  ;; CHECK-NEXT:   (local.get $child-A)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (struct.set $struct 0
  ;; CHECK-NEXT:   (local.get $struct)
  ;; CHECK-NEXT:   (local.get $child-A)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $work (param $struct (ref $struct)) (param $child-A (ref $child-A))
    (struct.set $struct 0
      (local.get $struct)
      (local.get $child-A)
    )
    (struct.set $struct 0
      (local.get $struct)
      (local.get $child-A)
    )
  )

  ;; CHECK:      (func $keepalive (type $4)
  ;; CHECK-NEXT:  (local $temp (ref null $child-B))
  ;; CHECK-NEXT: )
  (func $keepalive
   ;; Add a reference to $child-B just to keep it alive in the output for easier
   ;; comparisons to the previous testcase. Note that $child-B's field will be
   ;; refined, because its parent $struct forces it to be.
   (local $temp (ref null $child-B))
  )
)

(module
  ;; Write to the parent a child, and to the child a parent. The write to the
  ;; child prevents specialization even in the parent and we only improve up to
  ;; $struct but not to $child.

  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $struct (sub (struct (field (mut (ref $struct))))))
  (type $struct (sub (struct (field (mut structref)))))

  ;; CHECK:       (type $child (sub $struct (struct (field (mut (ref $struct))))))
  (type $child (sub $struct (struct (field (mut structref)))))

  ;; CHECK:       (type $2 (func (param (ref $struct) (ref $child))))

  ;; CHECK:      (func $work (type $2) (param $struct (ref $struct)) (param $child (ref $child))
  ;; CHECK-NEXT:  (struct.set $struct 0
  ;; CHECK-NEXT:   (local.get $struct)
  ;; CHECK-NEXT:   (local.get $child)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (struct.set $child 0
  ;; CHECK-NEXT:   (local.get $child)
  ;; CHECK-NEXT:   (local.get $struct)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $work (param $struct (ref $struct)) (param $child (ref $child))
    (struct.set $struct 0
      (local.get $struct)
      (local.get $child)
    )
    (struct.set $child 0
      (local.get $child)
      (local.get $struct)
    )
  )
)

(module
  ;; As above, but both writes are of $child, so we can optimize.

  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $struct (sub (struct (field (mut (ref $child))))))
  (type $struct (sub (struct (field (mut structref)))))

  ;; CHECK:       (type $child (sub $struct (struct (field (mut (ref $child))))))
  (type $child (sub $struct (struct (field (mut structref)))))

  ;; CHECK:       (type $2 (func (param (ref $struct) (ref $child))))

  ;; CHECK:      (func $work (type $2) (param $struct (ref $struct)) (param $child (ref $child))
  ;; CHECK-NEXT:  (struct.set $struct 0
  ;; CHECK-NEXT:   (local.get $struct)
  ;; CHECK-NEXT:   (local.get $child)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (struct.set $child 0
  ;; CHECK-NEXT:   (local.get $child)
  ;; CHECK-NEXT:   (local.get $child)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $work (param $struct (ref $struct)) (param $child (ref $child))
    (struct.set $struct 0
      (local.get $struct)
      (local.get $child)
    )
    (struct.set $child 0
      (local.get $child)
      (local.get $child)
    )
  )
)

(module
  ;; As in 2 testcases ago, write to the parent a child, and to the child a
  ;; parent, but now the writes happen in struct.new. Even with that precise
  ;; info, however, we can't make the parent field more specific than the
  ;; child's.

  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $struct (sub (struct (field (mut (ref $struct))))))
  (type $struct (sub (struct (field (mut structref)))))

  ;; CHECK:       (type $child (sub $struct (struct (field (mut (ref $struct))))))
  (type $child (sub $struct (struct (field (mut structref)))))

  ;; CHECK:       (type $2 (func (param (ref $struct) (ref $child))))

  ;; CHECK:      (func $work (type $2) (param $struct (ref $struct)) (param $child (ref $child))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new $struct
  ;; CHECK-NEXT:    (local.get $child)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new $child
  ;; CHECK-NEXT:    (local.get $struct)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $work (param $struct (ref $struct)) (param $child (ref $child))
    (drop
      (struct.new $struct
        (local.get $child)
      )
    )
    (drop
      (struct.new $child
        (local.get $struct)
      )
    )
  )
)

(module
  ;; Write a parent to the parent and a child to the child. We can specialize
  ;; each of them to contain their own type. This tests that we are aware that
  ;; a struct.new is of a precise type, which means that seeing a type written
  ;; to a parent does not limit specialization in a child.
  ;;
  ;; (Note that we can't do a similar test with struct.set, as that would
  ;; imply the fields are mutable, which limits optimization, see the next
  ;; testcase after this.)

  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $struct (sub (struct (field (ref $struct)))))
  (type $struct (sub (struct (field structref))))

  ;; CHECK:       (type $child (sub $struct (struct (field (ref $child)))))
  (type $child (sub $struct (struct (field structref))))

  ;; CHECK:       (type $2 (func (param (ref $struct) (ref $child))))

  ;; CHECK:      (func $work (type $2) (param $struct (ref $struct)) (param $child (ref $child))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new $struct
  ;; CHECK-NEXT:    (local.get $struct)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new $child
  ;; CHECK-NEXT:    (local.get $child)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $work (param $struct (ref $struct)) (param $child (ref $child))
    (drop
      (struct.new $struct
        (local.get $struct)
      )
    )
    (drop
      (struct.new $child
        (local.get $child)
      )
    )
  )
)

(module
  ;; As above, but the fields are mutable. We cannot specialize them to
  ;; different types in this case, and both will become $struct (still an
  ;; improvement!)

  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $struct (sub (struct (field (mut (ref $struct))))))
  (type $struct (sub (struct (field (mut structref)))))

  ;; CHECK:       (type $child (sub $struct (struct (field (mut (ref $struct))))))
  (type $child (sub $struct (struct (field (mut structref)))))

  ;; CHECK:       (type $2 (func (param (ref $struct) (ref $child))))

  ;; CHECK:      (func $work (type $2) (param $struct (ref $struct)) (param $child (ref $child))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new $struct
  ;; CHECK-NEXT:    (local.get $struct)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new $child
  ;; CHECK-NEXT:    (local.get $child)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $work (param $struct (ref $struct)) (param $child (ref $child))
    (drop
      (struct.new $struct
        (local.get $struct)
      )
    )
    (drop
      (struct.new $child
        (local.get $child)
      )
    )
  )
)

(module
  ;; As above, but the child also has a new field that is not in the parent. In
  ;; that case there is nothing stopping us from specializing that new field
  ;; to $child.

  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $struct (sub (struct (field (mut (ref $struct))))))
  (type $struct (sub (struct (field (mut structref)))))

  ;; CHECK:       (type $child (sub $struct (struct (field (mut (ref $struct))) (field (mut (ref $child))))))
  (type $child (sub $struct (struct (field (mut structref)) (field (mut structref)))))

  ;; CHECK:       (type $2 (func (param (ref $struct) (ref $child))))

  ;; CHECK:      (func $work (type $2) (param $struct (ref $struct)) (param $child (ref $child))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new $struct
  ;; CHECK-NEXT:    (local.get $struct)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new $child
  ;; CHECK-NEXT:    (local.get $child)
  ;; CHECK-NEXT:    (local.get $child)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $work (param $struct (ref $struct)) (param $child (ref $child))
    (drop
      (struct.new $struct
        (local.get $struct)
      )
    )
    (drop
      (struct.new $child
        (local.get $child)
        (local.get $child)
      )
    )
  )
)

(module
  ;; A copy of a field does not prevent optimization (even though it assigns
  ;; the old type).

  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $struct (sub (struct (field (mut (ref $struct))))))
  (type $struct (sub (struct (field (mut structref)))))

  ;; CHECK:       (type $1 (func (param (ref $struct))))

  ;; CHECK:      (func $work (type $1) (param $struct (ref $struct))
  ;; CHECK-NEXT:  (struct.set $struct 0
  ;; CHECK-NEXT:   (local.get $struct)
  ;; CHECK-NEXT:   (local.get $struct)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (struct.set $struct 0
  ;; CHECK-NEXT:   (local.get $struct)
  ;; CHECK-NEXT:   (struct.get $struct 0
  ;; CHECK-NEXT:    (local.get $struct)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $work (param $struct (ref $struct))
    (struct.set $struct 0
      (local.get $struct)
      (local.get $struct)
    )
    (struct.set $struct 0
      (local.get $struct)
      (struct.get $struct 0
        (local.get $struct)
      )
    )
  )
)

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

    ;; CHECK:       (type $Y (sub $X (struct)))
    (type $Y (sub $X (struct)))

    ;; CHECK:       (type $A (sub (struct (field (ref $Y)))))
    (type $A (sub (struct (field (ref $X)))))

    ;; CHECK:       (type $C (sub $A (struct (field (ref $Y)))))
    (type $C (sub $A (struct (field (ref $X)))))

    ;; CHECK:       (type $B (sub $A (struct (field (ref $Y)))))
    (type $B (sub $A (struct (field (ref $X)))))
  )

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

  ;; CHECK:      (func $foo (type $5)
  ;; CHECK-NEXT:  (local $unused (ref null $C))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new $B
  ;; CHECK-NEXT:    (struct.new_default $Y)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $foo
    ;; A use of type $C without ever creating an instance of it. We do still need
    ;; to update the type if we update the parent type, and we will in fact update
    ;; the parent $A's field from $X to $Y (see below), so we must do the same in
    ;; $C. As a result, all the fields with $X in them in all of $A, $B, $C will
    ;; be improved to contain $Y.
    (local $unused (ref null $C))

    (drop
      (struct.new $B
        (struct.new $Y) ;; This value is more specific than the field, which is an
                        ;; opportunity to subtype, which we do for $B. As $A, our
                        ;; parent, has no writes at all, we can propagate this
                        ;; info to there as well, which means we can perform the
                        ;; same optimization in $A as well.
      )
    )
  )
)

(module
  ;; As above, but remove the struct.new to $B, which means $A, $B, $C all have
  ;; no writes to them. There are no optimizations to do here.

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

    ;; CHECK:       (type $Y (sub $X (struct)))
    (type $Y (sub $X (struct)))

    ;; CHECK:       (type $A (sub (struct (field (ref $X)))))
    (type $A (sub (struct (field (ref $X)))))

    ;; CHECK:       (type $B (sub $A (struct (field (ref $X)))))
    (type $B (sub $A (struct (field (ref $X)))))

    ;; CHECK:       (type $C (sub $A (struct (field (ref $X)))))
    (type $C (sub $A (struct (field (ref $X)))))
  )

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

  ;; CHECK:      (func $foo (type $5)
  ;; CHECK-NEXT:  (local $unused1 (ref null $C))
  ;; CHECK-NEXT:  (local $unused2 (ref null $B))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new_default $Y)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $foo
    (local $unused1 (ref null $C))
    (local $unused2 (ref null $B))
    (drop (struct.new $Y))
  )
)

(module
  ;; CHECK:      (type $X (sub (struct)))
  (type $X (sub (struct)))

  ;; CHECK:      (type $Y (sub $X (struct)))
  (type $Y (sub $X (struct)))

  ;; CHECK:      (type $A (sub (struct (field (ref $X)))))
  (type $A (sub (struct (field (ref $X)))))

  ;; CHECK:      (type $B (sub $A (struct (field (ref $Y)))))
  (type $B (sub $A (struct (field (ref $Y)))))

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

  ;; CHECK:      (func $foo (type $4)
  ;; CHECK-NEXT:  (local $unused2 (ref null $B))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new $A
  ;; CHECK-NEXT:    (struct.new_default $X)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $foo
    ;; $B begins with its field of type $Y, which is more specific than the
    ;; field is in the supertype $A. There are no writes to $B, and so we end
    ;; up looking in the parent to see what to do; we should still emit a
    ;; reasonable type for $B, and there is no reason to make it *less*
    ;; specific, so leave things as they are.
    (local $unused2 (ref null $B))
    (drop
      (struct.new $A
        (struct.new $X)
      )
    )
  )
)

(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $struct (sub (struct (field (mut (ref null $struct))))))
  (type $struct (sub (struct (field (mut (ref null struct))))))

  ;; CHECK:       (type $1 (func (param (ref $struct))))

  ;; CHECK:      (func $update-null (type $1) (param $struct (ref $struct))
  ;; CHECK-NEXT:  (struct.set $struct 0
  ;; CHECK-NEXT:   (local.get $struct)
  ;; CHECK-NEXT:   (local.get $struct)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (struct.set $struct 0
  ;; CHECK-NEXT:   (local.get $struct)
  ;; CHECK-NEXT:   (ref.null none)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $update-null (param $struct (ref $struct))
    (struct.set $struct 0
      (local.get $struct)
      ;; Write a $struct to the field.
      (local.get $struct)
    )
    (struct.set $struct 0
      (local.get $struct)
      ;; This null does not prevent refinement.
      (ref.null none)
    )
  )
)

(module
  ;; As above, but now the null is in a child. The result should be the same:
  ;; refine the field to nullable $struct.

  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $struct (sub (struct (field (mut (ref null $struct))))))
  (type $struct (sub (struct (field (mut (ref null struct))))))
  ;; CHECK:       (type $child (sub $struct (struct (field (mut (ref null $struct))))))
  (type $child (sub $struct (struct (field (mut (ref null struct))))))

  ;; CHECK:       (type $2 (func (param (ref $struct) (ref $child))))

  ;; CHECK:      (func $update-null (type $2) (param $struct (ref $struct)) (param $child (ref $child))
  ;; CHECK-NEXT:  (struct.set $struct 0
  ;; CHECK-NEXT:   (local.get $struct)
  ;; CHECK-NEXT:   (local.get $struct)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (struct.set $child 0
  ;; CHECK-NEXT:   (local.get $child)
  ;; CHECK-NEXT:   (ref.null none)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $update-null (param $struct (ref $struct)) (param $child (ref $child))
    (struct.set $struct 0
      (local.get $struct)
      (local.get $struct)
    )
    (struct.set $child 0
      (local.get $child)
      (ref.null none)
    )
  )
)

(module
  ;; As above, but now the null is in a parent. The result should be the same.

  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $struct (sub (struct (field (mut (ref null $struct))))))
  (type $struct (sub (struct (field (mut (ref null struct))))))
  ;; CHECK:       (type $child (sub $struct (struct (field (mut (ref null $struct))))))
  (type $child (sub $struct (struct (field (mut (ref null struct))))))

  ;; CHECK:       (type $2 (func (param (ref $struct) (ref $child))))

  ;; CHECK:      (func $update-null (type $2) (param $struct (ref $struct)) (param $child (ref $child))
  ;; CHECK-NEXT:  (struct.set $struct 0
  ;; CHECK-NEXT:   (local.get $struct)
  ;; CHECK-NEXT:   (ref.null none)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (struct.set $child 0
  ;; CHECK-NEXT:   (local.get $child)
  ;; CHECK-NEXT:   (local.get $struct)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $update-null (param $struct (ref $struct)) (param $child (ref $child))
    (struct.set $struct 0
      (local.get $struct)
      (ref.null none)
    )
    (struct.set $child 0
      (local.get $child)
      (local.get $struct)
    )
  )
)

(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $struct (sub (struct (field (mut nullref)))))
  (type $struct (sub (struct (field (mut (ref null struct))))))

  ;; CHECK:       (type $1 (func (param (ref $struct))))

  ;; CHECK:      (func $work (type $1) (param $struct (ref $struct))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new_default $struct)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $work (param $struct (ref $struct))
    ;; The only write to this struct is of a null default value, so we can
    ;; optimize to nullref.
    (drop
      (struct.new_default $struct)
    )
  )
)

(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $struct (sub (struct (field (mut (ref null $struct))))))
  (type $struct (sub (struct (field (mut (ref null struct))))))

  ;; CHECK:       (type $1 (func (param (ref $struct))))

  ;; CHECK:      (func $work (type $1) (param $struct (ref $struct))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new_default $struct)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (struct.set $struct 0
  ;; CHECK-NEXT:   (local.get $struct)
  ;; CHECK-NEXT:   (local.get $struct)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $work (param $struct (ref $struct))
    (drop
      (struct.new_default $struct)
    )
    ;; Also write a $struct. The null default should not prevent us from
    ;; refining the field's type to $struct (but nullable).
    (struct.set $struct 0
      (local.get $struct)
      (local.get $struct)
    )
  )
)

(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $struct (sub (struct (field (mut (ref null $struct))))))
  (type $struct (sub (struct (field (mut (ref null struct))))))

  ;; CHECK:       (type $1 (func (param (ref $struct))))

  ;; CHECK:      (func $work (type $1) (param $struct (ref $struct))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new $struct
  ;; CHECK-NEXT:    (ref.null none)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (struct.set $struct 0
  ;; CHECK-NEXT:   (local.get $struct)
  ;; CHECK-NEXT:   (local.get $struct)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $work (param $struct (ref $struct))
    ;; As before, but instead of new_default, new, and use a null in the given
    ;; value.
    (drop
      (struct.new $struct
        (ref.null none)
      )
    )
    (struct.set $struct 0
      (local.get $struct)
      (local.get $struct)
    )
  )
)

(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $struct (sub (struct (field (mut (ref null $child))) (field (mut (ref null $struct))))))
  (type $struct (sub (struct (field (mut (ref null struct))) (field (mut (ref null struct))))))

  ;; CHECK:       (type $child (sub $struct (struct (field (mut (ref null $child))) (field (mut (ref null $struct))))))
  (type $child (sub $struct (struct (field (mut (ref null struct))) (field (mut (ref null struct))))))

  ;; CHECK:       (type $2 (func (param (ref $struct) (ref $child))))

  ;; CHECK:      (func $update-null (type $2) (param $struct (ref $struct)) (param $child (ref $child))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new $struct
  ;; CHECK-NEXT:    (local.get $child)
  ;; CHECK-NEXT:    (ref.null none)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new $struct
  ;; CHECK-NEXT:    (ref.null none)
  ;; CHECK-NEXT:    (local.get $struct)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $update-null (param $struct (ref $struct)) (param $child (ref $child))
    ;; Update nulls in two fields that are separately optimized to separate
    ;; values.
    (drop
      (struct.new $struct
        (local.get $child)
        (ref.null none)
      )
    )
    (drop
      (struct.new $struct
        (ref.null none)
        (local.get $struct)
      )
    )
  )
)

(module
  ;; There are two parallel type hierarchies here: "Outer", which are objects
  ;; that have fields, that contain the "Inner" objects.
  ;;
  ;; Root-Outer -> Leaf1-Outer
  ;;            -> Leaf2-Outer
  ;;
  ;; Root-Inner -> Leaf1-Inner
  ;;            -> Leaf2-Inner
  ;;
  ;; Adding their contents, where X[Y] means X has a field of type Y:
  ;;
  ;; Root-Outer[Root-Inner] -> Leaf1-Outer[Leaf1-Inner]
  ;;                        -> Leaf2-Outer[Leaf2-Inner]

  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $Root-Inner (sub (struct)))
  (type $Root-Inner (sub (struct)))

  ;; CHECK:       (type $Leaf1-Inner (sub $Root-Inner (struct (field i32))))
  (type $Leaf1-Inner (sub $Root-Inner (struct (field i32))))

  ;; CHECK:       (type $Leaf2-Inner (sub $Root-Inner (struct)))
  (type $Leaf2-Inner (sub $Root-Inner (struct )))

  ;; CHECK:       (type $Root-Outer (sub (struct (field (ref $Leaf2-Inner)))))
  (type $Root-Outer (sub (struct (field (ref $Root-Inner)))))

  ;; CHECK:       (type $Leaf1-Outer (sub $Root-Outer (struct (field (ref $Leaf2-Inner)))))
  (type $Leaf1-Outer (sub $Root-Outer (struct (field (ref $Leaf1-Inner)))))

  ;; CHECK:       (type $Leaf2-Outer (sub $Root-Outer (struct (field (ref $Leaf2-Inner)))))
  (type $Leaf2-Outer (sub $Root-Outer (struct (field (ref $Leaf2-Inner)))))

  ;; CHECK:       (type $6 (func (param (ref null $Leaf1-Outer))))

  ;; CHECK:      (func $func (type $6) (param $Leaf1-Outer (ref null $Leaf1-Outer))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block ;; (replaces unreachable StructGet we can't emit)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (block
  ;; CHECK-NEXT:      (drop
  ;; CHECK-NEXT:       (local.get $Leaf1-Outer)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:      (unreachable)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new $Leaf2-Outer
  ;; CHECK-NEXT:    (struct.new_default $Leaf2-Inner)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $func (param $Leaf1-Outer (ref null $Leaf1-Outer))
    (drop
      ;; The situation here is that we have only a get for some types, and no
      ;; other constraints. As we ignore gets, we work under no constraints at
      ;; We then have to pick some type, so we pick the one used by our
      ;; supertype - and the supertype might have picked up a type from another
      ;; branch of the type tree, which is not a subtype of ours.
      ;;
      ;; In more detail, we never create an instance of $Leaf1-Outer, and we
      ;; only have a get of its field. This optimization ignores the get (to not
      ;; be limited by it). It will then optimize $Leaf1-Outer's field of
      ;; $Leaf1-Inner (another struct for which we have no creation, and only a
      ;; get) into $Leaf2-Inner, which is driven by the fact that we do have a
      ;; creation of $Leaf2-Inner. But then this struct.get $Leaf1-Inner on field
      ;; 0 is no longer valid, as we turn $Leaf1-Inner => $Leaf2-Inner, and
      ;; $Leaf2-Inner has no field 0. To keep the module validating, we must not
      ;; emit that. Instead, since there can be no instance of $Leaf1-Inner (as
      ;; mentioned before, it is never created, nor anything that can be cast to
      ;; it), we know this code is logically unreachable, and can emit an
      ;; unreachable here.
      (struct.get $Leaf1-Inner 0
        (struct.get $Leaf1-Outer 0
          (local.get $Leaf1-Outer)
        )
      )
    )
    (drop
      (struct.new $Leaf2-Outer
        (struct.new_default $Leaf2-Inner)
      )
    )
  )
)

(module
  ;; CHECK:      (type $A (sub (struct (field (mut (ref null $A))))))
  (type $A (sub (struct (field (mut (ref null $A))))))

  ;; CHECK:      (type $1 (func (param (ref $A) (ref null $A))))

  ;; CHECK:      (func $non-nullability (type $1) (param $nn (ref $A)) (param $A (ref null $A))
  ;; CHECK-NEXT:  (local $temp (ref null $A))
  ;; CHECK-NEXT:  (struct.set $A 0
  ;; CHECK-NEXT:   (local.get $A)
  ;; CHECK-NEXT:   (local.get $nn)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (struct.set $A 0
  ;; CHECK-NEXT:   (local.get $A)
  ;; CHECK-NEXT:   (local.tee $temp
  ;; CHECK-NEXT:    (struct.get $A 0
  ;; CHECK-NEXT:     (local.get $A)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new $A
  ;; CHECK-NEXT:    (local.tee $temp
  ;; CHECK-NEXT:     (struct.get $A 0
  ;; CHECK-NEXT:      (local.get $A)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $non-nullability (param $nn (ref $A)) (param $A (ref null $A))
    (local $temp (ref null $A))
    ;; Set a non-null value to the field.
    (struct.set $A 0
      (local.get $A)
      (local.get $nn)
    )
    ;; Set a get of the same field to the field - this is a copy. However, the
    ;; copy goes through a local.tee. Even after we refine the type of the field
    ;; to non-nullable, the tee will remain nullable since it has the type of
    ;; the local. We could add casts perhaps, but for now we do not optimize,
    ;; and type $A's field will remain nullable.
    (struct.set $A 0
      (local.get $A)
      (local.tee $temp
        (struct.get $A 0
          (local.get $A)
        )
      )
    )
    ;; The same, but with a struct.new.
    (drop
      (struct.new $A
        (local.tee $temp
          (struct.get $A 0
            (local.get $A)
          )
        )
      )
    )
  )
)

(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $A (sub (struct (field (ref null $A)))))
  (type $A (sub (struct (field (ref null $A)))))
  ;; CHECK:       (type $B (sub $A (struct (field (ref null $B)))))
  (type $B (sub $A (struct (field (ref null $A)))))

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

  ;; CHECK:      (func $heap-type (type $2) (param $b (ref null $B)) (param $A (ref null $A))
  ;; CHECK-NEXT:  (local $a (ref null $A))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new $B
  ;; CHECK-NEXT:    (local.get $b)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new $A
  ;; CHECK-NEXT:    (local.tee $a
  ;; CHECK-NEXT:     (struct.get $A 0
  ;; CHECK-NEXT:      (local.get $A)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $heap-type (param $b (ref null $B)) (param $A (ref null $A))
    (local $a (ref null $A))
    ;; Similar to the above, but instead of non-nullability being the issue,
    ;; now it is the heap type. We write a B to B's field, so we can trivially
    ;; refine that, and we want to do a similar refinement to the supertype A.
    ;; But below we do a copy on A through a tee. As above, the tee's type will
    ;; not change, so we do not optimize type $A's field (however, we can
    ;; refine $B's field, which is safe to do).
    (drop
      (struct.new $B
        (local.get $b)
      )
    )
    (drop
      (struct.new $A
        (local.tee $a
          (struct.get $A 0
            (local.get $A)
          )
        )
      )
    )
  )
)

(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $A (sub (struct (field (mut (ref $A))))))
  (type $A (sub (struct (field (mut (ref null $A))))))

  ;; CHECK:       (type $1 (func (param (ref $A) (ref null $A))))

  ;; CHECK:      (func $non-nullability-block (type $1) (param $nn (ref $A)) (param $A (ref null $A))
  ;; CHECK-NEXT:  (struct.set $A 0
  ;; CHECK-NEXT:   (local.get $A)
  ;; CHECK-NEXT:   (local.get $nn)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (struct.set $A 0
  ;; CHECK-NEXT:   (local.get $A)
  ;; CHECK-NEXT:   (if (result (ref $A))
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:    (then
  ;; CHECK-NEXT:     (struct.get $A 0
  ;; CHECK-NEXT:      (local.get $A)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (else
  ;; CHECK-NEXT:     (unreachable)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new $A
  ;; CHECK-NEXT:    (if (result (ref $A))
  ;; CHECK-NEXT:     (i32.const 1)
  ;; CHECK-NEXT:     (then
  ;; CHECK-NEXT:      (struct.get $A 0
  ;; CHECK-NEXT:       (local.get $A)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (else
  ;; CHECK-NEXT:      (unreachable)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $non-nullability-block (param $nn (ref $A)) (param $A (ref null $A))
    (struct.set $A 0
      (local.get $A)
      (local.get $nn)
    )
    ;; As above, but instead of a local.tee fallthrough, use an if. We *can*
    ;; optimize in this case, as ifs etc do not pose a problem (we'll refinalize
    ;; the ifs to the proper, non-nullable type, the same as the field).
    (struct.set $A 0
      (local.get $A)
      (if (result (ref null $A))
        (i32.const 1)
        (then
          (struct.get $A 0
            (local.get $A)
          )
        )
        (else
          (unreachable)
        )
      )
    )
    (drop
      (struct.new $A
        (if (result (ref null $A))
          (i32.const 1)
          (then
            (struct.get $A 0
              (local.get $A)
            )
          )
          (else
            (unreachable)
          )
        )
      )
    )
  )
)

(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $struct (struct (field (mut (ref $struct)))))
  (type $struct (struct (field (mut (ref null struct)))))

  ;; CHECK:       (type $1 (func (param (ref $struct))))

  ;; CHECK:      (func $work (type $1) (param $struct (ref $struct))
  ;; CHECK-NEXT:  (struct.set $struct 0
  ;; CHECK-NEXT:   (local.get $struct)
  ;; CHECK-NEXT:   (local.get $struct)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (struct.set $struct 0
  ;; CHECK-NEXT:   (local.get $struct)
  ;; CHECK-NEXT:   (block ;; (replaces unreachable StructGet we can't emit)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (block (result nullref)
  ;; CHECK-NEXT:      (ref.null none)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $work (param $struct (ref $struct))
    ;; The only set to the field is (ref $struct), so we can refine to that.
    (struct.set $struct 0
      (local.get $struct)
      (local.get $struct)
    )
    ;; After refining, we must update the get's type properly. ReFinalize by
    ;; itself would hit a problem here, as it first turns the block's result to
    ;; a bottom type, after which it can't figure out how to update the
    ;; struct.get. TypeRefining should handle that internally by updating all
    ;; struct.gets itself based on the changes it is making.
    ;;
    ;; Note that this problem depends on the recursion of a struct.get feeding
    ;; into a struct.set: after the refining, the struct.set will only
    ;; validate if we provide it the *refined* type for the field.
    (struct.set $struct 0
      (local.get $struct)
      (struct.get $struct 0
        (block (result (ref null $struct))
          (ref.null none)
        )
      )
    )
  )
)

(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $A (struct (field (ref $B))))
    (type $A (struct (field (ref struct))))
    ;; CHECK:       (type $B (struct))
    (type $B (struct))
  )

  ;; CHECK:       (type $2 (func (result (ref $A))))

  ;; CHECK:      (func $0 (type $2) (result (ref $A))
  ;; CHECK-NEXT:  (struct.new $A
  ;; CHECK-NEXT:   (ref.cast (ref $B)
  ;; CHECK-NEXT:    (struct.get $A 0
  ;; CHECK-NEXT:     (struct.new $A
  ;; CHECK-NEXT:      (struct.new_default $B)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $0 (result (ref $A))
    ;; The cast in the middle here will be skipped in the analysis, as we have
    ;; a copy: a read from $A.0 to a write, with only a cast in the middle
    ;; (which does not alter the value). While that is valid to do, we need to
    ;; properly propagate the new output type of the struct.get (which goes from
    ;; (ref struct) to (ref $B) to the cast, so that the cast has that type as
    ;; well, as otherwise the struct.new will not validate - we can't write a
    ;; (ref struct) to a field of (ref $B).
    (struct.new $A
      (ref.cast (ref struct)
        (struct.get $A 0
          (struct.new $A
            (struct.new_default $B)
          )
        )
      )
    )
  )
)

(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $A (struct (field (mut nullref))))
    (type $A (struct (field (mut anyref))))
    ;; CHECK:       (type $B (struct (field (mut nullref))))
    (type $B (struct (field (mut (ref null $A)))))
  )

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

  ;; CHECK:      (func $0 (type $2)
  ;; CHECK-NEXT:  (block ;; (replaces unreachable StructSet we can't emit)
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (block ;; (replaces unreachable StructNew we can't emit)
  ;; CHECK-NEXT:     (drop
  ;; CHECK-NEXT:      (block
  ;; CHECK-NEXT:       (drop
  ;; CHECK-NEXT:        (struct.get $B 0
  ;; CHECK-NEXT:         (struct.new_default $B)
  ;; CHECK-NEXT:        )
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:       (unreachable)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (unreachable)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (ref.null none)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $0
    (struct.set $A 0
      (struct.new $A
        ;; These two struct.gets will both be refined. That of $B will be
        ;; refined to a get of a null, at which point the get of $A could get
        ;; confused and not know what type it is reading from (since in the IR,
        ;; we depend on the ref for that). The pass should not error here while
        ;; it refines both the struct type's fields to nullref, after which the
        ;; code here will be unreachable (since we do a struct.get from a
        ;; nullref).
        (struct.get $A 0
          (struct.get $B 0
            (struct.new_default $B)
          )
        )
      )
      (ref.null none)
    )
  )
)

(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $A (struct (field (mut (ref noextern)))))
  (type $A (struct (field (mut externref))))

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

  ;; CHECK:       (type $2 (func (param externref) (result anyref)))

  ;; CHECK:       (type $3 (func (param (ref $A) externref)))

  ;; CHECK:       (type $4 (func (param (ref none) (ref noextern))))

  ;; CHECK:       (type $5 (func (param (ref noextern))))

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

  ;; CHECK:      (tag $tag)
  (tag $tag)

  ;; CHECK:      (func $struct.new (type $2) (param $extern externref) (result anyref)
  ;; CHECK-NEXT:  (struct.new $A
  ;; CHECK-NEXT:   (ref.cast (ref noextern)
  ;; CHECK-NEXT:    (try (result externref)
  ;; CHECK-NEXT:     (do
  ;; CHECK-NEXT:      (struct.get $A 0
  ;; CHECK-NEXT:       (struct.new $A
  ;; CHECK-NEXT:        (ref.as_non_null
  ;; CHECK-NEXT:         (ref.null noextern)
  ;; CHECK-NEXT:        )
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (catch $tag
  ;; CHECK-NEXT:      (local.get $extern)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $struct.new (param $extern externref) (result anyref)
    ;; A noextern is written into the struct field and then read. Note that the
    ;; try's catch is never reached, since the body cannot throw, so the
    ;; fallthrough of the try is the struct.get, which leads into a struct.new, so
    ;; we have a copy of that field. For that reason TypeRefining thinks it can
    ;; refine the type of the field from externref to noextern. However, the
    ;; validation rule for try-catch prevents the try from being refined so,
    ;; since the catch has to be taken into account, and it has a less refined
    ;; type than the body.
    ;;
    ;; In such situations we rely on other optimizations to improve things, like
    ;; getting rid of the catch in this case. In this pass we add a cast to get
    ;; things to validate, which should be removable by other passes later on.
    (struct.new $A
      (try (result externref)
        (do
          (struct.get $A 0
            (struct.new $A
              (ref.as_non_null
                (ref.null noextern)
              )
            )
          )
        )
        (catch $tag
          (local.get $extern)
        )
      )
    )
  )

  ;; CHECK:      (func $struct.set (type $3) (param $ref (ref $A)) (param $extern externref)
  ;; CHECK-NEXT:  (struct.set $A 0
  ;; CHECK-NEXT:   (local.get $ref)
  ;; CHECK-NEXT:   (ref.cast (ref noextern)
  ;; CHECK-NEXT:    (try (result externref)
  ;; CHECK-NEXT:     (do
  ;; CHECK-NEXT:      (struct.get $A 0
  ;; CHECK-NEXT:       (struct.new $A
  ;; CHECK-NEXT:        (ref.as_non_null
  ;; CHECK-NEXT:         (ref.null noextern)
  ;; CHECK-NEXT:        )
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (catch $tag
  ;; CHECK-NEXT:      (local.get $extern)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $struct.set (param $ref (ref $A)) (param $extern externref)
    (struct.set $A 0
      (local.get $ref)
      (try (result externref)
        (do
          (struct.get $A 0
            (struct.new $A
              (ref.as_non_null
                (ref.null noextern)
              )
            )
          )
        )
        (catch $tag
          (local.get $extern)
        )
      )
    )
  )

  ;; CHECK:      (func $bottom.type (type $4) (param $ref (ref none)) (param $value (ref noextern))
  ;; CHECK-NEXT:  (block ;; (replaces unreachable StructSet we can't emit)
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (local.get $ref)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (local.get $value)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $bottom.type (param $ref (ref none)) (param $value (ref noextern))
    ;; The reference here is a bottom type, which we should not crash on.
    (struct.set $A 0
      (local.get $ref)
      (local.get $value)
    )
  )

  ;; CHECK:      (func $unreachable (type $5) (param $value (ref noextern))
  ;; CHECK-NEXT:  (block ;; (replaces unreachable StructSet we can't emit)
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (local.get $value)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $unreachable (param $value (ref noextern))
    ;; The reference here is unreachable, which we should not crash on.
    (struct.set $A 0
      (unreachable)
      (local.get $value)
    )
  )
)

;; We cannot refine the fields of public types. One of $A's children is public
;; here, $B. That makes $A public as well, leaving only $C as theoretically
;; optimizable, but we cannot refine a mutable field in a way that makes it
;; differ from the super, so we end up doing nothing here.
(module
  ;; CHECK:      (type $A (sub (struct (field (mut anyref)))))
  (type $A (sub (struct (field (mut anyref)))))

  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $B (sub $A (struct (field (mut anyref)))))
    (type $B (sub $A (struct (field (mut anyref)))))
    ;; CHECK:       (type $brand (struct))
    (type $brand (struct))
  )

  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $C (sub $A (struct (field (mut anyref)))))
    (type $C (sub $A (struct (field (mut anyref)))))
    ;; CHECK:       (type $brand2 (struct))
    (type $brand2 (struct))
    ;; CHECK:       (type $brand3 (struct))
    (type $brand3 (struct))
  )

  ;; CHECK:      (type $6 (func (param (ref any))))

  ;; CHECK:      (global $global (ref null $B) (ref.null none))
  (global $global (ref null $B) (ref.null $B))

  ;; CHECK:      (export "global" (global $global))
  (export "global" (global $global))

  ;; CHECK:      (func $work (type $6) (param $nn (ref any))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new $A
  ;; CHECK-NEXT:    (local.get $nn)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new $B
  ;; CHECK-NEXT:    (local.get $nn)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new $C
  ;; CHECK-NEXT:    (local.get $nn)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $work (param $nn (ref any))
    ;; All the types look refinable, as we write a non-nullable value.
    (drop
      (struct.new $A
        (local.get $nn)
      )
    )
    (drop
      (struct.new $B
        (local.get $nn)
      )
    )
    (drop
      (struct.new $C
        (local.get $nn)
      )
    )
  )
)

;; As above, but now the fields are all immutable. This allows us to refine $C,
;; but nothing else.
(module
  ;; CHECK:      (type $A (sub (struct (field anyref))))
  (type $A (sub (struct (field anyref))))

  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $B (sub $A (struct (field anyref))))
    (type $B (sub $A (struct (field anyref))))
    ;; CHECK:       (type $brand (struct))
    (type $brand (struct))
  )

  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $C (sub $A (struct (field (ref any)))))
    (type $C (sub $A (struct (field anyref))))
    (type $brand2 (struct))
    (type $brand3 (struct))
  )

  ;; CHECK:       (type $4 (func (param (ref any))))

  ;; CHECK:      (global $global (ref null $B) (ref.null none))
  (global $global (ref null $B) (ref.null $B))

  ;; CHECK:      (export "global" (global $global))
  (export "global" (global $global))

  ;; CHECK:      (func $work (type $4) (param $nn (ref any))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new $A
  ;; CHECK-NEXT:    (local.get $nn)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new $B
  ;; CHECK-NEXT:    (local.get $nn)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new $C
  ;; CHECK-NEXT:    (local.get $nn)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $work (param $nn (ref any))
    (drop
      (struct.new $A
        (local.get $nn)
      )
    )
    (drop
      (struct.new $B
        (local.get $nn)
      )
    )
    (drop
      (struct.new $C
        (local.get $nn)
      )
    )
  )
)

;; Regression test for a bug (#7103) in which the set of public types was
;; computed incorrectly, leading to an assertion failure.
(module
 ;; CHECK:      (type $1 (sub (struct (field (mut (ref null $1))))))
 (type $1 (sub (struct (field (mut (ref null $1))))))
 (rec
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $7 (sub (struct (field (ref $1)))))

  ;; CHECK:       (type $8 (sub (func)))

  ;; CHECK:      (type $3 (func (result (ref null $8))))

  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $5 (sub (struct (field (ref $3)))))
  (type $5 (sub (struct (field (ref func)))))
  ;; CHECK:       (type $6 (sub $1 (struct (field (mut (ref null $1))))))
  (type $6 (sub $1 (struct (field (mut (ref null $1))))))
 )
 (rec
  (type $7 (sub (struct (field (ref $1)))))
  (type $8 (sub (func)))
 )
 ;; CHECK:       (type $9 (sub $6 (struct (field (mut (ref null $1))))))
 (type $9 (sub $6 (struct (field (mut (ref null $1))))))

 ;; CHECK:      (elem declare func $1)

 ;; CHECK:      (export "func" (func $1))
 (export "func" (func $1))

 ;; CHECK:      (func $1 (type $3) (result (ref null $8))
 ;; CHECK-NEXT:  (local $l (ref $9))
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (struct.get $5 0
 ;; CHECK-NEXT:    (struct.new $5
 ;; CHECK-NEXT:     (ref.func $1)
 ;; CHECK-NEXT:    )
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (ref.null nofunc)
 ;; CHECK-NEXT: )
 (func $1 (result (ref null $8))
  (local $l (ref $9))
  (drop
   (struct.get $5 0
    (struct.new $5
     (ref.func $1)
    )
   )
  )
  (ref.null $8)
 )
)

;; Test for a bug where we made a struct.get unreachable because it was reading
;; a field that had no writes, but in a situation where it is invalid for the
;; struct.get to be unreachable.
(module
 ;; CHECK:      (type $never (sub (struct (field i32))))
 (type $never (sub (struct (field i32))))

 ;; CHECK:      (rec
 ;; CHECK-NEXT:  (type $optimizable (struct (field (mut nullfuncref))))
 (type $optimizable (struct (field (mut (ref null func)))))

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

 ;; CHECK:      (global $never (ref null $never) (ref.null none))
 (global $never (export "never") (ref null $never)
   ;; Make the type $never public (if it were private, we'd optimize in a
   ;; different way that avoids the bug that this tests for).
   (ref.null $never)
 )

 ;; CHECK:      (export "never" (global $never))

 ;; CHECK:      (func $setup (type $2)
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (struct.new $optimizable
 ;; CHECK-NEXT:    (ref.null nofunc)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (block
 ;; CHECK-NEXT:    (drop
 ;; CHECK-NEXT:     (block (result (ref none))
 ;; CHECK-NEXT:      (ref.as_non_null
 ;; CHECK-NEXT:       (ref.null none)
 ;; CHECK-NEXT:      )
 ;; CHECK-NEXT:     )
 ;; CHECK-NEXT:    )
 ;; CHECK-NEXT:    (unreachable)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $setup
  ;; A struct.new, so that we have a field to refine (which avoids the pass
  ;; early-exiting).
  (drop
   (struct.new $optimizable
    (ref.null func)
   )
  )
  ;; A struct.get that reads a $never, but the actual fallthrough value is none.
  ;; We never create this type, so the field has no possible content, and we can
  ;; replace this with an unreachable.
  (drop
   (struct.get $never 0
    (block (result (ref $never))
     (ref.as_non_null
      (ref.null none)
     )
    )
   )
  )
 )
)

;; Test that we note default values.
(module
 (rec
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $A (sub (struct (field i32))))
  (type $A (sub (struct (field i32))))
  ;; CHECK:       (type $B (sub (struct (field i32))))
  (type $B (sub (struct (field i32))))
 )
 ;; CHECK:      (type $2 (func (param (ref null $A) (ref null $B))))

 ;; CHECK:      (type $optimizable (sub (struct (field (ref $2)))))
 (type $optimizable (sub (struct (field funcref))))

 ;; CHECK:      (elem declare func $test)

 ;; CHECK:      (export "test" (func $test))

 ;; CHECK:      (func $test (type $2) (param $x (ref null $A)) (param $y (ref null $B))
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (struct.new $optimizable
 ;; CHECK-NEXT:    (ref.func $test)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (struct.get $A 0
 ;; CHECK-NEXT:    (struct.new $A
 ;; CHECK-NEXT:     (i32.const 0)
 ;; CHECK-NEXT:    )
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (struct.get $B 0
 ;; CHECK-NEXT:    (struct.new_default $B)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $test (export "test") (param $x (ref null $A)) (param $y (ref null $B))
  ;; Use $A, $B as params of this export, so they are public.

  ;; Make something for the pass to do, to avoid early-exit.
  (drop
   (struct.new $optimizable
    (ref.func $test)
   )
  )

  ;; Get from a struct.new. We have nothing to optimize here. (In particular, we
  ;; cannot make this unreachable, as there is a value in the field, 0.)
  (drop
   (struct.get $A 0
    (struct.new $A
     (i32.const 0)
    )
   )
  )

  ;; As above. Now the value in the field comes from a default value.
  (drop
   (struct.get $B 0
    (struct.new_default $B)
   )
  )
 )
)