Subject: bug#77725: 31.0.50; Add support for types accepted by `cl-typep' to cl-generic?

From: Stefan Monnier <[email-masked]>
Date: Tue, 06 May 2025 22:51:00 -0400

>>>   Debugger entered--Lisp error: (void-function kmacro-register-p)
>>>    kmacro-register-p(2)
>>>    cl-typep(2 (satisfies kmacro-register-p))
>>>    cl-typep(2 kmacro-register)
>>>    cl-types-of(2)
>>>    (seq-intersection (cl-types-of 2) types)
>>>    [...]
>> That's an incorrect `cl-deftype` call.  AFAIK this was in `register.el`
>> but I actually removed it from there with:
>>      commit b1407b41a16c4a3ec15c88be91ba0ae1e65c212e
>>      Author: Stefan Monnier <[email-masked]>
>>      Date:   Tue Apr 29 16:04:54 2025 -0400
>>               register.el: Remove bogus deftypes and fix associated
>> methods
>>                   * lisp/register.el (frame-register, kmacro-register):
>> Remove bogus deftypes.
>>          (register--type) <oclosure>: Fix kmacro method and generalize it to
>>          any OClosure.
>>          (register--type) <frameset-register>: Fix method and move it to ...
>>          * lisp/frameset.el (register--type) <frameset-register>: ... here,
>>          where `frameset-register` is defined.
>> I guess I need to rebase the `cl-types` branch.  🙂
>
> I also see a similar error with type `frame-register':
>
> "cl-types-of frame-register: Symbol’s function definition is void: frameset-register-p"

IIUC: Same error, at the same place, fixed by the same commit, yes.  🙂

> I guess a general solution is much more complex than what I thought.
> And maybe risk to be more costly than the current implementation
> without set of cl-types.

There's probably some way to do better than the naïve loop we have now,
but ... we can keep that for later.

> Please find attached a patch proposal for the branch.  Mainly to
> ensure that types not suitable for cl-defmethod (erroneous type or
> type with arguments) will signal an "Unknown specializer" error when
> trying to use them as argument type in cl-defmethod.

Good point.  But see below.

>           (condition-case-unless-debug e
>               (cl-typep object type)
> -           (error (setq cl--type-list (delq type cl--type-list))
> -                  (warn  "cl-types-of %S: %s"
> -                         type (error-message-string e)))))
> +           (error
> +            ;; Temporarily raise the recursion limit to avoid
> +            ;; another recursion error while processing error.
> +            (let ((max-lisp-eval-depth (+ 800 max-lisp-eval-depth)))

We almost never do that in ELisp except in very specific circumstances
where we decide it's really worth the trouble (even in though in theory
it could be necessary in pretty much all error handlers).
Is there a reason why it's more necessary here than usual?
Maybe a simpler fix is to replace `warn` with `message`?

> +              ;; Remove the type in error from valid specializers.
> +              (cl-remprop type 'cl--class)

I think that's rather drastic.  Just because it signals errors in
`cl-types-of` doesn't mean it can't be useful in other places.

> +              (setq cl--type-dispatch-list (delq type cl--type-dispatch-list))

Why do we need this?  The `(if types` test above presumes that we're OK
with the assumption that we don't need to catch/silence errors for types
in `cl--type-dispatch-list`.

> --- a/lisp/emacs-lisp/cl-macs.el
> +++ b/lisp/emacs-lisp/cl-macs.el
> @@ -3816,18 +3816,22 @@ cl-deftype
>          (cl-callf (lambda (x) (delq declares x)) decls)))
>      (and parents arglist
>           (error "Parents specified, but arglist not empty"))
> -    `(eval-and-compile ;;cl-eval-when (compile load eval)
> -       ;; FIXME: Where should `cl--type-deftype' go?  Currently, code
> -       ;; using `cl-deftype' can use (eval-when-compile (require
> -       ;; 'cl-lib)), so `cl--type-deftype' needs to go either to
> -       ;; `cl-preloaded.el' or it should be autoloaded even when
> -       ;; `cl-lib' is not loaded.
> -       (cl--type-deftype ',name ',parents ',arglist ,docstring)
> -       (define-symbol-prop ',name 'cl-deftype-handler
> -                           (cl-function
> -                            (lambda (&cl-defs ('*) ,@arglist)
> -                              ,@decls
> -                              ,@forms))))))
> +    ;; Don't register types that can't be used without arguments for
> +    ;; dispatch.  They'd signal errors in `cl-types-of'!
> +    (let ((noarg (memq (car arglist) '(nil &rest &optional &keys))))
> +      ;;(or noarg (message "Can't dispatch type with argument, `%S'" name))
> +      `(eval-and-compile ;;cl-eval-when (compile load eval)
> +         ;; FIXME: Where should `cl--type-deftype' go?  Currently, code
> +         ;; using `cl-deftype' can use (eval-when-compile (require
> +         ;; 'cl-lib)), so `cl--type-deftype' needs to go either to
> +         ;; `cl-preloaded.el' or it should be autoloaded even when
> +         ;; `cl-lib' is not loaded.
> +         ,@(if noarg `((cl--type-deftype ',name ',parents ,docstring)))
> +         (define-symbol-prop ',name 'cl-deftype-handler
> +                             (cl-function
> +                              (lambda (&cl-defs ('*) ,@arglist)
> +                                ,@decls
> +                                ,@forms)))))))

I intend to use the `cl--type-class` struct in `cl--class` for purposes
like browsing types (via `C-h o`) like we can do already for structs and
built-in types, so we should register the types with `cl--type-deftype`
regardless if they can be used for dispatch.

So I installed an alternative solution to this problem in
`cl-generic-generalizers`, we call the `cl-type-handler` to make sure
it's valid to use the type without arguments.

        Stefan