Robust command not expanding as \input argument

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.


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.