読者です 読者をやめる 読者になる 読者になる

Clangのコード補完機能とシステムインクルードファイルの問題

Clangのコード補完機能

Clangを用いるとC/C++のコード補完を行うことができます。

例として次のようなC++コードを考えてみます。

#include <vector>

int main(int argc, char* argv[]) {
    std::vector<int> v;
    v.
    return 0;
}

ここでv.の直後における適切な補完候補(つまりstd::vectorのメンバ)の一覧をClangに出力させてみましょう。 test.cppにこのコードが含まれているとすると、v.の直後の位置はコードの5行7列目であるので、次のコマンドラインにより出力させることができます。

% clang -fsyntax-only -Xclang -code-completion-at=test.cpp:5:7 test.cpp
COMPLETION: assign : [#void#]assign(<#size_type __n#>, <#const value_type &__val#>)
COMPLETION: assign : [#void#]assign(<#_InputIterator __first#>, <#_InputIterator __last#>)
COMPLETION: at : [#reference#]at(<#size_type __n#>)
COMPLETION: at : [#const_reference#]at(<#size_type __n#>)[# const#]
COMPLETION: back : [#reference#]back()
COMPLETION: back : [#const_reference#]back()[# const#]
COMPLETION: begin : [#iterator#]begin()
COMPLETION: begin : [#const_iterator#]begin()[# const#]
COMPLETION: capacity : [#size_type#]capacity()[# const#]
COMPLETION: clear : [#void#]clear()
COMPLETION: data : [#pointer#]data()
COMPLETION: data : [#const_pointer#]data()[# const#]
COMPLETION: empty : [#bool#]empty()[# const#]
COMPLETION: end : [#iterator#]end()
COMPLETION: end : [#const_iterator#]end()[# const#]
COMPLETION: erase : [#iterator#]erase(<#iterator __position#>)
COMPLETION: erase : [#iterator#]erase(<#iterator __first#>, <#iterator __last#>)
COMPLETION: front : [#reference#]front()
COMPLETION: front : [#const_reference#]front()[# const#]
COMPLETION: get_allocator : [#allocator_type#]get_allocator()[# const#]
COMPLETION: insert : [#iterator#]insert(<#iterator __position#>, <#const value_type &__x#>)
COMPLETION: insert : [#void#]insert(<#iterator __position#>, <#size_type __n#>, <#const value_type &__x#>)
COMPLETION: insert : [#void#]insert(<#iterator __position#>, <#_InputIterator __first#>, <#_InputIterator __last#>)
COMPLETION: max_size : [#size_type#]max_size()[# const#]
COMPLETION: operator= : [#std::vector<int, std::allocator<int> > &#]operator=(<#const std::vector<int, std::allocator<int> > &__x#>)
COMPLETION: operator= (Hidden) : [#std::_Vector_base<int, std::allocator<int> > &#]std::_Vector_base<int, allocator<int> >::operator=(<#const std::_Vector_base<int, std::allocator<int> > &#>)
COMPLETION: operator[] : [#reference#]operator[](<#size_type __n#>)
COMPLETION: operator[] : [#const_reference#]operator[](<#size_type __n#>)[# const#]
COMPLETION: pop_back : [#void#]pop_back()
COMPLETION: push_back : [#void#]push_back(<#const value_type &__x#>)
COMPLETION: rbegin : [#reverse_iterator#]rbegin()
COMPLETION: rbegin : [#const_reverse_iterator#]rbegin()[# const#]
COMPLETION: rend : [#reverse_iterator#]rend()
COMPLETION: rend : [#const_reverse_iterator#]rend()[# const#]
COMPLETION: reserve : [#void#]reserve(<#size_type __n#>)
COMPLETION: resize : [#void#]resize(<#size_type __new_size#>{#, <#value_type __x#>#})
COMPLETION: size : [#size_type#]size()[# const#]
COMPLETION: swap : [#void#]swap(<#std::vector<int, std::allocator<int> > &__x#>)
COMPLETION: vector : vector::
COMPLETION: vector : [#void#]vector(<#_InputIterator __first#>, <#_InputIterator __last#>{#, <#const allocator_type &__a#>#})
COMPLETION: ~_Vector_base : [#void#][#_Vector_base<int, allocator<int> >::#]~_Vector_base()
COMPLETION: ~vector : [#void#]~vector()

それらしい候補が出力されていますね。

システムインクルードファイル問題

Clangのコマンドclangには、指定するフラグに応じて「ドライバ」を呼び出す場合と、「フロントエンド」呼び出す場合とがあります。 単純にclangとして呼び出すと、GCCと互換性のある「ドライバ」を呼び出します。 また、clang -cc1として呼び出すと「コンパイラフロントエンド」を直接呼び出します。

ドライバはClangのユーザー用のインターフェイスで、内部でユーザーに代わりフロントエンドを呼び出してくれます。ドライバはフロントエンドの呼び出しに際し、ユーザーのシステム向けに適切なフラグを付与して呼び出します。僕の調べた範囲では、例えばシステムの標準インクルードファイルのパス指定などの設定が含まれているようです。 またドライバに対し、特定のフラグをフロントエンドに渡すように指定することもできます。 先程の例では-Xclang -code-completion-at=test.cpp:5:7とドライバに指定することで、内部でclang -cc1-code-completion-at=test.cpp:5:7というフラグが渡されています。

このように、コード補完機能を利用するのに必要な-code-completion-at=...というフラグは、フロントエンド固有のものであり、ドライバのフラグではありません。従って、コード補完機能を利用するだけであればドライバを利用する必要はなく、フロントエンドを直接呼び出せば良いことになります。実際にClangのリポジトリに入っているEmacs用の補完プラグインclang-completion-mode.elでは、フロントエンドを直接呼び出しています(148行目)

しかし、この方法ではドライバがやってくれるインクルードパスなどの自動設定の恩恵にあずかれません。 試しに先の補完候補を出力させる例を、フロントエンドを直接叩いてやってみようとすると

% clang -cc1 -fsyntax-only -code-completion-at=test.cpp:5:7 test.cpp
test.cpp:1:10: fatal error: 'vector' file not found
#include <vector>
         ^
1 error generated.

となり、システムの標準インクルードファイルが見つからないと言われてしまいます。

この問題は実際にEmacsの自動補完プラグインなどでも報告されています。 例えば代表的なauto-complete-clangでも、同様の問題が起きています。解決策も示されていますが、場当たり的な対処でしかありません。 恐らく最初の実行例で示したように、clangドライバに頼るのが良い解決策だと思います。

[追記:2014-02-11 17:00]auto-complete-clangPull Requestを出しました。