エラーメッセージ定義を掘り下げる
Swift のコンパイルエラーの仕組み
ここでは Swift のコンパイルエラーのメッセージ定義を改めて掘り下げる。
メッセージ定義は .def ファイルの中でこのような形で書かれている。
ERROR(extra_rbrace,none,
"extraneous '}' at top level", ())
ERROR
の他に WARNING
と NOTE
もあるが形は同じである。
これがさらに利用方法に応じて様々な形に変えられる。その変換処理は #define
機能を使って非常に複雑に(どこが走っているかわかりにくい状態で)行われる。
各 .def ファイルに対応する.hファイルによる利用
.def ファイルには、それぞれ対応する .h ファイルがあり、まずはそのペアのつながりで .def ファイルがどう利用されるかを見ていく。
.def ファイルと .h ファイルのペアというのは、例えば
DiagnosticsParse.def
に対して
DiagnosticsParse.h
があるということ。
.def ファイルの中身は
#ifndef ERROR
# define ERROR(ID,Options,Text,Signature) \
DIAG(ERROR,ID,Options,Text,Signature)
#endifそれぞれのメッセージ定義が大量#ifndef DIAG_NO_UNDEF
# if defined(DIAG)
# undef DIAG
# endif
# undef ERROR
#endif
となっている。ポイントを挙げると
- ファイルの頭で、
ERROR
がすでに#define
されているかどうかを確認し、されていない場合には#define
を行い、ERROR
をDIAG
に置き換える設定をする(引数の表記は省略)。 - 個々のメッセージ定義の後、最後に
DIAG_NO_UNDEF
の状態によりdefine 状態の消去を行う。
ちなみに、 WARNING
と NOTE
は構造が ERROR
と同じなので省いてある、ここから先も同じ。
対応する .h ファイルの中身は
#include "swift/AST/DiagnosticsCommon.h"#define DIAG(KIND,ID,Options,Text,Signature) \
extern detail::DiagWithArguments<void Signature>::type ID;
#include "DiagnosticsParse.def"
DIAG
が#define
されて、
extern detail::DiagWithArguments<void Signature>::type ID;
になる。- その後で .def ファイルが
#include
される。
したがって、この .h ファイルを読み込んだ場合は、
ERROR
-> DIAG
-> detail::DiagWith~
という変換がされる。その過程は、
- .h ファイル内で
ERROR
は#define
されていないので、.def ファイル内のERROR
からDIAG
への#define
が有効になり、 - その
DIAG
が .h ファイル先頭の#define
によりdetail::DiagWith~
に置き換えられる。
となる。
また、 DIAG_NO_UNDEF
も#define
されていないので、.def ファイルの最後で define 状態が消される。
Common.h と Common.def
.h をよく見ると DiagnosticsCommon.h を #include
している。この DiagnosticsCommon.h (とそのペアの DiagnosticsCommon.def) は、コンパイルの中で汎用的に利用されるもの。やっていることは上と同じで
ERROR
-> DIAG
-> detail::DiagWith~
の変換を行い、最後に define 状態を消す。
DiagnosticsAll.def でも読み込んでいる
上に書いたのは各 .def ファイルに対応した .h ファイルを呼んだ時のことであるが、他にも各 .def ファイルを呼んでいるところがある。 DiagnosticsAll.def
である。これを見ていく。
#ifndef ERROR
# define ERROR(ID,Options,Text,Signature) \
DIAG(ERROR,ID,Options,Text,Signature)
#endif#define DIAG_NO_UNDEF#include "DiagnosticsCommon.def"
#include "DiagnosticsParse.def"
#include "DiagnosticsSema.def"
#include "DiagnosticsClangImporter.def"
#include "DiagnosticsSIL.def"
#include "DiagnosticsIRGen.def"
#include "DiagnosticsFrontend.def"
#include "DiagnosticsDriver.def"
#include "DiagnosticsRefactoring.def"#undef DIAG_NO_UNDEF#if defined(DIAG)
# undef DIAG
#endif
#undef ERROR
このDiagnosticsAll.def
は全ての .def ファイルを読み込むものである。真ん中の #include
の連続で読み込んでいる。
- まず先頭で
ERROR
が#define
されているか確認し、されていなければERROR
からDIAG
への変換を設定している。 - また、各 .def ファイルを読み込む前に
DIAG_NO_UNDEF
を#define
している。これは各 .def ファイル内でERROR
の#define
を消さないようにするためである。 - 各 .def ファイル内では、すでに
ERROR
が#define
されている場合は 再#define
しないので、このDiagnosticsAll.def
の先頭のERROR
の#define
が全体に渡り有効である。 DiagnosticsAll.def
ファイルの最後では例によって#define
したものを削除している。
DiagnosticsAll.def を読むところ
ではこのDiagnosticsAll.def
を読むところはどこか。
- /lib/AST/DiagnosticEngine.cpp
- /lib/AST/DiagnosticList.cpp
である。
/lib/AST/DiagnosticEngine.cpp について
DiagnosticsAll.def
を #include
しているところを見ていく。
enum LocalDiagID : uint32_t
enum LocalDiagID : uint32_t {
#define DIAG(KIND, ID, Options, Text, Signature) ID,
#include "swift/AST/DiagnosticsAll.def"
NumDiags
};
LocalDiagID
という enum を宣言している。ここでは DIAG
から ID
を取り出す #define
をしてから DiagnosticsAll.def
を読みこんでいて
ERROR
-> DIAG
-> ID
のみ
という変換が行われる。
置き換え先( ID,
)の最後の ,
は emun の要素の区切りになる。エラー識別文字列による enum を作って、ひとつひとつに数値を与えていることがわかる。0から順番に番号が振られて、最後の NumDiags
に個数が入る。
static StoredDiagnosticInfo storedDiagnosticInfos[]
static StoredDiagnosticInfo storedDiagnosticInfos[] = {
#define ERROR(ID, Options, Text, Signature) \
StoredDiagnosticInfo(DiagnosticKind::Error, DiagnosticOptions::Options),#include "swift/AST/DiagnosticsAll.def"
};
StoredDiagnosticInfo
の配列を宣言している。StoredDiagnosticInfo
については未開拓。
ここでは ERROR
を #define
しているので
ERROR
-> StoredDiagnosticInfo
という変換が行われる。ここで ERROR
が #define
されると、この先で ERROR
の上書き #define
はされない。DIAG
を経由しないで ERROR
を直接料理している。
static const char *diagnosticStrings[]
static const char *diagnosticStrings[] = {
#define ERROR(ID, Options, Text, Signature) Text,
#include "swift/AST/DiagnosticsAll.def"
"<not a diagnostic>",
};
ここは Text
部分を取り出して配列を作るところ。 上と同じように ERROR
の直接料理で、
ERROR
-> Text
のみ
という変換がされる。
/lib/AST/DiagnosticList.cpp について
次にもう一つのほう、DiagnosticList.cpp を見ていく。
enum class swift::DiagID : uint32_t
enum class swift::DiagID : uint32_t {
#define DIAG(KIND,ID,Options,Text,Signature) ID,
#include "swift/AST/DiagnosticsAll.def"
};
ここは ID
部分を取り出して enum を作るところ。ここもエラー識別文字列による enum を作って、ひとつひとつに数値を与えていることがわかる。
detail::DiagWithArguments<void Signature>::type ID
#define DIAG(KIND,ID,Options,Text,Signature) \
detail::DiagWithArguments<void Signature>::type ID = { DiagID::ID };
#include "swift/AST/DiagnosticsAll.def"
ここも置き換え。
extern detail::DiagWithArguments<void Signature>::type ID = { DiagID::ID };
に変換される。
この type
は /include/swift/AST/DiagnosticsCommon.h で
template<typename T>
struct DiagWithArguments;template<typename ...ArgTypes>
struct DiagWithArguments<void(ArgTypes...)> {
typedef Diag<ArgTypes...> type;
};
と定義されているが深そうなので、一旦切り上げる。
以上、メッセージ定義がどういう形になっていくかを見てきた。当然ここから先も調べる項目は残されている。調べたらまた記事にしたいと思う。
内容に間違いがあった場合、お知らせ下さい。