存在するはずの LaTeX パッケージのオプションがなぜか未定義?
この記事は、サブブログの 2016-09-15 投稿記事を移転してきたものです。
早速ですが…
さて、問題です
\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
を発行する必要性がほとんど生じない(自分でパッケージの末尾に書けば済む場合がほとんど)なので、それでもよいということなのだろう。
そこで作ってみた
この現状「未考慮」とおぼしきものを大丈夫にしようとすると
みたいな感じになる。なおこのコードは、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 のドキュメントのどこにも書いておらず、少々意外だが「想定外」なのだろう。パッケージ作者各位は気をつけられたし。