latex3 / latex2e

The LaTeX2e kernel
https://www.latex-project.org/
LaTeX Project Public License v1.3c
1.96k stars 267 forks source link

\DeclareRobustCommand with active chars redefine control space (\ ) #345

Open PhelypeOleinik opened 4 years ago

PhelypeOleinik commented 4 years ago

Brief outline of the bug

When \DeclareRobustCommand is used to define an active character, the internal definition is stored in the control space (\␣).

Minimal example showing the bug

\RequirePackage{latexbug}
\documentclass{article}
\begin{document}
\DeclareRobustCommand~{I'm a tilde!}
A control space: \ 
\end{document}

Proposed fix

The redefinition of the control space happens because the code does (assuming a printable \escapechar) \csname\expandafter\@gobble\string#1 \endcsname, then in the case of an active char the \@gobble consumes the entire char, so the \csname makes a space.

To avoid this we can ensure \escapechar=-1 and remove the \@gobble:

\RequirePackage{latexbug}
\documentclass{article}
\makeatletter
\def\declare@robustcommand#1{%
   \ifx#1\@undefined\else\ifx#1\relax\else
      \@latex@info{Redefining \string#1}%
   \fi\fi
   \edef\reserved@a{\string#1}%
   \def\reserved@b{#1}%
   \edef\reserved@b{\expandafter\strip@prefix\meaning\reserved@b}%
   \begingroup
     \escapechar=\m@ne
     \edef\reserved@a{%
   \endgroup
   \def\noexpand#1{%
      \ifx\reserved@a\reserved@b
         \noexpand\x@protect
         \noexpand#1%
      \fi
      \noexpand\protect
      \expandafter\noexpand\csname\string#1 \endcsname
   }%
   \let\noexpand\@ifdefinable\noexpand\@rc@ifdefinable
   \noexpand\new@command
     \expandafter\noexpand\csname\string#1 \endcsname}%
   \reserved@a
}
\makeatother
\begin{document}
\DeclareRobustCommand~{I'm still a tilde!}
A control space:\ .
And ~
\end{document}

Log file (required) and possibly PDF file

test.log

davidcarlisle commented 4 years ago

oops...

But just using escapechar=-1 isn't quite enough as then \~ and ~ both have the same internal form. I'll be offline most of today but

\documentclass{article}

\makeatletter

\def\declare@robustcommand#1{%
   \ifx#1\@undefined\else\ifx#1\relax\else
      \@latex@info{Redefining \string#1}%
   \fi\fi
   \edef\reserved@a{\string#1}%
   \def\reserved@b{#1}%
   \edef\reserved@b{\expandafter\strip@prefix\meaning\reserved@b}%
   \begingroup
     \escapechar=\m@ne
     \edef\reserved@a{%
   \endgroup
   \def\noexpand#1{%
      \ifx\reserved@a\reserved@b
         \noexpand\x@protect
         \noexpand#1%
      \fi
      \noexpand\protect
      \expandafter\noexpand\csname\string#1 \endcsname
   }%
   \let\noexpand\@ifdefinable\noexpand\@rc@ifdefinable
   \noexpand\new@command
     \expandafter\noexpand\csname\string#1 \endcsname}%
   \reserved@a
}

\makeatother

\begin{document}

\DeclareRobustCommand~{I'm still a tilde!}
\DeclareRobustCommand\~{But I'm  a tilde command!}

A control space:\ .

And ~

Or \~ j

\end{document}
PhelypeOleinik commented 4 years ago

Hm, yes, I forgot about \~... Well, with the current mechanism there's really no way to fit both ~ and \~, because \DeclareRobustCommand will define \~␣ for both since it strips the leading backslash, so their internal definition will always clash. We can make the internal definition for active chars <char><space><char>, so it won't clash with a command char (and since active chars didn't work anyway, I won't break anything ;-)


\RequirePackage{latexbug}
\documentclass{article}
\makeatletter
\def\declare@robustcommand#1{%
   \ifx#1\@undefined\else\ifx#1\relax\else
      \@latex@info{Redefining \string#1}%
   \fi\fi
   \edef\reserved@a{\string#1}%
   \def\reserved@b{#1}%
   \edef\reserved@b{\expandafter\strip@prefix\meaning\reserved@b}%
   \begingroup
     \escapechar=\m@ne
     \let\reserved@c\@empty
     \ifcat\noexpand~\noexpand#1%
       \edef\reserved@c{\string#1}%
     \fi
     \edef\reserved@a{%
   \endgroup
   \def\noexpand#1{%
      \ifx\reserved@a\reserved@b
         \noexpand\x@protect
         \noexpand#1%
      \fi
      \noexpand\protect
      \expandafter\noexpand\csname\string#1 \reserved@c\endcsname
   }%
   \let\noexpand\@ifdefinable\noexpand\@rc@ifdefinable
   \noexpand\new@command
     \expandafter\noexpand\csname\string#1 \reserved@c\endcsname}%
   \reserved@a
}
\makeatother
\begin{document}
\DeclareRobustCommand~{I'm still a tilde!}
\DeclareRobustCommand\~{But I'm  a tilde command!}

A control space:\ .

And ~

Or \~ j
\end{document}
stale[bot] commented 4 years ago

This issue has been automatically marked as stale because it has not had recent activity.

FrankMittelbach commented 2 years ago

I see 3 options:

hand-waving

stale[bot] commented 2 years ago

This issue has been automatically marked as stale because it has not had recent activity.

josephwright commented 1 month ago

Looking back, this is not supported behaviour - just needs documenting.

FrankMittelbach commented 1 month ago
  • test for the arg being an active character and say, not allowed

maybe we should do that as well, but perhaps overkill