[Tex/LaTex] error when patching with etoolbox \apptocmd: “the patching command seems to be nested in the argument to some other command”

errorsetoolboxpatching

The \apptocmd command from etoolbox is giving me an error I don't understand. I know how to work around the error in this particular case, but I'd like to understand what it's complaining about so that I can fix similar errors in the future.

This is the error I'm getting:

[debug] tracing \apptocmd on input line 28
[debug] analyzing \H@old@part
[debug] ++ control sequence is defined
[debug] ++ control sequence is a macro
[debug] ++ control sequence is a macro with parameters
[debug] -- nested patching command and parameters in patch
[debug] -> the patching command seems to be nested in the
[debug]    argument to some other command
[debug] -> the patch text seems to contain # characters
[debug] -> either avoid nesting or use # characters with
[debug]    category code 12 in the patch text
[debug] -> simply doubling the # characters will not work

! Package cont Error: can't patch \H@old@part.

See the cont package documentation for explanation.
Type  H <return>  for immediate help.
 ...                                              

l.28     }

? 

The following produces the above error:

\documentclass{report}
\usepackage{etoolbox}
\tracingpatches
\usepackage{hyperref}
\makeatletter

% error handler for patching commands
\newrobustcmd{\cont@err}[1]{\PackageError{cont}{can't patch \protect#1}{}}%

% contains the name of the current part
\gdef\cont@name@part{\PackageError{cont}{not in a part}{}}

% patch \part to save the part name to \cont@name@part.  note
% that \part is a parameterless macro that has \@part as its last
% token, which does take the arguments.  Thus, \part can't be
% appended to; \@part has to be appended to instead.
%
% The hyperref package redefines \@part, so when hyperref is
% loaded \H@old@part needs to be patched instead.
\ifdef{\H@old@part}{
  \apptocmd{\H@old@part}{%
    \gdef\cont@name@part{#2}%
  }{}{\cont@err{\H@old@part}}
}{
  \apptocmd{\@part}{%
    \gdef\cont@name@part{#2}%
  }{}{\cont@err{\H@old@part}}
}

\begin{document}
\part{first part}
this part's name is ``\cont@name@part''
\end{document}

The following also doesn't work:

\catcode`\#=12
\ifdef{\H@old@part}{
  \apptocmd{\H@old@part}{%
    \gdef\cont@name@part{#2}%
  }{}{\cont@err{\H@old@part}}
}{
  \apptocmd{\@part}{%
    \gdef\cont@name@part{#2}%
  }{}{\cont@err{\H@old@part}}
}
\catcode`\#=6

It produces the following error:

[debug] analyzing \H@old@part
[debug] ++ control sequence is defined
[debug] ++ control sequence is a macro
[debug] ++ control sequence is a macro with parameters
[debug] -- macro cannot be retokenized cleanly
[debug] -> the macro may have been defined under a category
[debug]    code regime different from the current one
[debug] -> the replacement text may contain special control
[debug]    sequence tokens formed with \csname...\endcsname;
[debug] -> the replacement text may contain carriage return,
[debug]    newline, or similar characters

I can work around the problem by doing the following instead:

\expandafter\apptocmd\expandafter{%
  \csname\ifdef{\H@old@part}{H@old}{}@part\endcsname%
}{%
  \gdef\cont@name@part{#2}%
}{}{\cont@error{\@part}}

However, I'd like to know how to fix the error while using the former approach (\apptocmd in an argument of \ifdef).

Best Answer

The patch command clearly uses catcode changes so it's like \verb while there are things you can do to make \verb half work in some arguments, the basic rule of TeX is that commands using catcode changes do not work in the arguments of other commands.

The reason for this is that catcode changes change the way that characters are converted to tokens by TeX's scanner, but macro arguments are scanned and tokenized while scanning the argument looking for the closing brace so what is passed to the macro as #1 or #2 etc. is not a list of characters it is a list of tokens and catcode changes have no effect on the interpretation of tokens.