bug-gnu-emacs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

bug#47552: 27.1; cl-defstruct field names matching read-only variables -


From: Stefan Monnier
Subject: bug#47552: 27.1; cl-defstruct field names matching read-only variables -> bad code
Date: Sun, 18 Jun 2023 15:03:25 -0400
User-agent: Gnus/5.13 (Gnus v5.13)

> But...  Indeed doing this "doesn't work":
>
> (cl-defsubst foo4 (&key gcs-done)
>   gcs-done)
>
> (foo4 :foo 1)
> -> Debugger entered--Lisp error: (wrong-type-argument numberp nil)

I think the problem is that ELisp function arguments are defined as
being always-statically-scoped, but the macroexpansion of

    (cl-defun foo4 (&key gcs-done)
      gcs-done)

uses `let` rather than `lambda` to bind `gcs-done`, so it ends up being
dynamically-scoped.  Maybe we should introduce something like

    (defmacro slet* (bindings &rest body)
      (named-let expand ((bindings bindings))
        (pcase-exhaustive bindings
          ('() (macroexp-progn body))
          (`((,var ,exp) . ,bindings)
           (let ((rest (expand bindings)))
             (if (macroexp--dynamic-variable-p var)
                 `(funcall (lambda (,var) ,rest) ,exp)
               (macroexp-let* `((,var ,exp)) rest)))))))

Except I see that `macroexpand-all` will incorrectly turn the
funcall+lambda into a `let`.
Some wise ass knew about it but did it anyway.  They even wrote
a comment about it:

    ;; In lexical-binding mode, let and functions don't bind vars in the same 
way
    ;; (let obey special-variable-p, but functions don't).  But luckily, this
    ;; doesn't matter here, because function's behavior is underspecified so it
    ;; can safely be turned into a `let', even though the reverse is not true.

so we need to either fix that `macroexp--unfold-lambda` or circumvent it
by obfuscating the code, e.g.:

    (defmacro slet* (bindings &rest body)
      (named-let expand ((bindings bindings))
        (pcase-exhaustive bindings
          ('() (macroexp-progn body))
          (`((,var ,exp) . ,bindings)
           (let ((rest (expand bindings)))
             (if (macroexp--dynamic-variable-p var)
                 `(funcall (identity (lambda (,var) ,rest)) ,exp)
               (macroexp-let* `((,var ,exp)) rest)))))))

Another way to look at it is that maybe we should introduce an
`un-defvar`, such that we can use

    (un-defvar gcs-done)
    (cl-defun foo4 (&key gcs-done)
      gcs-done)

and have `gcs-done` bound statically.


        Stefan






reply via email to

[Prev in Thread] Current Thread [Next in Thread]