4

I could really use some help - I've been using LaTeX casually for several years, but have rarely had to dive into the details or problems, and I can't figure this out.

Problem

I am working with a .cls file given to me from my organization that has a (possibly) problematic line. It defines a macro that stores the second digit of the inputted point size, and then uses that to input a .clo file. A paired down MWE is as follows:

\NeedsTeXFormat{LaTeX2e}
\ProvidesClass{mwe}

\RequirePackage{etoolbox}
\newrobustcmd\@ptsize{}

\DeclareOption{10pt}{\renewrobustcmd\@ptsize{0}}
\DeclareOption{11pt}{\renewrobustcmd\@ptsize{1}}
\DeclareOption{12pt}{\renewrobustcmd\@ptsize{2}}

\ExecuteOptions{11pt}
\ProcessOptions
\input{size1\@ptsize.clo}

I've also created a MWE .tex file to compile:

\documentclass[11pt]{mwe}

\begin{document}
    
\end{document}

However, when I compile this using latexmk -synctex=1 -interaction=nonstopmode -file-line-error -pdf -outdir=%OUTDIR% %DOC% (inside VSCode using LaTeX Workshop), I get this log file with a message saying

`File `[email protected]' not found`

The log says:

This is pdfTeX, Version 3.14159265-2.6-1.40.21 (MiKTeX 20.11) (preloaded format=pdflatex 2020.11.3)  3 NOV 2020 08:57
entering extended mode
**"path/to/mwe/mwe.tex"
("path/to/mwe/mwe.tex"
LaTeX2e <2020-10-01> patch level 2
L3 programming layer <2020-10-27> xparse <2020-03-03> ("path/to\mwe\mwe.cls" <-- In case it matters, these slashes
Document Class: mwe                                                              really do switch directions
(path\to\tex/latex/etoolbox\etoolbox.sty                                     <-- Here as well
Package: etoolbox 2020/10/05 v2.5k e-TeX tools for LaTeX (JAW)
\etb@tempcnta=\count175
)

! LaTeX Error: File `[email protected]' not found.

Type X to quit or <RETURN> to proceed,
or enter new name. (Default extension: clo)

Enter file name: 

Potential Solution

Here's the thing: if I change the .cls file so that @ptsize is defined via \newcommand and \renewcommand instead of via \newrobustcmd and \renewrobustcmd, then the above MWE .tex file works just fine, generating a blank (two-page) PDF.

Could anybody a) explain why this is happening (as far as I know, this .cls file works fine for other people in my organization), and b) what the best-practices approach would be for this problem? Thank you in advance!

3
  • 2
    Welcome to TeX.SX! There have been some changes to how LaTeX processes file names, and one of those changes includes an enhanced file name parser that does not expand protected (robust) macros. Indeed, in a previous version of LaTeX your example works as you expect, but I'm inclined to say that it is the wrong behaviour. A robust command is really not supposed to be used like that, so the correct solution would be to change the class file to use \renewcommand there, instead of the robust version. Nov 3, 2020 at 14:25
  • This is fantastic! Thanks for the warm welcome and the quick, clear response. Just to clarify, I should be defining the command with \newcommand and using \renewcommand (not defining with \newrobustcmd and using \renewcommand)?
    – Adam Kern
    Nov 3, 2020 at 14:33
  • 1
    Yes. The distinction robust vs. non-robust is that certain commands “explode” in an expansion-only context (that is, inside an \edef, or a \write---for example when you use \includegraphics in a \caption, like here), so these commands are defined robust so that they do not expand in those places. But what you want with \@ptsize here is precisely the opposite: you want it to expand to 0 or 1 or 2, so robust is conceptually wrong here. I'll write an answer with a bit more detail (this change might affect other people too) Nov 3, 2020 at 14:40

2 Answers 2

4

The difference in behaviour can be seen in this example code:

\makeatletter
\protected\def\@ptsize{1}
\input{size1\@ptsize.clo}
\stop

which used to input size11.clo, but since the 2020-10-01 LaTeX release it doesn't anymore, instead it errors with:

! LaTeX Error: File `[email protected]' not found.

Type X to quit or <RETURN> to proceed,
or enter new name. (Default extension: clo)

Enter file name:

This was a deliberate change in LaTeX's file name parsing mechanism. It was replaced by a more robust parser that does not expand protected macros (along with several other goodies). The behaviour of the new parser is the correct one regarding robust macros: these cannot work in expansion-only contexts because they will break one way or another (you can find tons of examples of that around), so not expanding them is the reasonable thing to do.

The class file in your organization misuses robust commands to store some data (what we usually call a “token list”). Data like that mostly always have to be available to other macros, so it has to expand, thus it cannot be robust/protected. The right thing to do, if you can edit the class file is to replace \(re)newrobustcmd by \(re)newcommand:

\NeedsTeXFormat{LaTeX2e}
\ProvidesClass{mwe}

\RequirePackage{etoolbox}
\newcommand\@ptsize{}

\DeclareOption{10pt}{\renewcommand\@ptsize{0}}
\DeclareOption{11pt}{\renewcommand\@ptsize{1}}
\DeclareOption{12pt}{\renewcommand\@ptsize{2}}

\ExecuteOptions{11pt}
\ProcessOptions
\input{size1\@ptsize.clo}

For the record, the error message

! LaTeX Error: File `[email protected]' not found.

looks like that because the file name parser works with \escapechar=-1, then \@ptsize is hit with \string and becomes @ptsize.

2

If for some obscure reason it is to be insisted on storing data that should be retrievable in robust macros, then you can trigger the expansion and placing in correct order of tokens yourself, using some \expandafter/\romannumeral/argument-exchanging-trickery:

file mwe.cls:

\NeedsTeXFormat{LaTeX2e}
\ProvidesClass{mwe}

\RequirePackage{etoolbox}
\newrobustcmd\@ptsize{}

\DeclareOption{10pt}{\renewrobustcmd\@ptsize{0}}
\DeclareOption{11pt}{\renewrobustcmd\@ptsize{1}}
\DeclareOption{12pt}{\renewrobustcmd\@ptsize{2}}

\newcommand\exchangeargs[2]{#2#1}

\ExecuteOptions{11pt}
\ProcessOptions
% It is not relied on \input expanding and putting into correct order
% tokens of its argument.
% Instead \romannumeral-expansion brings all tokens into
% correct order before \input and \input's filename-parsing 
% come into action.
% (This way things might sustain more changes to \input's 
% filename-parsing.)
\expandafter\input\expandafter{%
  \romannumeral
  \expandafter\exchangeargs\expandafter{\@ptsize}{\z@ size1}.clo%
}

On my system compiling a file test.tex:

\documentclass{mwe}
\stop

yields this console-output:

$ pdflatex test.tex
This is pdfTeX, Version 3.14159265-2.6-1.40.21 (TeX Live 2020) (preloaded format=pdflatex)
 restricted \write18 enabled.
entering extended mode
(./test.tex
LaTeX2e <2020-10-01> patch level 2
L3 programming layer <2020-10-27> xparse <2020-03-03> (./mwe.cls
Document Class: mwe 
(/usr/local/texlive/2020/texmf-dist/tex/latex/etoolbox/etoolbox.sty)
(/usr/local/texlive/2020/texmf-dist/tex/latex/base/size11.clo)) )
No pages of output.
Transcript written on test.log.

and this file test.log:

This is pdfTeX, Version 3.14159265-2.6-1.40.21 (TeX Live 2020) (preloaded format=pdflatex 2020.11.23)  4 DEC 2020 19:47
entering extended mode
 restricted \write18 enabled.
 %&-line parsing enabled.
**test.tex
(./test.tex
LaTeX2e <2020-10-01> patch level 2
L3 programming layer <2020-10-27> xparse <2020-03-03> (./mwe.cls
Document Class: mwe 
(/usr/local/texlive/2020/texmf-dist/tex/latex/etoolbox/etoolbox.sty
Package: etoolbox 2020/10/05 v2.5k e-TeX tools for LaTeX (JAW)
\etb@tempcnta=\count175
)
(/usr/local/texlive/2020/texmf-dist/tex/latex/base/size11.clo
File: size11.clo 2020/04/10 v1.4m Standard LaTeX file (size option)
)) ) 
Here is how much of TeX's memory you used:
 364 strings out of 479485
 4393 string characters out of 5871962
 273140 words of memory out of 5000000
 17462 multiletter control sequences out of 15000+600000
 535388 words of font info for 30 fonts, out of 8000000 for 9000
 1141 hyphenation exceptions out of 8191
 52i,1n,59p,208b,36s stack positions out of 5000i,500n,10000p,200000b,80000s

No pages of output.
PDF statistics:
 0 PDF objects out of 1000 (max. 8388607)
 0 named destinations out of 1000 (max. 500000)
 1 words of extra memory for PDF output out of 10000 (max. 10000000)

To your questions:

a) explain why this is happening (as far as I know, this .cls file works fine for other people in my organization)

Significant changes were introduced to the LaTeX 2ε-kernel. One of the more recent changes is that within the argument of \input{...} robust macros are not expanded.

These changes are good and annoying in the same time: They are good because with many things the way they work now seems more stringent to me. They are annoying because I have to look at the code again and get used to it. ;-)

Probably the other people in your organization don't use one of the more recent releases of the LaTeX 2ε-kernel and therefore changes introduced with more recent releases of the LaTeX 2ε-kernel don't affect the way in which things work on their machines.

b) what the best-practices approach would be for this problem?

I don't know if these are "best practices", but since there have been so many changes in the last two years, I have stopped relying on LaTeX 2ε-kernel macros to work the ways I was used to over two decades. ;-)

Especially I don't rely any more on code maintained by others doing expansion in the same way it did two days ago and therefore I often have my own code do all the expansion-work before things are handed over as arguments to other people's macros.

I do not do this because I would be unhappy about the changes and innovations.

I do so to make my own code compatible with as many different LaTeX 2ε-releases as possible.

3
  • This is great, thank you! Just to be clear, the "argument-exchanging trickery" that you've used here will only work for \input, not for other cases, correct? Just trying to understand the generality vs specificity of this solution
    – Adam Kern
    Dec 7, 2020 at 14:57
  • 1
    @AdamKern The tricks I used for getting the tokens forming the argument of \input into correct order do not depend on the way in which \input works. These tricks have to do with the ways in which expansion works in TeX. These tricks are applied to get the tokens forming the argument of \input into correct order before starting to carry out \input. Such and similar tricks can be applied in order to get the tokens forming the arguments of whatsoever macros into correct order before carrying out these macros. If you are interested in such things, probably my answer to ... Dec 7, 2020 at 21:19
  • 1
    @AdamKern ... If you are interested in such things, probably my answer to How can I know the number of expandafters when appending to a csname macro? is of interest to you. In that answer I explain (or at least I try to explain) \expandafter, usage of \romannumeral for triggering expansion, and tricks related to exchanging macro-arguments. Dec 7, 2020 at 21:22

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .