According to the documentation of the package xparse the syntax of \NewExpandableDocumentCommand is:
\NewExpandableDocumentCommand ⟨function⟩ {⟨arg spec⟩} {⟨code⟩}
When using xparse's \NewExpandableDocumentCommand for defining an expandable command ⟨function⟩ - what is the exact rule for calculating the amount of expansion-steps it takes outgoing from ⟨function⟩⟨arguments⟩ to obtain the ⟨replacement-text⟩ which is formed by the tokens ⟨code⟩ with ⟨code⟩'s parameters #1, #2, etc replaced by the ⟨arguments⟩?
E.g., using \newcommand one can do
% LaTeX 2e
\newcommand\FirstAndSecond[2]{#1 and #2}
\expandafter\def\expandafter\test\expandafter{\FirstAndSecond{A}{B}}
\show\test
\stop
and as expected with one \expandafter-chain one gets the message:
> \test=macro:
->A and B.
l.4 \show\test
If one uses \NewExpandableDocumentCommand instead of \newcommand and does
% LaTeX 2e
\RequirePackage{xparse}
\NewExpandableDocumentCommand\FirstAndSecond{mm}{#1 and #2}
\expandafter\def\expandafter\test\expandafter{\FirstAndSecond{A}{B}}
\show\test
\stop
, then one gets the message
> \test=macro:
->\__xparse_start_expandable:nNNNNn {mm}\FirstAndSecond \FirstAndSecond \Firs
tAndSecond code ?{\__xparse_expandable_grab_m:w \__xparse_expandable_grab_m:w }
{A}{B}.
l.5 \show\test
. Obviously commands defined in terms of xparse's \NewExpandableDocumentCommand need more expansion-steps.
It seems using \romannumeral-expansion one can work around this without knowing the exact rules:
% LaTeX 2e
\RequirePackage{xparse}
\csname @ifdefinable\endcsname\stopromannumeral{\chardef\stopromannumeral=`\^^00}%
% !!! The call to \FirstAndSecond must always be preceded by \romannumeral !!!
\NewExpandableDocumentCommand\FirstAndSecond{mm}{\stopromannumeral #1 and #2}
\expandafter\def\expandafter\test\expandafter{\romannumeral\FirstAndSecond{A}{B}}
\show\test
\stop
> \test=macro:
->A and B.
l.7 \show\test
But this question is not about weird workarounds but about exact rules. :-)
\expandedmakes such concerns basically irrelevant. – Joseph Wright Jul 13 '21 at 13:29\romannumeral-expansion into\NewExpandableDocumentCommandas indicated in my workaround (or\expanded-expansion as indicated by you) so that the rule with expandable document commands will be that in any case obtaining the replacement text takes exactly two expansion-steps? Such a rule would relieve some worries for those users who write commands for the document level where expansion does plays a role (e.g. in alignments/tables - tabularray) no matter if you don't want people to worry about that or not. – Ulrich Diez Jul 13 '21 at 13:55\romannumeral-expansion into\NewExpandableDocumentCommandas indicated in my workaround (or\expanded-expansion as indicated by you) so that the rule with expandable document commands will be that in any case obtaining the replacement text takes exactly two expansion-steps? Such a rule would relieve some worries for those users who write commands for the document level where expansion does plays a role (e.g. in alignments/tables - tabularray) no matter if you don't want people to worry about that or not. – Ulrich Diez Jul 13 '21 at 13:56\expandedand understand exactly what it does? – Ulrich Diez Jul 13 '21 at 13:59margument in your example. However, if I change your example toom, the function requires a different amount of steps if used as\FirstAndSecond[A]{B}or\FirstAndSecond[[A]]{B}or\FirstAndSecond{B}or ... because it processes what it sees differently. – Phelype Oleinik Jul 13 '21 at 14:03\edefor\expanded, or they do their best to avoid any expansion (\protected/LaTeX robust/...). – Joseph Wright Jul 13 '21 at 15:47\AllPreceedingAndFollowingLabelswould not be possible without exact knowledge of the expansion-steps needed with\GetLabelNameFromLabelNumber, which also is a user-level-command. Therefore defining\GetLabelNameFromLabelNumberI couldn't use\NewExpandableDocumentCommandbut had to resort to\newcommand. – Ulrich Diez Jul 13 '21 at 20:57\expanded{..}might as well expand the tokens forming that result although this might be undesired. – Ulrich Diez Jul 14 '21 at 16:51\unexpandedaround it to allow reuse without depending on implementation details. – Marcel Krüger Jul 14 '21 at 18:46