As @UlrikeFischer and @DavidCarlisle pointed out, using \edef
with arbitrary material is likely to cause errors. For instance, this will be the case with material containing \textbf
, and there are plenty of other cases.
Two ways in LaTeX2e style
I present two techniques in LaTeX2e style that use \protected@edef
so that macros defined with \DeclareRobustCommand
are not affected. This way, the added material may contain \textbf
.
These techniques also don't overwrite the \next
macro, which is always nice (well, they do but automatically restore it when finished). Also note that your \tmpmacroval
macro wasn't really useful.
Note that your list is duly modified despite the grouping I added, because \g@addto@macro
uses \xdef
, which performs a global assignment.
First technique
\documentclass{article}
\newcommand{\newslist}{}
\makeatletter
\newcommand{\newappend}[1]{%
\begingroup
\protected@edef\next{\endgroup\noexpand\g@addto@macro\noexpand\newslist{#1,}}%
\next
}
\makeatother
\newcommand{\foo}{\textbf{foo}val}
\begin{document}
\newappend{\foo}
\renewcommand{\foo}{someotherval}
\verb|\newslist| is \newslist.
\end{document}

Explanation
Let's assume that TeX has just expanded \newappend{〈argument〉}
. This leaves the following at the front of the input stream:
\begingroup
\protected@edef\next{\endgroup\noexpand\g@addto@macro\noexpand\newslist{〈argument〉,}}%
\next
Upon execution of \begingroup
, TeX opens a new group. Then he sees the line:
\protected@edef\next{\endgroup\noexpand\g@addto@macro\noexpand\newslist{〈argument〉,}}
After \protected@edef
has been expanded and all the resulting tokens processed, the macro \next
is defined in the current group. The definition is local to this group, and the previous definition of \next
(possibly: none) will be automatically restored by TeX as soon as this group ends—that is the main purpose of TeX groups. Now, the next token in the input stream is the \next
control sequence token. Since it is a macro, TeX expands it, which causes this \next
token to be replaced by:
\endgroup\g@addto@macro\newslist{〈expanded argument〉,}
(the \noexpand
tokens have been removed as part of the execution of an \edef
in the implementation of \protected@edef
). The next token in the input stream is thus \endgroup
now. TeX executes it (it is unexpandable, thus it is handled in TeX's stomach, contrary to macros), which closes the current group. As said, this has the effect of cancelling the (re-)definition of \next
that we did. This is clean. What remains at the front of the input stream is now
\g@addto@macro\newslist{〈expanded argument〉,}
which is exactly what you wanted.
Second technique
\documentclass{article}
\newcommand{\newslist}{}
\makeatletter
\newcommand{\newappend}[1]{%
\begingroup
\protected@edef\next{\noexpand\g@addto@macro\noexpand\newslist{#1,}}%
\expandafter
\endgroup
\next
}
\makeatother
\newcommand{\foo}{\textbf{foo}val}
\begin{document}
\newappend{\foo}
\renewcommand{\foo}{someotherval}
\verb|\newslist| is \newslist.
\end{document}
Same output as in the previous example.
Examples with expl3
expl3
has many facilities to deal with list-like material. I'll show some examples with clist
, i.e. comma-separated lists. This allows easy direct setting from user input, since commas can be entered in the text. For more general lists, you can use seq
variables. For lists of (key,value) pairs, there is the prop
module (“property lists”). All these modules are built in the expl3
language, thus available after \usepackage{expl3}
or \usepackage{xparse}
, which loads expl3.
There is no need to load anything else.
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\clist_new:N \g_faheem_clist
\cs_new_protected:Npn \faheem_append:Nn #1#2
{
\clist_gput_right:Nn #1 {#2} % 'g' for “global”
}
% Variant that performs one expansion step on the second argument
\cs_generate_variant:Nn \faheem_append:Nn { No }
% Variant that performs \edef-like expansion on the second argument
\cs_generate_variant:Nn \faheem_append:Nn { Nx } % \edef-like expansion
% There are also 'e' to perform \expanded-like expansion, 'f' for
% \romannumeral-like expansion, etc.
\NewDocumentCommand \newList { m }
{
\clist_new:N #1
}
% In fact, for serious work, better do:
%
% \cs_new_protected:Npn \faheem_new_list:N #1
% {
% \clist_new:N #1
% }
%
% and make \newList call \faheem_new_list:N or \faheem_new_list:c as I do for
% the \append* functions below (the 'c' variant would allow you to choose
% arbitrary names for the clist variable: you would pass whatever name you
% want to the 'c' variant, normally without a leading slash, otherwise that
% would create a strange beast: \\the_passed_name).
\NewDocumentCommand \clearList { O{\g_faheem_clist} }
{
\clist_gclear:N #1 % 'g' for “global”
}
\NewDocumentCommand \appendNoExp { O{\g_faheem_clist} m }
{
\faheem_append:Nn #1 {#2}
}
\NewDocumentCommand \appendOneExp { O{\g_faheem_clist} m }
{
\faheem_append:No #1 {#2}
}
\NewDocumentCommand \appendFullExp { O{\g_faheem_clist} m }
{
\faheem_append:Nx #1 {#2}
}
% This requires a recent LaTeX release (2020 or later?)
\NewDocumentCommand \appendTextExpand { O{\g_faheem_clist} m }
{
% \text_expand:n works inside an 'x'-type argument because it is fully
% expandable, as indicated by the star in the margin in interface3.pdf.
\faheem_append:Nx #1 { \text_expand:n {#2} }
}
\NewDocumentCommand \useList { O{\g_faheem_clist} m }
{
\clist_use:Nn #1 {#2}
}
\ExplSyntaxOff
\newcommand*{\showres}[1]{\makebox[0pt][r]{#1.~}\ignorespaces}
\begin{document}
\newList{\somelist}
\appendNoExp{first in default list}
\appendNoExp[\somelist]{first in somelist}
\def\foobar{element with \textbf{non-expandable}\_material}
\def\baz{quux}
\appendOneExp{\foobar}
\appendOneExp[\somelist]{\baz}
\showres{1}
Default list: \useList{, }\par
\verb|\somelist|: \useList[\somelist]{, }
\def\fooleveli{\foolevelii}
\def\foolevelii{\fooleveliii}
\def\fooleveliii{other element}
\appendFullExp{\fooleveli}
\showres{2}
Default list: \useList{, }
\renewcommand{\fooleveliii}{\textbf{non-expandable} \emph{stuff}}
% Expand again \fooleveli, \foolevelii and (the new) \fooleveliii, this time
% with a method that won't break with LaTeX non-expandable commands defined
% with \DeclareRobustCommand.
\appendTextExpand{\fooleveli}
\showres{3}
Default list: \useList{, }.
\def\foobar{overwrite}
\def\fooleveli{overwrite}
\showres{4}
Default list: \useList{, }.\par
\verb|\somelist|: \useList[\somelist]{, }.
\showres{5}
With X as separator: \useList{X}.
\clearList % clear the default list
\showres{6}
Y\useList{X}Y
\end{document}

Note: for expanding text material without causing errors due to non-expandable, non-engine-protected commands like \textbf
, recent expl3
has \text_expand:n
, which is in the spirit of \protected@edef
(see Joseph Wright's blog and interface3.pdf).
\tmpmacroval/\foo
command. This could explode if the command contains stuff that better shouldn't be passed through an \edef. Better use \protected@edef, or expand only once. And consider to use expl3 which has readymade commands for this.