GNU bug report logs - #59469
29.0.50; Eshell "for" loop: Calling a non-lisp command (example: /usr/bin/tail) sets the variable exported in the {} block of "for var in list {}" to nil

Previous Next

Package: emacs;

Reported by: Milan Zimmermann <milan.zimmermann <at> gmail.com>

Date: Tue, 22 Nov 2022 02:06:02 UTC

Severity: normal

Found in version 29.0.50

Done: Jim Porter <jporterbugs <at> gmail.com>

Bug is archived. No further changes may be made.

Full log


Message #17 received at 59469 <at> debbugs.gnu.org (full text, mbox):

From: Jim Porter <jporterbugs <at> gmail.com>
To: Milan Zimmermann <milan.zimmermann <at> gmail.com>
Cc: 59469 <at> debbugs.gnu.org
Subject: Re: bug#59469: Adding a simpler duplication of the issue
Date: Tue, 24 Jan 2023 17:39:26 -0800
[Message part 1 (text/plain, inline)]
On 11/21/2022 11:18 PM, Milan Zimmermann wrote:
> But it sounds to me like your intuition about this could be fixed by 
> rewriting the core 'eshell-do-eval' loop in bug#57635 can be correct.  I 
> would enjoy helping with  it, but at the moment it is above my time and 
> elisp abilities.

After digging through 'eshell-do-eval' for another issue, I think I 
mostly understand it now. I still think bug#57635 is the way to go 
long-term (either that or use real threads), but since that's a big 
change, it might be best to give users some time where they could opt 
out of some new Eshell evaluation backend, just in case we break 
something with it. As such, I want to make sure the existing backend is 
as bug-free as possible so that (ideally) switching between the two has 
no behavioral difference.

With that said, here's a patch that fixes this, plus a regression test. 
One question for anyone reviewing the patch: is there a better way to do 
the "catch and rethrow" dance I do in 'eshell-do-eval'? It seems kind of 
clumsy...

----------------------------------------

The patch has a description of the problem, but I'll describe it in more 
detail here for anyone curious. Eshell turns the command in the original 
message into a form like this (note: this is very approximate):

  (let ((process-environment process-environment))
    (setenv "var" "value")
    (eshell-external-command "grep") ; This throws 'eshell-defer'
    (eshell/echo (getenv "var")))

'eshell-do-eval' gradually steps through this form, evaluating subforms 
and replacing them with their (quoted) results. This way, when a command 
throws 'eshell-defer', you can resume this form simply by calling 
'eshell-do-eval' again. So at the point 'eshell-defer' gets thrown, the 
form has been updated to:

  (let ((process-environment process-environment))
    '"value"   ; The quoted result of 'setenv' above.
    (eshell-external-command "grep")
    (eshell/echo (getenv "var")))

But since 'process-environment' is let-bound, when we resume evaluation, 
it's as though 'setenv' had never been called at all!

The fix here is that when we're inside a 'let' and see 'eshell-defer' 
get thrown, update the let-bindings in place. So now the updated form 
would look like:

  (let ((process-environment (cons "var=value" process-environment)))
    '"value"   ; Not really necessary, but it doesn't hurt anything.
    (eshell-external-command "grep")
    (eshell/echo (getenv "var")))

And so with this, it all works.
[0001-Ensure-that-deferred-commands-don-t-make-Eshell-forg.patch (text/plain, attachment)]

This bug report was last modified 2 years and 197 days ago.

Previous Next


GNU bug tracking system
Copyright (C) 1999 Darren O. Benham, 1997,2003 nCipher Corporation Ltd, 1994-97 Ian Jackson.