Dartの定数は定数を内包できない? ⇒ Dart 2で出来るようになりました ⇒ なってませんでした

#アップデート (2018.9.24)
初期化リスト内でのconstコンストラクタ呼び出しは未サポートだそうです。
理由は、(たぶんDartVMでの)起動を遅くしないため。
四則演算等の極限られた定数式のみが許されるようです。

ちなみに私のissueは、将来エンハンス要望が高まり、状況が許す場合に備えて、openしておくそうです。

#アップデート (2018.9.22)
~~Dart 2で改善されました。~~
~~基本的には代替案2が採用され、newも省略で万事(?)解決です。~~

```dart
class C {
  final x;
  const C(this.x);
}

class D {
  final C y;
  const D(x):y = C(x); // コンパイル時エラー! ではなくなった。
}

main(){
  const d = D(1);
  print(d.y.x);
}
```
[DartPadで実行](https://dartpad.dartlang.org/10cfd9921d22f31dbfceb69432382fd6 "DartPad")

~~4年の歳月を経てようやくこれが実現されたのは、Dartがバージョン2でコンパイルを重視した型安全な処理系に変わり、静的に深い解析を行うようになったのが要因だと思います。~~

~~とはいえ、まだDartPadでは変なエラーメッセージを出したり、コメントの有る行を`const D(x):y = const C(x);`と書いても通るなど、まだ動きが怪しいですが。~~

#まえおき
もちろん出来ますが、一見普通なのに通らないケースもあります。
まずは予備知識から。

## static, final, constの違い
Dartでは```static```と```final```と```const```はそれぞれ異なる意味を持っています。 [Dart News & Updates: Const, Static, Final, Oh my!](http://news.dartlang.org/2012/06/const-static-final-oh-my.html "Dart News & Updates: Const, Static, Final, Oh my!")

| キーワード | 修飾対象 | 説明 |
|:--:|:---------:|:--|
| ```static``` | メンバシップ | クラス定義でstaticと書くとクラスメンバになります。```new```しなくとも(変数:リファレンスが)存在するという意味で静的です。ただし、常に静的な存在のトップレベル変数では明示的に```static```を指定するとエラーになります。初期化の実行は **なんと** 最初の参照時です。 |
| ```final``` | 変数 | 1度だけ「代入」可能です。「代入」は初期化で行う必要があります。その後は変更不可です。厳密には初期化要、代入不可。初期化は通常のインスタンスメンバならnewの時、クラスメンバ(static final)やトップレベル変数なら先述の通り最初に参照した時、自分を包含するクラスのインスタンスが定数なら後述の通りコンパイル時です。 |
| ```const``` | 値 | コンパイル時定数値です。コレクションやユーザ定義クラスの定数オブジェクトを定義できますが、その場合、その値は「deep」にコンパイル時に決定的であることが必要です。値はcanonicalizeされます。(場合によっては、最適化がcanonicalizeされた唯一のオブジェクトも消しそう。) |

##定数コンストラクタ

Dartにはユーザ定義クラスの定数インスタンスを作るために定数コンストラクタがあります。

```dart:
class C {
  final x;
  const C(this.x);
}
```

対象となるクラスのインスタンスメンバ変数は全て```final```である必要があります。
そして、定数を定義する場合には```new```の代わりに```const```を用います。

```dart:
var c = const C(1); // リファレンスは変数
const C = const C(2); // リファレンスも定数 (最適化で消されるかも)
```

##コレクションを持つ定数
コレクションを持つユーザ定義クラスも定数化できます。

```dart:
class CC {
  final x;
  final List l;
  const CC(this.x, this.l);
}

var cc = const CC(1, const[true, false, const['a', 'b', 'c']]); // const[...]に注目
```

先述の通り、メンバのメンバも遷移的、再帰的にコンパイル時に決定的な定数式です。

#本題
##ユーザ定義クラスを持つ定数は作れない?
次に、ユーザ定義クラスを持つユーザ定義クラスの定数を宣言してみます。

```dart:
class D {
  final C y;
  const D(x):y = const C(x); // コンパイル時エラー!
}
```

ダメです。

##なぜダメか
Dart Editorのメッセージは、下記の通り。

> Arguments of a constant creation must be constant expressions

ここで言うArgumetとは```D(x)```の```x```ではなくて、```C(x)```の```x```です。

[ECMA-408 10.6.3 Constant Constructors
](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-408.pdf#page=37)によると、```x```は「潜在的定数式」であって「コンパイル時定数」ではないので```const C()```の引数には使えないよ、とのことです。

納得です...ん?

##なぜ、なぜダメか

```x```は定数コンストラクタ```D()``` の仮引数なんだから、実引数も定数って仮定してOKではないのか?

ところが、実は、定数コンストラクタは```const```のみならず、```new```で呼び出せるのです。

```dart:
var cv = new C(1); // コンパイル時じゃないよ
```

そうだとすると、

```dart:
var v;
//　:
//　:
var dv = new D(v);
```

のようなことが出来てしまい、この場合、

```dart:
  const D(x):y = const C(x) // xは変数vじゃねーか!!
````

のようなことが静的に想定されるので、コンストラクタ定義がコンパイル時エラーということです。

##なぜコレクションは大丈夫だったか?

コンストラクタ定義ではなく、コンストラクタ呼び出しにおいて、メンバであるコレクションをリテラルで初期化しているから、ですね。
つまり、コレクションのメンバも、そのメンバも、大元の```CC```のコンストラクタ呼び出しに登場し、全体が定数であることが表現できているから。

従って、コレクションでも次のように使うとNGです。

```dart:
class CC2 {
  final l;
  const CC2(x, y, z):this.l = const[x, y, z]; // コンパイル時エラー
}
```

なお、```const[...]``` の```const```は(メンバではなく)コレクション自体が定数(メンバの入れ替えもない)と宣言するためですね。
型推論してくれても良さそうですが。

##どうすれば良いか?
これなら通ります。

```dart:
class D2 {
  final C y;
  const D2(this.y);
}

var d2 = const D2(const C(1));
```

#代替案 (言語仕様提案)
こんな代替案を考えてみました。

1. 定数コンストラクタを```new```で呼び出せないようにする
2. 型推論で```const```指定を暗黙的に遷移的に適用する
3. 定数コンストラクタを```new```で呼び出したら、そのメンバ初期化の```const```を```new```に読み替えて実行する

1.は```new```と```const```でコンストラクタを作り分ける・使い分けるのがメンドクセ。

```dat:
class D {
  final C y;
  D.f(x):y = new C(x); // final版
  const D.c(x):y = const C(x); // const版
}

var d = new D.f(1);
const D = const D.c(2);
```

また、

> In contrast, the legal examples make sense regardless of whether the constructor is invoked via const or via new.

は信念のようなので、さらに、後方互換性がなくなるので、変えるのは難しいと思います。

2.なら```const[...]```の```const```が不要になりそう。でも、ユーザ定義クラスの場合、その初期化において敢えて```new```って書かせるのか? キモチワリ。

```dat:
class D3 {
  final C y;
  final l;
  const D3.c(x, this.l):y = new C(x); // 定数コンストラクタなのにnew C(x)って...
}

var d3 = const D3(3, [true, false, ['a', 'b', 'c']]); // [...]の前のconstなんて不要でしょ? まあ、new...の構文糖衣だけどね。
```

3.じゃ何がいけないんだろうか?
```new```で呼び出せる時点で定数コンストラクタの```const```は定数コンテキストで呼び出された場合 **も** 想定した事前チェックの指示でしか無いのだから、そのメンバ初期化における```const```も```new```に読み替えて動的に実行したところで何の問題もないはずですよね?

```dat:
class D {
  final C y;
  const D(x):y = const C(x); // コンパイル時はnewでの呼び出しも想定して new C(x) 呼び出しを展開
}

var d = new D(1); // 中でnew C(1)が呼び出される
const D = const D(2); // もちろん中ではconst C(2)が解釈される
```