Let's see how \pushQED and \popQED are defined in amsthm.sty:
274 \DeclareRobustCommand{\qed}{%
275 \ifmmode \mathqed
276 \else
277 \leavevmode\unskip\penalty9999 \hbox{}\nobreak\hfill
278 \quad\hbox{\qedsymbol}%
279 \fi
280 }
281 \let\QED@stack\@empty
282 \let\qed@elt\relax
283 \newcommand{\pushQED}[1]{%
284 \toks@{\qed@elt{#1}}\@temptokena\expandafter{\QED@stack}%
285 \xdef\QED@stack{\the\toks@\the\@temptokena}%
286 }
287 \newcommand{\popQED}{%
288 \begingroup\let\qed@elt\popQED@elt \QED@stack\relax\relax\endgroup
289 }
290 \def\popQED@elt#1#2\relax{#1\gdef\QED@stack{#2}}
291 \newcommand{\qedhere}{%
292 \begingroup \let\mathqed\math@qedhere
293 \let\qed@elt\setQED@elt \QED@stack\relax\relax \endgroup
294 }
The name clearly shows that this has to do with the typesetting of the QED marker in proofs, so I included the definition of \qed.
There is a “stack”, actually a macro, called \QED@stack, that's initialized to empty.
Suppose we call \pushQED{foo} when the stack is empty. Two scratch token registers are set: first \toks@ is set to \qed@elt{foo}, then \@temptokena is set to contain the current first level expansion of \QED@stack (in this case, nothing). Next, \QED@stack is redefined to contain the token lists in the two registers; with e-TeX extensions, this could be achieved with the single instruction
\xdef\QED@stack{\unexpanded{\qed@elt{#1}}\unexpanded\expandafter{\QED@stack}}
So now \QED@stack will expand to \qed@elt{foo}. If another \pushQED{bar} follows, the expansion would become \qed@elt{bar}\qed@elt{foo}. But let's stay with the simple case.
What happens when \popQED is called? The instructions at line 288 are executed, namely
\begingroup\let\qed@elt\popQED@elt \QED@stack\relax\relax\endgroup
The macro \qed@elt (that normally is \relax, see line 282) is set to mean \popQED@elt inside a group and then \QED@stack\relax\relax is examined. In your case it is
\qed@elt{foo}\relax\relax
Since the macro \qed@elt has been redefined, this is the same as \popQED@elt{foo}\relax\relax and, according to the definition of \popQED@elt,
#1 <- {foo}
#2 <-
and therefore
foo\gdef\QED@stack{}\relax
would remain in the main token list (the braces are stripped off by rule of TeX). In case \QED@stack had been \qed@elt{bar}\qed@elt{foo}, we'd have
#1 <- {bar}
#2 <- \qed@elt{foo}
and bar\gdef\QED@stack{\qed@elt{foo}}\relax would be pushed into the main input stream.
Just the fact that the expansion of \popQED begins with \begingroup disqualifies it from being legal inside \csname...\endcsname; moreover assignments cannot be performed in that context, so it's a lost battle to begin with.
The double \relax is in case the stack is empty at the time \popQED is called, that is without a matching \pushQED command.
What's the main usage of the system? The standard proof environment in amsthm.sty is defined as
432 \newenvironment{proof}[1][\proofname]{\par
433 \pushQED{\qed}%
434 \normalfont \topsep6\p@\@plus6\p@\relax
435 \trivlist
436 \item[\hskip\labelsep
437 \itshape
438 #1\@addpunct{.}]\ignorespaces
439 }{%
440 \popQED\endtrivlist\@endpefalse
441 }
The idea is that a subordinate proof environment might define its own tombstone symbol and push it in the stack, so at end environment the right symbol would be used.
Can you use stacks for this purpose? Yes.
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\newstack}{m}
{
\seq_new:c { g_thorsten_#1_stack_seq }
}
\NewDocumentCommand{\push}{mm}
{% #1 is the stack's name, #2 the item to push
\seq_gpush:cn { g_thorsten_#1_stack_seq } { #2 }
}
\NewDocumentCommand{\pop}{mo}
{% #1 is the stack's name, #2 what you should do with the top item
% reinitialize, in case it has been modified
\cs_set_eq:NN \__thorsten_stack_exec:n \__thorsten_stack_exec_default:n
\IfValueT{ #2 }
{
\cs_set:Nn \__thorsten_stack_exec:n { #2 }
}
\seq_gpop:cNTF { g_thorsten_#1_stack_seq } \l__thorsten_stack_item_tl
{% if the stack is not empty
\__thorsten_stack_exec:V \l__thorsten_stack_item_tl
}
{% if the stack is empty, issue an error
\__thorsten_stack_exec:n { \STACKEMPTYERROR }
}
}
\tl_new:N \l__thorsten_stack_item_tl
\cs_new_protected:Nn \__thorsten_stack_exec_default:n { #1 }
\cs_set_eq:NN \__thorsten_stack_exec:n \__thorsten_stack_exec_default:n
\cs_generate_variant:Nn \__thorsten_stack_exec:n { V }
\ExplSyntaxOff
\newstack{env}
\newenvironment{myEnv}[1][default]
{%
\push{env}{#1}%
\csname x#1\endcsname
}
{%
\pop{env}[\csname endx##1\endcsname]%
}
\newenvironment{xdefault}{\par start}{finish\par}
\newenvironment{xinner}{\par startinner}{finishinner\par}
\begin{document}
\begin{myEnv}
\begin{myEnv}[inner]
\end{myEnv}
\end{myEnv}
\newstack{foo}
\push{foo}{A}
\push{foo}{B}
\push{foo}{C}
\pop{foo}[@@#1@@]
\pop{foo}[??#1??]
\pop{foo}[!!#1!!]
\pop{foo}[---#1---]
\end{document}
The \push command takes as arguments the stack's name and the item to push. \pop takes as mandatory argument the stack's name and the optional argument is a template for what to do with the popped item (default is to just deliver it) after removing the item from the top of the stack.
Be careful with spaces in your input: \csname x#1 \endcsname is not the same as \csname x#1\endcsname.

Since in the last example the \pop operation is called on an empty stack, an error is produced
! Undefined control sequence.
<argument> \STACKEMPTYERROR
l.69 \pop{foo}[---#1---]
\popQEDwithin\csname ... \endcsnameis a bit too much for LaTeX. I presume\popQEDdoes not expand to simple text, so LaTeX fails to make sense of it as part of a control sequence name. Maybe you just mean\csname endx#1\endcsname\popQED? I'm also not quite sure if the\expandafters are needed and the trailing space after#1in\csname x#1 \endcsnamemight be too much. (But this is all speculation without a full example document where I can verify the definitions involved.) – moewe May 19 '19 at 19:56\popQEDand\pushQEDcan help you here. Try\def\tssavedarg{#1}and\csname endx\tssavedarg\endcsnameinstead. – moewe May 19 '19 at 20:05\expandafter\csname xexpandsxwhich is not expandable, so the\expandafteris doing nothing. – David Carlisle May 19 '19 at 20:30