Subject: bug#78786: 31.0.50; eieio-core.el requires byte compilation

From: Pip Cet <[email-masked]>
Date: Fri, 13 Jun 2025 15:20:32 +0000

Evaluating eieio-core.el without compilation currently results in
breakage:

(load "eieio-core.el")
(cl-defstruct foo x)
(eieio-oset (make-foo) 'x nil)

results in:

  signal(wrong-type-argument (eieio--class #s(cl-structure-class :name foo :docstring nil :parents (#s(cl-structure-class :name cl-structure-object :docstring "The root parent of all \"normal\" CL structs" :parents (#s(built-in-class :name record :docstring "Abstract type of objects with slots." :parents (...) :slots nil :index-table nil)) :slots [] :index-table #<hash-table eq 0/0 ...> :tag cl-structure-object :type nil :named nil :print t :children-sym cl-struct-cl-structure-object-tags)) :slots [#s(cl-slot-descriptor :name x :initform nil :type t :props nil)] :index-table #<hash-table eq 1/1 ...> :tag foo :type nil :named nil :print t :children-sym cl-struct-foo-tags)))
  (or (let* ((cl-x cl-x)) (and (memq (type-of cl-x) cl-struct-eieio--class-tags) t)) (signal 'wrong-type-argument (list 'eieio--class cl-x)))
  (progn (or (let* ((cl-x cl-x)) (and (memq (type-of cl-x) cl-struct-eieio--class-tags) t)) (signal 'wrong-type-argument (list 'eieio--class cl-x))) (aref cl-x 4))
  (let* ((cl-x class)) (progn (or (let* ((cl-x cl-x)) (and (memq (type-of cl-x) cl-struct-eieio--class-tags) t)) (signal 'wrong-type-argument (list 'eieio--class cl-x))) (aref cl-x 4)))
  (aref (let* ((cl-x class)) (progn (or (let* ((cl-x cl-x)) (and (memq (type-of cl-x) cl-struct-eieio--class-tags) t)) (signal 'wrong-type-argument (list 'eieio--class cl-x))) (aref cl-x 4))) slot-idx)
  (let* ((sd (aref (let* ((cl-x class)) (progn (or (let* ... ...) (signal ... ...)) (aref cl-x 4))) slot-idx)) (st (let* ((cl-x sd)) (progn (or (let* (...) (and ... t)) (signal 'wrong-type-argument (list ... cl-x))) (aref cl-x 3))))) (cond ((not (eieio--perform-slot-validation st value)) (signal 'invalid-slot-type (list (let* ((cl-x class)) (progn (or ... ...) (aref cl-x 1))) slot st value))) ((alist-get :read-only (let* ((cl-x sd)) (progn (or (let* ... ...) (signal ... ...)) (aref cl-x 4)))) (signal 'eieio-read-only (list (let* ((cl-x class)) (progn (or ... ...) (aref cl-x 1))) slot)))))
  (if eieio-skip-typecheck nil (setq slot-idx (- slot-idx '1)) (let* ((sd (aref (let* ((cl-x class)) (progn (or ... ...) (aref cl-x 4))) slot-idx)) (st (let* ((cl-x sd)) (progn (or (let* ... ...) (signal ... ...)) (aref cl-x 3))))) (cond ((not (eieio--perform-slot-validation st value)) (signal 'invalid-slot-type (list (let* (...) (progn ... ...)) slot st value))) ((alist-get :read-only (let* ((cl-x sd)) (progn (or ... ...) (aref cl-x 4)))) (signal 'eieio-read-only (list (let* (...) (progn ... ...)) slot))))))
  eieio--validate-slot-value(#s(cl-structure-class :name foo :docstring nil :parents (#s(cl-structure-class :name cl-structure-object :docstring "The root parent of all \"normal\" CL structs" :parents (#s(built-in-class :name record :docstring "Abstract type of objects with slots." :parents (#s(built-in-class :name atom :docstring "Abstract supertype of anything but cons cells." :parents ... :slots nil :index-table nil)) :slots nil :index-table nil)) :slots [] :index-table #<hash-table eq 0/0 ...> :tag cl-structure-object :type nil :named nil :print t :children-sym cl-struct-cl-structure-object-tags)) :slots [#s(cl-slot-descriptor :name x :initform nil :type t :props nil)] :index-table #<hash-table eq 1/1 ...> :tag foo :type nil :named nil :print t :children-sym cl-struct-foo-tags) 1 nil x)

The expected behavior is for this code to complete without error.

The failure is because eieio--validate-slot-value is called for a class
(object) of type cl-structure-object, but calls eieio--class-slots on
that object; it could call cl--class-slots instead, and then this bug
wouldn't happen.

When byte-compiled, we optimize out the type checks so everything works.
This is achieved by the cl-declaim calls surrounding the cl-defstruct of
eieio--class.

When interpreted directly, cl-defstruct always adds the type checks, so
the error pops up.

However, I have my doubts about whether we might end up calling
eieio--class-class-slots or eieio--class-initarg-tuples on a
cl-structure-object, which would return its "type" or "named" slots
(unless I miscounted, sorry).  So I'd like to fix these while we're
there (in the latter case, we're on slow code paths so this should not
be a problem; in the former case, if there is a performance impact, we
could consider moving the class-slots slot to cl--class instead,
reserving a useless slot in it for cl-structure-object objects).

I propose moving to cl--class-* accessors where we can, and calling
eieio--class-slot-name-index and eieio--class-initarg-to-attribute only
on known/checked eieio--class objects.

But if an alternative is desired (I think this may be the case because I
suggested changing one of the accessors before), and the
initarg/class-slot case is always safe, we could simply error out when
trying to evaluate eieio-core.el directly rather than compiling it.

While this is related to bug#78685, I think there are other,
non-bug-induced circumstances in which we end up interpreting
eieio-core.el, so we should either allow it or make it fail less subtly.