Acetaminophen’s diary

化学に関すること,TeXに関すること,ゆきだるまに関すること。

存在するはずの LaTeX パッケージのオプションがなぜか未定義?

この記事は、サブブログの 2016-09-15 投稿記事を移転してきたものです。

早速ですが…

さて、問題です

以下の LaTeX ソースを処理してみる*1

\begin{filecontents}{test.sty}
\DeclareOption{TEST}{\typeout{*** TEST OPTION ***}}
\ProcessOptions
\AtEndOfPackage{\RequirePackage{xspace}} % !!!
\end{filecontents}
\documentclass{article}
\usepackage[TEST]{test}
\begin{document}
\end{document}

この !!! を付けた行で呼び出すパッケージによって、これがエラーになったりエラーにならなかったりする。エラーというのは

! LaTeX Error: Unknown option `TEST' for package `test'.

See the LaTeX manual or LaTeX Companion for explanation.
Type  H <return>  for immediate help.
 ...                                              
                                                  
l.8 \begin
          {document}
? 

というもの。LaTeX の required に含まれるパッケージ群 (graphics, tools) の中で、適当に試してみると、以下は OK なもの:

  • longtable
  • multicol
  • varioref
  • color
  • graphicx

以下は NG なもの:

  • alltt
  • array
  • enumerate
  • ftnright
  • hhline
  • somedefs
  • theorem
  • xspace

この違いはなんでしょう?

 

…どうやら、正解は

呼び出されたパッケージに何らかのオプションが実装されているかどうか

らしい。パッケージにオプションが実装されていない場合、妙なエラーが出るということである(LaTeX2e 2018-04-01 でも同様の結果)。

 

latex.ltx を読んでみる

解析結果は以下のとおり。

  • エラー本体は \@unknownoptionerror という名前。
  • これは \@@unprocessedoptions の中で使われている。
  • 実際の呼び出しは \@onefilewithoptions で行われる。
        \let\@unprocessedoptions\@@unprocessedoptions % <= ここでコピーされる
        \csname\@currname.\@currext-h@@k\endcsname % <= これに注目!
        \expandafter\let\csname\@currname.\@currext-h@@k\endcsname
                  \@undefined
        \@unprocessedoptions % <= ここで使われる
    
  • 一見そのままだと常にエラーになりそう…だがまだ続きがある。もし「いま読んでいるパッケージ」にオプションが実装されていれば話は別で、\ProcessOptions の下請け \@process@pti@ns がエラーを抑制する。
      \AtEndOfPackage{\let\@unprocessedoptions\relax}
    
    ここで使われている \AtEndOfPackage の引数は
        \csname\@currname.\@currext-h@@k\endcsname
    
    のタイミングで実行されることに注目。
  • 上記の説明は、呼び出し元のパッケージだけでなく呼び出された側のパッケージでも起きる。このため、\AtEndOfPackage のなかで呼び出されたパッケージが「エラー抑制」をしなかった(= \ProcessOptions が使われていなかった)場合、呼び出し元のパッケージに波及してエラーを吐いてしまうようだ…。

これで理由までは分かった。でも単に「想定外」な気もする…。

 

紛らわしいけど OK なケースと NG なケース

元のソースをちょっとずつ変えてみよう。

% NG
\begin{filecontents}{test1.sty}
\DeclareOption{loadxspace}{\relax}
\ProcessOptions
\AtEndOfPackage{\RequirePackage{xspace}}
\end{filecontents}
\documentclass{article}
\usepackage[loadxspace]{test1}
\begin{document}
\end{document}

はエラーになる。しかし

% OK
\begin{filecontents}{test0.sty}
\DeclareOption{loadxspace}{\AtEndOfPackage{\RequirePackage{xspace}}}
\ProcessOptions
\end{filecontents}
\documentclass{article}
\usepackage[loadxspace]{test0}
\begin{document}
\end{document}

は正常に通る。これは先ほどの解析結果から特に不思議なことではないし、実際にこの OK な書式は幾つかのパッケージで使われている。

上の「NG になるケース」についてもう少し厳密にいうと

パッケージ内で \ProcessOptions した後に、\AtEndOfPackage{\RequirePackage{hoge}} されることが現状「未考慮」である

ということなのだと思う。そもそも、\DeclareOption 以外の場所で \AtEndOfPackage を発行する必要性がほとんど生じない(自分でパッケージの末尾に書けば済む場合がほとんど)なので、それでもよいということなのだろう。

 

そこで作ってみた

この現状「未考慮」とおぼしきものを大丈夫にしようとすると

  • コレ (Gist: 20160916-AtEndOfPackage-RequirePackage-test.tex)

みたいな感じになる。なおこのコードは、keyval を使うパッケージが内部に持っている「エラーを出さないようにするハンドリング」も考慮したつもりである。

→更新 (2020-09-30):ついに LaTeX2e 2020-10-01 で,拙作のコードが取り込まれて問題が解消する見込みです。参考:latex3/latex2e#22

 

ちなみに:別の「アレ」な挙動と組み合わせてもっと「アレ」

もうひとつ妙な挙動がある。

この回答では、\AtEndOfPackage{\RequirePackage{hoge}} とするのが workaround とされている。そうすることで、「親パッケージ読み込みが完全に終了したあとに、子パッケージを読み込む」ということが可能になるという算段である。このケースも考えることにすると、先ほど述べたばかりの

自分でパッケージの末尾に書けば済む場合がほとんど

は必ずしも成り立たない。実際にこの TeX.SX のソースと、冒頭の不可解な挙動の合わせ技を見てみよう。すなわち「二つのパッケージが互いに相手を必要としている場合」である。

\documentclass{article}

\begin{filecontents}{test0.sty}
\DeclareOption{FOO}{\typeout{*** FOO OPTION ***}}
\ProcessOptions\relax
\RequirePackage{test1}
%\AtEndOfPackage{\RequirePackage{test1}} % <= "workaround" proposed in TeX.SX
\end{filecontents}

\begin{filecontents}{test1.sty}
\RequirePackage{test0}
\end{filecontents}

\usepackage[FOO]{test0}

\begin{document}

\end{document}

試してみるとわかることだが

\ProcessOptions よりあとに、オプション実装を持たない子パッケージを読み込むと、どうやっても(素朴に \RequirePackage しても TeX.SX 的な workaround をしても)エラーが出る

という困った結果になる*2。「\RequirePackage\ProcessOptions よりあとに書いてはダメ」というのは LaTeX のドキュメントのどこにも書いておらず、少々意外だが「想定外」なのだろう。パッケージ作者各位は気をつけられたし。

*1:簡略化のため filecontents 環境を使っているが、これは「環境内の命令を test.sty というファイルに保存して同じディレクトリに置く」というのと同じである。

*2:ちなみに、このケースに対する正しい workaround は「test0.sty のなかで \RequirePackage{test1} を \ProcessOptions より前に持ってくる」である。