latex3 / latex2e

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

`\dots` not working correctly with `\cong` #1265

Open eg9 opened 5 months ago

eg9 commented 5 months ago

Brief outline of the bug

While \dots does magic when used between symbols, it doesn't when the following symbol is \cong

Minimal example showing the bug

\RequirePackage{latexbug}       % <--should be always the first line (see CONTRIBUTING)!
\documentclass{article}

  % Any preamble code goes here
\usepackage{amsmath}

\begin{document}

  % Demonstration of issue here
$\equiv\dots\equiv$

$\cong\dots\cong$

\end{document}

Log file (required) and possibly PDF file

This is pdfTeX, Version 3.141592653-2.6-1.40.25 (TeX Live 2023) (preloaded format=pdflatex 2024.1.24)  6 FEB 2024 23:35
entering extended mode
 restricted \write18 enabled.
 %&-line parsing enabled.
**cong.tex
(./cong.tex
LaTeX2e <2023-11-01> patch level 1
L3 programming layer <2024-01-22>
(/usr/local/texlive/2023/texmf-dist/tex/latex/latexbug/latexbug.sty
Package: latexbug 2022/06/14 v1.0n Bug-classification
)
(/usr/local/texlive/2023/texmf-dist/tex/latex/base/article.cls
Document Class: article 2023/05/17 v1.4n Standard LaTeX document class
(/usr/local/texlive/2023/texmf-dist/tex/latex/base/size10.clo
File: size10.clo 2023/05/17 v1.4n Standard LaTeX file (size option)
)
\c@part=\count187
\c@section=\count188
\c@subsection=\count189
\c@subsubsection=\count190
\c@paragraph=\count191
\c@subparagraph=\count192
\c@figure=\count193
\c@table=\count194
\abovecaptionskip=\skip48
\belowcaptionskip=\skip49
\bibindent=\dimen140
)
(/usr/local/texlive/2023/texmf-dist/tex/latex/amsmath/amsmath.sty
Package: amsmath 2023/05/13 v2.17o AMS math features
\@mathmargin=\skip50

For additional information on amsmath, use the `?' option.
(/usr/local/texlive/2023/texmf-dist/tex/latex/amsmath/amstext.sty
Package: amstext 2021/08/26 v2.01 AMS text

(/usr/local/texlive/2023/texmf-dist/tex/latex/amsmath/amsgen.sty
File: amsgen.sty 1999/11/30 v2.0 generic functions
\@emptytoks=\toks17
\ex@=\dimen141
))
(/usr/local/texlive/2023/texmf-dist/tex/latex/amsmath/amsbsy.sty
Package: amsbsy 1999/11/29 v1.2d Bold Symbols
\pmbraise@=\dimen142
)
(/usr/local/texlive/2023/texmf-dist/tex/latex/amsmath/amsopn.sty
Package: amsopn 2022/04/08 v2.04 operator names
)
\inf@bad=\count195
LaTeX Info: Redefining \frac on input line 234.
\uproot@=\count196
\leftroot@=\count197
LaTeX Info: Redefining \overline on input line 399.
LaTeX Info: Redefining \colon on input line 410.
\classnum@=\count198
\DOTSCASE@=\count199
LaTeX Info: Redefining \ldots on input line 496.
LaTeX Info: Redefining \dots on input line 499.
LaTeX Info: Redefining \cdots on input line 620.
\Mathstrutbox@=\box51
\strutbox@=\box52
LaTeX Info: Redefining \big on input line 722.
LaTeX Info: Redefining \Big on input line 723.
LaTeX Info: Redefining \bigg on input line 724.
LaTeX Info: Redefining \Bigg on input line 725.
\big@size=\dimen143
LaTeX Font Info:    Redeclaring font encoding OML on input line 743.
LaTeX Font Info:    Redeclaring font encoding OMS on input line 744.
\macc@depth=\count266
LaTeX Info: Redefining \bmod on input line 905.
LaTeX Info: Redefining \pmod on input line 910.
LaTeX Info: Redefining \smash on input line 940.
LaTeX Info: Redefining \relbar on input line 970.
LaTeX Info: Redefining \Relbar on input line 971.
\c@MaxMatrixCols=\count267
\dotsspace@=\muskip16
\c@parentequation=\count268
\dspbrk@lvl=\count269
\tag@help=\toks18
\row@=\count270
\column@=\count271
\maxfields@=\count272
\andhelp@=\toks19
\eqnshift@=\dimen144
\alignsep@=\dimen145
\tagshift@=\dimen146
\tagwidth@=\dimen147
\totwidth@=\dimen148
\lineht@=\dimen149
\@envbody=\toks20
\multlinegap=\skip51
\multlinetaggap=\skip52
\mathdisplay@stack=\toks21
LaTeX Info: Redefining \[ on input line 2953.
LaTeX Info: Redefining \] on input line 2954.
)
(/usr/local/texlive/2023/texmf-dist/tex/latex/l3backend/l3backend-pdftex.def
File: l3backend-pdftex.def 2024-01-04 L3 backend support: PDF output (pdfTeX)
\l__color_backend_stack_int=\count273
\l__pdf_internal_box=\box53
)
No file cong.aux.
\openout1 = `cong.aux'.

LaTeX Font Info:    Checking defaults for OML/cmm/m/it on input line 7.
LaTeX Font Info:    ... okay on input line 7.
LaTeX Font Info:    Checking defaults for OMS/cmsy/m/n on input line 7.
LaTeX Font Info:    ... okay on input line 7.
LaTeX Font Info:    Checking defaults for OT1/cmr/m/n on input line 7.
LaTeX Font Info:    ... okay on input line 7.
LaTeX Font Info:    Checking defaults for T1/cmr/m/n on input line 7.
LaTeX Font Info:    ... okay on input line 7.
LaTeX Font Info:    Checking defaults for TS1/cmr/m/n on input line 7.
LaTeX Font Info:    ... okay on input line 7.
LaTeX Font Info:    Checking defaults for OMX/cmex/m/n on input line 7.
LaTeX Font Info:    ... okay on input line 7.
LaTeX Font Info:    Checking defaults for U/cmr/m/n on input line 7.
LaTeX Font Info:    ... okay on input line 7.
[1

{/usr/local/texlive/2023/texmf-var/fonts/map/pdftex/updmap/pdftex.map}]
(./cong.aux)
 ***********
LaTeX2e <2023-11-01> patch level 1
L3 programming layer <2024-01-22>
 ***********
 ) 
Here is how much of TeX's memory you used:
 2466 strings out of 474228
 50963 string characters out of 5746458
 1923975 words of memory out of 5000000
 24781 multiletter control sequences out of 15000+600000
 558523 words of font info for 38 fonts, out of 8000000 for 9000
 1141 hyphenation exceptions out of 8191
 56i,8n,65p,200b,1309s stack positions out of 10000i,1000n,20000p,200000b,200000s
</usr/local/texlive/2023/texmf-dist/fonts/type1/public/amsfonts/cm/cmmi10.pfb
></usr/local/texlive/2023/texmf-dist/fonts/type1/public/amsfonts/cm/cmr10.pfb><
/usr/local/texlive/2023/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy10.pfb>
Output written on cong.pdf (1 page, 25456 bytes).
PDF statistics:
 23 PDF objects out of 1000 (max. 8388607)
 13 compressed objects within 1 object stream
 0 named destinations out of 1000 (max. 500000)
 1 words of extra memory for PDF output out of 10000 (max. 10000000)
image

Possible solution:

Take the standard definition of \cong, that's not changed by amsmath and perform the standard works by amsmath

\NewCommandCopy{\cong@}{\cong}
\renewcommand{\cong}{\DOTSB\cong@}

Adding \DOTSB to the original code for \cong doesn't work because of \DeclareRobustCommand. However, using \protected\def wouldn't do anyway.

The workaround code would result in \DOTSB \cong@ written in the aux file, but that's normal and also \sum would lead to \DOTSB\sum@, for instance.

davidcarlisle commented 5 months ago

we could pull \DOTSB out of the protection but I think it might be better to use \protected\def and then make the lookahead cope with a \protected meaning (similar to how we added support for \newcommand aka \long\def a while back)

davidcarlisle commented 5 months ago

\documentclass{article}

  % Any preamble code goes here
\usepackage{amsmath}
\makeatletter

{\uccode`9=`\p %
  \uppercase{\gdef\stripprotected@#1#2#3\relax{%
      \ifx9#2 \@xp\@xp\@xp\zap@to@space\fi}}}

\def\mdots@@{\gdef\thedots@{\dotso@}%
 \ifx\@let@token\boldsymbol
   \gdef\thedots@\boldsymbol{\boldsymboldots@}%
 \else
   \ifx,\@let@token \gdef\thedots@{\dotsc}%
   \else
     \ifx\not\@let@token
       \gdef\thedots@{\dotsb@}%
     \else
       \keybin@
       \ifgtest@ % if \keybin@ test
         \gdef\thedots@{\dotsb@}%
       \else
         \xdef\meaning@{\meaning\@let@token. .........}%
         \xdef\meaning@@{\@xp\striplong@\meaning@\relax\meaning@}%
% NEW BIT
         \xdef\meaning@@{\@xp\stripprotected@\meaning@@\relax\meaning@@}%
% END NEW BIT
         \@xp\math@\meaning@\math@
         \ifgtest@ % if \mathxxx test
           \@xp\mathch@\meaning@\mathch@
           \ifgtest@ % if \mathchar
             \@xp\getmathch@\meaning@\getmathch@
           \fi % end if \mathchar
         \else  % \not \mathxxx
             \@xp\Umathch@\meaning@"0"\Umathch@
             \ifgtest@ % if \Umathchar
             \else % else not \Umathcar
           \@xp\macro@\meaning@@\macro@
           \ifgtest@ % if macro test
             \@xp\not@\meaning@\not@
             \ifgtest@ % if macro starts \not test
               \gdef\thedots@{\dotsb@}%
             \else% else not \not
               \@xp\DOTS@\meaning@\DOTS@
               \ifgtest@ % \if DOTS
                 \ifcase\number\DOTSCASE@ %ifcase dots
                   \gdef\thedots@{\dotsb@}%
                 \or\gdef\thedots@{\dotsi}\else
                 \fi % endifcase dots
               \else % not macro starts \DOTS
                 \@xp\math@\meaning@\math@
                 \ifgtest@ % \if macro starts \mathxxxx
                   \@xp\mathbin@\meaning@\mathbin@
                   \ifgtest@ % if macro starts \mathbin
                     \gdef\thedots@{\dotsb@}%
                   \else % not macro starting \mathbin
                     \@xp\mathrel@\meaning@\mathrel@
                     \ifgtest@ % if macro starts \mathrel
                       \gdef\thedots@{\dotsb@}%
                     \fi % endif macro starts \mathrel (no else)
                   \fi % endif macro starts \mathbin
                 \fi % endif macro starts with \mathxxx (no else)
               \fi % endif macro starts \DOTS else
             \fi % end macro  starting \not \ifgtest@ test (no else)
             \else
               \@xp\thecharacter@\meaning@\thecharacter@
             \fi % end macro \ifgtest@ test (no else)
           \fi % end if \Umathchar test
         \fi % end \math@   \ifgtest@
       \fi % end \keybin@ \ifgtest@ test (no else)
     \fi % end if \not (no else)
   \fi % end if comma (no else)
 \fi % end if boldsymbol (no else)
 \thedots@}

\protected
\def
  \cong{\mathrel{\mathpalette\@vereq\sim}} % congruence sign
\makeatother
\begin{document}

  % Demonstration of issue here
$\equiv\dots\equiv$

$\cong\dots\cong$

\end{document}
FrankMittelbach commented 5 months ago

we could pull \DOTSB out of the protection but I think it might be better to use \protected\def and then make the lookahead cope with a \protected meaning (similar to how we added support for \newcommand aka \long\def a while back)

agreed, but then we should alter this for all symbols that use \DOTSB etc, shouldn't we?

By the way where is \DOTSB in your code sample?

davidcarlisle commented 5 months ago

agreed, but then we should alter this for all symbols that use \DOTSB etc, shouldn't we?

probably or at least such a change gives a way forward for that to work.

By the way where is \DOTSB in your code sample?

it is not needed as once the lookahead can get past the \protected (or we could make it work with the existing \DeclareRobstCommand \protect version) then it sees the \mathrel so defaults to centred dots just a it does with \equiv in the example (which also has no \DOTS... prefix).

FrankMittelbach commented 1 week ago

wouldn't it be better to also handle robust macros of the form \protect\foo<space> by dropping the \protect and expanding the second macro? There are quite a number of symbols defined with \DeclareRobustCommand.

E.g., something like

+
+\def\stripprotect@#1#2\stripprotect@#3{\ifx#1\protect
+  \meaning#2\else
+  \meaning#3\fi. }
+
 \def\mdots@@{\gdef\thedots@{\dotso@}%
  \ifx\@let@token\boldsymbol
    \gdef\thedots@\boldsymbol{\boldsymboldots@}%
  \else
    \ifx,\@let@token \gdef\thedots@{\dotsc}%
    \else
      \ifx\not\@let@token
        \gdef\thedots@{\dotsb@}%
      \else
        \keybin@
        \ifgtest@ % if \keybin@ test
          \gdef\thedots@{\dotsb@}%
        \else
 %    \end{macrocode}
 % \changes{v2.15d}{2016/06/28}{Add space token to prevent runaway argument error}
 %    \begin{macrocode}
-         \xdef\meaning@{\meaning\@let@token. .........}%
+       \begingroup
+         \def\protect{\protect}% % make it a quark
+         \xdef\meaning@{\@xp\stripprotect@\@let@token. .........\stripprotect@\@let@token}%
+       \endgroup
 %    \end{macrocode}
 % In previous versions \verb|\long| macros were not seen by the lokkahead.
 % That was bad as this file uses \verb|\(re)newcommand| for \verb|\implies| etc.
 %    \begin{macrocode}
          \xdef\meaning@@{\@xp\striplong@\meaning@\relax\meaning@}%
davidcarlisle commented 1 week ago

@FrankMittelbach yes we should make \protect and \protected robust macros work

FrankMittelbach commented 1 week ago

@davidcarlisle does my code look reasonable (not sure I fully got the issues with "space" and all the dots (the latter presumably to have something for up to 9 arguments)) or do you see a better way to do this?

davidcarlisle commented 1 week ago

@FrankMittelbach I'll look later: not at home today

davidcarlisle commented 6 days ago

@FrankMittelbach looks good, I just wonder if having got that far we should go further and f-expand the token so that DOTSB hidden by macro exapnsion not just by macro expansion with \protect would work (you'd have to use a \noexpandrather than a quak version of\protect` probably.

specifically should the last, case e give centred dots here or is the system not seeing DOTSB in that case a feature

\documentclass{article}

\usepackage{amsmath}
\def\wibble#1#2#3{}
\def\aaa{\DOTSB ,\wibble xyz}
\protected\def\aab{\DOTSB ,\wibble xyz}
\def\aac{\protect\aaa}
\long\def\aad{\DOTSB ,\wibble xyz}
\def\aae{\aaa}

\begin{document}

$,\dots \aaa$ a\par
$,\dots \aab$ b\par
$,\dots \aac$ c\par
$,\dots \aad$ d\par
$,\dots \aae$ e\par

\end{document}

almost all the tokens we test for are unexpandable primitives so mostly f-expanding the token and testing just the first non expandable token wouldn't require much change