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

From: David Ponce <[email-masked]>
Date: Wed, 23 Apr 2025 11:34:52 +0200

On 2025-04-22 23:34, Stefan Monnier wrote:
>>> Yes, as you've seen, the placement can be tricky.  There are bootstrap
>>> constraints as well as "general design" desires, and they all kind
>>> of pull in different directions.
>>> I'll take a closer look and come back with a proposal.
>> Great. Thank you!
> 
> Sorry for the delay, here's what I came up with.

No problem, we're in no rush and certainly have plenty of other things
to do in our life.  Thank you very much for taking time to work on
this!

> I added a FIXME about the recursion-breaker which I have a feeling might
> not work.

I read your FIXME:

+  ;; FIXME: Does this even work?  Won't `cl--type-object' be rebound to
+  ;; the other object before we get here?  IOW wouldn't we need a list
+  ;; of objects that are in the process of being checked?  And while
+  ;; returning nil breaks the recursion, won't it return a bogus result?

However, I don't understand why you think this might not work.  I don't
see how `cl--type-object' can be rebound while `cl-types-of' is
running?  Recursion can only occur when calling `cl-typep' to check the
type of `cl--type-object', and `cl-types-of' will immediately return
nil in that case.  Did I miss something?

Regarding the bogus result, I chose to ignore types that will never
match, resulting in infinite recursion of `cl-types-of'.  It's not
possible to signal an error here, as that would block subsequent types
that have no problem.  Perhaps a warning message is missing when
recursion is detected on a type?  It would probably be much better to
detect recursions during type definition and report an error at that
point.  But I suspect that would be a lot of work.

Here is a test case with a recursion:

(cl-deftype T1 ()
   "Root type.
Check if passed object is a subtype of T1. I.e., if T1 is present in
object type parents."
   `(satisfies ,(lambda (o) (memq 'T1 (gtype-of o)))))

(cl-deftype T2 ()
   "Recursive on checking for T1, never match."
   (declare (parents T1))
   `(and T1 (satisfies ,(lambda (o) (equal o '(T2))))))

(defgtype T3 ()
   "Not recursive on T1."
   (declare (parents T1))
   `(satisfies ,(lambda (o) (equal o '(T3)))))

;; T2 will never match, because `cl-types-of' enters in an endless recursion
(cl-typep (list 'T2) 'T1)
=> nil

(cl-types-of (list 'T2))
=> (cons list sequence t)

;; T3 will match.
(cl-typep (list 'T3) 'T1)
=> (T1 cons list sequence t)

(cl-types-of (list 'T3))
=> (T3 T1 cons list sequence t)

> There's also some stylistic problems with the tests:
> 
> - We shouldn't test the return value of `cl-deftype`: I don't think it's
>    documented anywhere and I don't think it's good practice to pay
>    attention to the return value of declarations.

You are certainly right.  I just wanted to check that `cl-deftype'
didn't fail, but there is no `should-no-error'.  Would it be better to
test for the presence of the type just defined in `cl--type-list'?

> - They use `eval` too much.

This is because `cl-deftype' is a macro and the doc string of
`ert-deftest' suggests to wrap macros in `(eval (quote ...))' to test
them.

But I must admit that my knowledge of ERT is very limited.  Any
suggestions for improving the tests would be welcome.

David