In case it can help, here's some example:
(cl-defmethod my-foo ((x symbol))
(message "%S is a symbol" x))
If I call `(my-foo nil)` it will report that nil is a symbol, even
though `(cl-type-of nil)` returns `null` rather than `symbol`.
If I add a new method for `null`:
(cl-defmethod my-foo ((x null))
(message "%S is null" x)
(cl-call-next-method))
Now a call to `(my-foo nil)` will report both that nil is null and that
it's a symbol (in that order).
For your `button` example, we'd similarly want that
(cl-defmethod my-foo ((x icon))
(message "%S is an icon" x)
(cl-call-next-method))
`(my-foo 'button)` reports button as both an icon and a symbol (in that
order). Now let's say we add:
(defgtype face nil ()
"Builtin face type."
'(and symbol (satisfies facep)))
(cl-defmethod my-foo ((x face))
(message "%S is an face" x)
(cl-call-next-method))
We'd want `(my-foo 'button)` now to report that button is an icon,
a face, and a symbol (and here I think it's OK if the ordering between
icon and face is arbitrary, tho they should both come before symbol).
One way you could do that is for your `(gtype-of 'button)` to return
some new `face&icon` type and to have your SPECIALIZERS-FUNCTION return
the list `(face icon symbol)` or `(icon face symbol)` for that type.
Does that help?