Subject: Re: bug#55645: src/print.c; print_object changes make it impossible to compare elisp code across versions

From: Eli Zaretskii <[email-masked]>
Date: Sat, 04 Jun 2022 12:15:26 +0300

> From: Tom Gillespie <[email-masked]>
> Date: Sun, 29 May 2022 14:03:11 -0700
> Cc: Lars Ingebrigtsen <[email-masked]>, [email-masked]
> 
> Hi Eli,
>   I have included an example below, with some additional context. Best!
> Tom

OK, thanks (and sorry for the delay in responding).  I think I
understand the issue now.

You say in your OP that there's no way to work around the issue, but I
wonder if this is really so.  It seems to me that by adding a
backslash before '.' and '?', before computing the hash, would be one
workaround?

> The example below works on emacs-18 (had to remove the number 1.0
> example because emacs 18 does not have support for reading floats).
> 
> The output of this code is unchanged from emacs-18 through emacs-28,
> however it is now different in emacs-29.
> Emacs 18-28:
> "(progn (+ 1 2) (a b\\.c d) (defun hello nil world)) (progn (some
> elisp code I want to normalize\\. That has strings \"1.0\" and symbols
> a\\.b))"
> Emacs 29:
> "(progn (+ 1 2) (a b.c d) (defun hello nil world)) (progn (some elisp
> code I want to normalize. That has strings \"1.0\" and symbols a.b))"
> 
> #+begin_src elisp :tangle /tmp/example.el
> (defun normalize-elisp-code (body)
>   (let (print-quoted print-length print-level) ; proposed variable
> would be added here
>     (prin1-to-string (read (concat "(progn\n" body "\n)")))))
> 
> (defvar example-body-1 "(+ 1 2) (a b\\.c d) (defun hello () world)")
> (defvar example-body-2 (prin1-to-string '(some elisp code I want to
> normalize\. That has strings "1.0" and symbols a\.b)))
> (message "%s %s"
>          (normalize-elisp-code example-body-1)
>          (normalize-elisp-code example-body-2))
> #+end_src
> 
> The additional step in my use case is the checksum, which cannot be
> read back in, and changes from 28 -> 29 due to the differences in the
> output of prin1-to-string seen above.
> 
> #+begin_src elisp
> (defun checksum-elisp-code (body)
>   (secure-hash 'sha256 (normalize-elisp-code body)))
> 
> (message "%s %s"
>          (checksum-elisp-code example-body-1)
>          (checksum-elisp-code example-body-2))
> #+end_src

Frankly, I'm amazed that someone could have such faith into
immutability of Emacs Lisp.  Why would you assume that the above
produces the same literal string across versions, especially given
everything that happens lately in Emacs, including changes in the
byte-compiler, the introduction of native-compilation, etc.  If
someone would show me a design that is based on this, I'd tell them
they place their bet on a dubious horse, and suggest to find a better
design.  To me, this is taking some Emacs feature that just happened
to be stable, and assuming it will forever be stable.  There's no
reason and no real basis for such assumptions.

Thus, I agree with Lars that it is strange to hear that prin1 is used
as something that's supposed to produce a canonical representation of
Lisp code; it's definitely isn't its purpose.

Anyway, one way forward is to add a new API specifically for that
purpose, and then guarantee that the output of that new API will be
stable.  This will also take care of the issue with the design that
relies on prin1.

Another way forward is to revert the change.  And here I'm asking Lars
what are the reasons for the change, except some aesthetics-related
considerations, whereby we basically didn't see why the escapes are
needed.  If the only problems are that we didn't see a good reason for
keeping the escapes, perhaps now we do have a good reason?

Thanks.