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

From: Stefan Monnier <[email-masked]>
Date: Thu, 24 Apr 2025 15:38:40 -0400

> 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)))))

Hmm... I can see that it could be handy if you don't know how to
characterize type T1 other than as the "sum" of its subtypes.
But this seems rather circular.  Have you seen such things out in
the wild?

More importantly, with your circularity-breaking approach, if `gtype-of`
(aka `cl-types-of`) happens to look at T1 first, that check will
immediately return nil, so `cl-types-of` may decide "Oh, I just
discovered this is not a T1, so I can skip checking all the subtypes of
T1".  So, the cycle is broken, but the output is wrong.

The need for multiple object would be for cases like:

    (cl-deftype car-foo ()
      "Car-Foo type.
    Check if passed object has a subtype of FOO as car."
      `(satisfies ,(lambda (o) (memq 'FOO (gtype-of (car-safe o))))))

but I guess this can inf-loop only if we follow a cycle in the object
(e.g. say if o == (car (car (car o)))).

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

Is this definition meaningful with the above definition of T1?
I don't think it's well-founded.

> (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

This is both right and wrong: we could return t and that would be
equally valid.

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

And here (T2 T1 cons list sequence t) would also be equally valid.

>> 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'?

I'd just move the `cl-deftype` calls out of the tests and presume that if
they signal an error we'll see it.

>> - 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.

That's only when you specifically need to test the effect of the
macro-expansion itself, rather than test the result of running the
macro-expanded code.  It's rarely needed IME.  An example would be to
detect when a macro-expansion emits a warning.

Also, by using this `eval+quote` you can't test the macro in the way
it's usually used (where it's macro-expanded once during compilation and
then its result is run in another Emacs session) so you may miss bugs
such as when the macro's expansion performs a side-effect (like add
something to a list) which the expanded code expects to have happened
(but this will have happened during compilation, so the element may not
be in the list any more when the code is finally executed).

IOW, I disagree with the docstring.  🙁

        Stefan