TypeScriptコンパイラにおけるconst
今回、const のコンパイルにおける特有な要素を紹介していきたいと思う。
まず、constの基本から始めよう:constとは再代入できない変数宣言である。これはTypeScriptもJavaScriptも変わらない。
しかし、constの再代入エラーの際に一つ注意すべき点がある。JavaScriptはインタプリタ型言語なので、再代入エラーは実行時に起き、例外処理の一環としてthrow
される。
以下はMDNのドキュメントで使われる例:
const number = 42;
try {
number = 99;
} catch (err) {
console.log(err);
// 型エラー: const `number'への無効な代入
}
console.log(number);
// 出力: 42
これに対し、TypeScriptはコンパイル型であるが故に実行前にエラーを報告する。実行前というと、VS Code ならば、エラーの赤い波線が挙げられる。
ちなみに、const
とlet
宣言の処理は非常に似ているため、発生し得るエラー以外ほとんど何も変わらない。要は、かなり短い記事になる。
構文解析
ここでバインド以外の処理段階におけるconst
とlet
との唯一の違いを紹介する。
変数宣言のコンパイル手順をより詳しく知りたい方はぜひ前回の記事を参照してください。
AST生成
AST生成にて、parser.ts でparseVariableDeclarationList
で const のフラグをつけ、「こいつは const だよ」と伝える。
switch (token()) {
...
case SyntaxKind.ConstKeyword:
flags |= NodeFlags.Const;
break;
...
}
constの仕組み
バインド
バインドを担当する binder.ts にはsymbolTable
(シンボルテーブル)というグローバル変数がある。
このテーブルは、ソースに現れるあらゆるシンボル(変数名・関数名・クラス名・型名・モジュール名など)を保管した上で、あるシンボルに対する全ての宣言も記録する。
バインド段階をより詳しく理解するためにdeclareSymbol
関数を見ていきましょう。
declareSymbol
関数の働きを次のようにまとめることができる:
- まず、現在評価している宣言における識別子はすでにシンボルテーブルにが存在しているかどうか判明
- なければ、新しいシンボルを生成
- 同じブロックに既存の宣言があれば、重複宣言エラーを報告
- シンボルに今回の宣言を追加
- シンボルを返す
これでconst
宣言と値を結合することができた。
今度はconst
ならではのエラーを紹介する。
再代入エラー
よほど純粋的なプログラミングを用いない限り、定数宣言において最もの簡単に起きるエラーは再代入エラーだろう:
const x = 0;
...
x = 1; // 再代入エラー
再代入エラーは型チェック中に起きる。checkIdentifier
(checker.ts) で識別子を確かめる際、定数宣言にあたると判明される。
// const は読み取り専用データの一種
if (isReadonlySymbol(localOrExportSymbol)) {
// この読み取り専用データは変数ならば、const だと断言できる
if (localOrExportSymbol.flags & SymbolFlags.Variable) {
error(node, Diagnostics.Cannot_assign_to_0_because_it_is_a_constant, symbolToString(symbol));
}
...
右下のDiagnostics.Cannot_assign_to_0_because_it_is_a_constant
が報告された、const 文法の誤用をチェックする。
未初期化エラー
const x; // あれ?
checkGrammarVariableDeclaration
(checker.ts)でconst
変数がちゃんと初期化されたか確認される。
else if (!node.initializer) {
...
case NodeFlags.Const:
return grammarErrorOnNode(node, Diagnostics._0_declarations_must_be_initialized, "const");
...
}
以上、const
特有のエラー紹介だった。
まとめ
ご覧いただいたように、const
とlet
のコンパイル手順はほぼ等しい。
値の代入の際にconst
特有のエラーも、実は二つしかないということだ。
参考資料
直接TypeScriptコンパイラのソースを参照したい方は以下のURLをご利用ください:
- parser.ts:
const
フラグ - binder.ts:
declareSymbol
関数 - checker.ts:残念ながら、このファイルは5万行に及ぶせいでGitHubにて表示できない。ソースをご覧になりたい方はTypeScriptプロジェクトのGet Startedセクションを参照し、TypeScriptをクローンしてください。
-
git clone
に--depth=1
をつけるとダウンロード時間は大幅に減るのでぜひ、このオプションを使ってください。
-