I am confused about how expansion works in \addtocontents, or more precisely in \protected@write. As I understand it, \addtocontents should essentially expand its second argument with \protected@edef and write the result to the aux file. With this understanding, I would expect the behavior to be (up to a \protected@) the same as
\iow_shipout:Ne \@auxout
{
\exp_not:N \@writefile { #1 } { #2 }
}
But as the following MWE shows, they behave differently when trying to prevent expansion inside their argument with \exp_not:n.
\documentclass{article}
\makeatletter
\ExplSyntaxOn
\NewDocumentCommand { \naiveaddtocontents } { m m }
{
\iow_shipout:Ne @auxout
{
\exp_not:N @writefile { #1 } { #2 }
}
}
\ExplSyntaxOff
\def\abc{some text}
\begin{document}
bla
\ExplSyntaxOn
% this does what I expect
\protected@edef \l_tmpa_tl { \exp_not:n { \abc } }
\tl_show:N \l_tmpa_tl
% so does this
\naiveaddtocontents{ lof }{ \exp_not:n { \abc } }
% this does not
\addtocontents{ lof }{ \exp_not:n { \abc } }
\ExplSyntaxOff
\end{document}
The aux file looks like
\relax
\@writefile {lof}{\abc }
\@writefile{lof}{some text}
\gdef \@abspage@last{1}
So \naiveaddtocontents is preventing expansion with \exp_not:n, but \addtocontents is not. For my use case where I don't want the argument expanded at all, I can just use this \naiveaddtocontents. But why doesn't \exp_not:n work as I incorrectly expected in \addtocontents?
\edefstrips one\exp_not:n, and since\writeexpands its argument another expansion takes place when\reserved@ais executed, hence the need for two\exp_not:n's? – mbert Jan 18 '24 at 23:27