nim-lang/Nim に issue を建ててちょっと恥ずかしい思いをした話
TL;DR
- Nim で C++ の std::set のラッパーを書いた
- 奇妙なコンパイラのバグを踏んだのでNimにissueを建てた
- 4年前に建てられていたissue と同様のものだったために close された、恥ずかし〜
はじめに
私は Nim が好きでNimをよく書いています。 最近AtCoderでstd::setを使う問題がよく出題されており、 Nimでも C++ の std::set を使えるようにラッパーを作っていました。
ところが、これを使用した方からコンパイルエラーが出るという報告をいただきました。
コンパイルエラー
type CSet {.importcpp: "std::set", header: "<set>".} [T] = object proc cInitSet(T: typedesc): CSet[T] {.importcpp: "std::set<'*1>()", nodecl.} proc insert[T](self: var CSet[T],x:T) {.importcpp: "#.insert(@)", nodecl.} proc `==`[T](x,y:CSet[T]):bool{.importcpp: "#==#", nodecl.} var (S1,S2) = (cInitSet(int),cInitSet(int)) S1.insert 1 if S1 == S2: echo "wrong" else: echo "correct"
このコードをコンパイルすると、以下のエラーがでて、NimからC++に変換した後のC++のコンパイルに失敗します。
Error: execution of an external compiler program 'clang++ -c -w -~/.choosenim/toolchains/nim-0.20.0/lib -I~/codes/nim/ -o ~/.cache/nim/t_d/t.nim.cpp.o ~/.cache/nim/t_d/t.nim.cpp' failed with exit code: 1 ~/.cache/nim/t_d/t.nim.cpp:153:7: error: invalid argument type 'TY_89ajMAHL4D29bP2aGRstu1mQ' (aka 'set<long long>') to unary expression if (!S1_X5vbOHyI0SI13LVkxEIzEQ==S2_q2YrwwKwgOf4xdemDnRTYA) goto LA4_; ^~~~~~~~~~~~~~~~~~~~~~~~~~ 1 error generated.
ほう、という感じですね、
このC++コードの全文は以下です。
N_LIB_PRIVATE N_NIMCALL(void, NimMainModule)(void) { { tyTuple_WVskh7WUQHL0ugdByxngAw T1_; nimfr_("t", "/path/to/t.nim"); nimln_(8, "/path/to/t.nim"); T1_.Field0 = std::set<NI>(); T1_.Field1 = std::set<NI>(); S1_X5vbOHyI0SI13LVkxEIzEQ = T1_.Field0; S2_q2YrwwKwgOf4xdemDnRTYA = T1_.Field1; nimln_(9, "/path/to/t.nim"); S1_X5vbOHyI0SI13LVkxEIzEQ.insert(((NI) 1)); nimln_(10, "/path/to/t.nim"); { if (!S1_X5vbOHyI0SI13LVkxEIzEQ==S2_q2YrwwKwgOf4xdemDnRTYA) goto LA4_; { echoBinSafe(TM_earX20ePV9cxitomwdUMS7g_2, 1); } } goto LA2_; LA4_: ; { nimln_(11, "/path/to/t.nim"); echoBinSafe(TM_earX20ePV9cxitomwdUMS7g_4, 1); } LA2_: ; popFrame(); } }
このエラーを見ると分かるように、要は if S1 == S2: ...
の条件分岐を C++ に翻訳した時に、 if (!S1 == S2 ) goto LA4_;
になってしまっているようです。
条件分岐をミスっている様子で、これは正解は if (!(S1 == S2 )) goto LA4_;
となるべきですね。
つまり、if A == B: X else: Y
をC++に翻訳する際に、 if (!(A == B))Y; else X;
となるべきところを、 if (!A == B)Y; else X;
としてしまっているわけです。どうして否定が入っているのかはわかりませんが、条件分岐のときにだけコンパイルが失敗するので、最適化か何かの都合で混入したバグなのでしょう。
nim-lang/Nim に issue を建てる
このバグは最新のNim0.20 でも発生します。流石にこれを放っておくわけにもいかないので issue を建ててみます。
勿論、(これはOSSを使うときには今までもよくあることなのですが) すでに issue で議論されていないか確認すべきです。
今回のissueに関連するワード equal
function
if
override
の単語で検索し、既に議論されていないか確認します。
無さそうだったので、早速 issue を建てます。
こんな感じで、Bug Report のissueを建てようとすると、指針としてテンプレートが既に書かれています。便利ですね。 これに従って書いていけばよさそうです。
Think about the title, twice
と初めに書いてあります。タイトルは大事ですからね。 普段技術文書を英語で読むことはよくあるのですが、それに対して英語を書くのはとても苦手なので最低限文法が間違っていないかをgrammarly でチェックします。
英語の文法ミス・綴りミスを直すためのサイトを検索するのに綴りミスをしている図です pic.twitter.com/fTGUfNc8ob
— むらため (> <) (@paradigm_9) September 12, 2019
そしてこれは戒めなのですが、最低限文法が通っているにしても意味が最低限間違っていないかをチェックするために Google 翻訳に2回かけるやつもやっておくべきでした...(英語→日本語→英語 or 日本語→英語→日本語 をして意味が崩れないかチェックするやつ)
例えば 建てた issue の冒頭、
Overriding equal function causes incorrect behaviour. Nim compiler translates !(a == b) into !a == b and fails to compile.
と拙い英語で書いていたのも、
Overriding an equal function will cause incorrect behavior. Nim compiler converts !(a == b)! to a == b and fails to compile.
になって、なんとなくこっちのほうがいい感じな気がします(非ネイティブ並の感想...)*1。
なんやかんやあって issue を建てました。 github.com
回答が来る
要は # == #
(#は出てきた順にその番目の引数を表す) ではなくて、 (# == #)
と定義してくれとのこと。なるほど *2。
Low Priority
のタグが付きます。
実は duplicate issue だった
その後、これは duplicate issue だよ、と指摘されます。あちゃ〜やっちまった〜〜。
そういうわけで4年前に建てられたissueと同一の内容であったので close されました。
Low Priority
なので 4年間放置されていたバグであっても仕方がないですね。
恥ずかし〜〜〜〜〜〜〜〜〜〜
どうすれば duplicate issue を回避できたか、 equal
function
if
override
で検索したのに加えて C++
compile
error
でも検索する、もしくは Label:C++ codegen
を探しておく、が正解だったっぽいですね。
う〜 。
未来永劫 duplicate issue を建てた者としてnim-lang/Nimに記録が残るのはちょっと悲しいですね〜〜〜。
総計 12028 件の Nimのissueのうち duplicate
を含む issue は たった 502 件 しかなくてそのうちの一つになっちゃったな〜〜〜〜。
うひ〜〜〜。
あ〜〜〜〜〜〜〜〜。
おわり。