規格通りパースできないC言語

FreeBSDのstdarg.hとstdio.hで、va_listの定義が重複しているのを見つけました。一応#ifdefで囲われてはいますが、使われているマクロ名が違うために両方通ってしまうというお粗末さです。でもFreeBSDのヘッダーに文句を言いたいわけではなくて。(……まあstdarg.hはgcc付属でFreeBSDのものではないから責めてもしょうがない)
お使いのコンパイラで、次のソースをコンパイルしてみてください。

#include <stdarg.h>
typedef __builtin_va_list va_list;
typedef __builtin_va_list va_list;

gccの独自仕様かと思ったら、Borland C++*1でも(__builtin_va_listは無いため他の型で)通ってしまいました。しかもBorland C++では実体が同じならなんでも通りますが、gccではva_list以外だとエラーになるんですよね……何故だ……。
これの何が問題でしょうか。実体が同じなら重複してもOKじゃね?と思ってしまいますが……。
C99の宣言文はこうなってます。

(6.7) declaration: 
        declaration-specifiers init-declarator-list(opt) ; 

declaration-specifiersは、static int const *a, b[10];とか書いた時のstatic int constの部分、init-declarator-listは、その後ろに続くdeclaratorのカンマ区切りリスト。この例ですとdeclaratorは*aとb[10]になります。*2
で、declaration-specifiersはこうなってます。

(6.7) declaration-specifiers: 
        storage-class-specifier declaration-specifiers(opt) 
        type-specifier declaration-specifiers(opt) 
        type-qualifier declaration-specifiers(opt)
        function-specifier declaration-specifiers(opt) 

実際のコンパイラでは__attribute__や__declspecやなんやかやも追加されていますが、とりあえず規格はこう。
要するにstaticとかintとかconstとかinlineとか順不同で好きなだけ並べて書けますよ、という定義です。これに従うとint int int a;なんて宣言も、構文解析レベルでは有効です。(当然意味解析ではねられますが)
で、ユーザーがtypedefしたシンボルは、type-specifierとして扱われます。typedefしたシンボルを構文解析レベルで把握しておくのは、C言語をパースするときの必須事項で、そうでないと(a)(b, c)なんて式が、関数呼び出しなのか、キャストなのか(その場合カンマはカンマ演算子でbは無視して(a)cの意味)、区別できなくなります。意味解析の段階まで保留にしようも、関数呼び出しとキャストは結合の優先順位すら違います。C言語爆発しろ!
さて、最初のva_listに戻ります。ひとつめの宣言は、こう解釈されますが……。

typedef __builtin_va_list】←declaration-specifiers【va_list】←init-declarator-list【;】

もうおわかりですね。ふたつめの宣言はこうなるはずなのです、本来は。

typedef __builtin_va_list va_list】←declaration-specifiers【】←init-declarator-list【;】

ふたつめのva_listも定義済みの型ですので、type-specifierのはずなのです。つまりこの宣言は、何も宣言していないdeclaration-specifiersだけの宣言として構文解析を通り(タグのみのstruct宣言のように、init-declarator-listが空の宣言文自体は合法です)、意味解析の段階で、int int int a;がエラーになるのと同じ理由で__builtin_va_listとva_listが同時に指定できない旨のエラーになるはずなのです、本来は。
ところが、gccBorland C++も、これを通してしまっています。エラーにする場合も"previous declaration of ‘va_list’ was here"(gccの場合)なんて言うからには、ふたつめのva_listもdeclaratorとして解釈しているわけです。
これがどういうことかといいますと、declaration-specifiersを構文解析するためには、文法に書かれていない暗黙の追加ルールに従う必要があるということです。C言語爆発しろ!
暗黙の追加ルール: 構文解析段階で型が複数併記されていることを認識して、二つ目の型が出てきたらそこでdeclaration-specifiersを打ち切って残りはinit-declarator-listに回さないといけない。C言語爆発しろ!
つまり、signed intやfloat _ImaginaryみたいなのはOKですが、int32_t int64_tみたいなのは、後ろのint64_tをinit-declarator-listに回すということです。C言語爆発しろ!
これを怠ると、というか怠るもなにも規格に書かれてない動作なんですが、これに従わないと、FreeBSDの標準ヘッダーファイルでエラーを出すコンパイラができてしまいます。C言語爆発しろ!

どうでもいいですが結構仲間がいた。google:"C言語爆発しろ"

追記

稲葉さんから指摘をいただきました。

typedefで定義した型は、type-specifierになりますが、本来のidentifierとして扱ってはいけない決まりはどこにもない……確かに……。
それだけですと文法が曖昧になって、例えばstatic int32_t a;みたいな文で、declaration-specifiersなのはstaticだけで、int32_t以降をinit-declaration-listとして解釈しようとしてカンマ抜きでaが出てきた時点でSyntax error!なんて無茶な構文解析もまかり通るので、6.7.2の2段落目のConstraints(ひとつのdeclaration-specifiersに型はひとつだけ)を加えて、ひとつ目はtype-specifier扱いでdeclaration-specifiersのうちとして解釈して、ふたつ目以降はidentifierとしてinit-declarator-listに移る、と解釈しているのが、gccBorland C++のパーサである、と……確かに……。

すると……上記「暗黙の追加ルール」が規格に書かれていないというのは嘘になりますね。皆さんごめんなさい。でもしかし……それでは……yacc等を使っていてこんな柔軟な条件分岐をしていない処理系は全て規格違反の恐れが……!?(←嘘です。構文解析を無事通っても意味解析段階で同じ名前が2回宣言された旨のエラーになるはずなので、どの段階でエラーになるかの違いだけ……本来……Borlandのは通しちゃいますが……gccとbcc32以外でも試すべきでしょうか?)

追記

http://d.hatena.ne.jp/RiSK/20101203/1291362575

C11では公式にtypedefの繰り返しが認められたようです。

*1:BDS2006のやつ。

*2:bはポインタにならないので注意!なんてここで敢えて書くと逆にYTってこんなこと見つけて喜んでるなんて初心者じゃね?って疑われるレベル……なんかすいません。