HaxeのAbstract typeで型変換がスキップされるバグ（？）

既知のバグ（あるいは仕様）かもしれないですが、困ったのでメモします。
__@mandel59 さんによる指摘で、開発版のHaxeにおいてこの問題は正しくコンパイルエラーになることが確認されました。__

# 事件簿

> ここにあるコードはまとめたものが以下にあります。
> https://gist.github.com/yoshimuraYuu/372caed884669e03ae21
> http://try.haxe.org/#60785

まずは二つの単一のコンストラクタを持つデータ型`Alice`と`Bob`を作ります。（コンストラクタは適当です）

```hx:Alice.hx
enum Alice {
	AliceConst;
}
```

```hx:Bob.hx
enum Bob {
	BobConst;
}
```

この二つをまとめて扱うための型`Person`を定義します。

```hx:Person.hx
enum Person<X> {
	A(x : Alice) : Person<Alice>;
	B(x : Bob)   : Person<Bob>;
}
```

`Person<Alice>`と`Alice`、そして`Person<Bob>`と`Bob`の間の型変換を定義するabstract typeを定義します。

```hx:AbsPerson
abstract AbsPerson<X>(Person<X>) {
	public function new(x) {
		this = x;
	}

	@:from public static function fromAlice(a : Alice) : AbsPerson<Alice> {
		return new AbsPerson(A(a));
	}

	@:from public static function fromBob(b : Bob) : AbsPerson<Bob> {
		return new AbsPerson(B(b));
	}

	@:from public static function fromPerson<T>(x : Person<T>) : AbsPerson<T> {
		return new AbsPerson(x);
	}

	@:to public function toX() : X {
		return switch(this) {
			case A(a): a;
			case B(b): b;
		}
	}

	@:to public function toAbsPerson() : AbsPerson<X> {
		return this;
	}
}
```

これは @mandel59 さんの[GADTを使った多相型に関する記事](http://qiita.com/mandel59/items/1fc4ffa8adb55ea03e09)を真似しました。

ここで、`Alice`に関する処理（関数）と`Bob`に関する処理（関数）を定義したいですが、abstract typeで多相にしたので、これを用いて次のようなインターフェースを定義します。

```hx:PersonInterface.hx
interface PersonInterface<X> {
	public function make() : AbsPerson<X>;
}
```

この型パラメータ`X`には`Alice`や`Bob`が入ることになります。これの実装を`Alice`と`Bob`でそれぞれ次のように作ります。

```hx:AliceFactory.hx
class AliceFactory implements PersonInterface<Alice> {
	public function new() { }

	public function make() : Alice {
		return AliceConst;
	}
}
```

```hx:BobFactory.hx
class BobFactory implements PersonInterface<Bob> {
	public function new() { }

	public function make() : Bob {
		return BobConst;
	}
}
```

これを次のように実行してみます。

```hx:Test.hx
class Test {
	static function test<T>(m : PersonInterface<T>) : T {
		return m.make();
	}

	static function main() {
		var x = test(new AliceFactory());
		
		$type(x);
		trace(x);
	}
}
```

Haxeのインタープリターで次のように実行してみました。

```console
$ haxe -main Test.hx --interp -debug
Test.hx:10: characters 8-9 : Warning : Alice
AbsPerson.hx:19: characters 16-20 : Invalid array access
Test.hx:3: characters 2-17 : Called from
Test.hx:7: characters 10-34 : Called from
?:1: characters 1048575-8796094070782 : Called from
Aborted
```

型は付きますが実行時のエラーで死にます。

# 調査

検証のためJavaScriptへ変換して実行してみます。

```console
$ haxe -main Test.hx -js test.js && node test.js
Test.hx:10: characters 8-9 : Warning : Alice
undefined
Alice!
```

実行時にエラーは起きませんが、変数`x`の値が`undefined`になっていて、これは正しい結果ではないです。
まず、実行結果の最初に出ている _Warning_ は`$type`で表示させている変数`x`の型です。これは`Alice`になっています。`AliceFactory.make`は

```hx
public function make() : Alice {
	return AliceConst;
}
```

なので、この型はどうやら正常のようです。ところが、変数`x`の値がJavaScript版では`undefined`になるのが気になるので、その部分を調査します。
まず`main`関数は次のようなJavaScriptに変換されます。

```js
Test.main = function() {
	var x = Test.test(new AliceFactory());
	x;
	console.log(x);
};
```

`Test.test`関数は次のようになります。

```js
Test.test = function(m) {
	return _Test.AbsPerson_Impl_.toX(m.make());
};
```

この中で利用されている`_Test.AbsPerson_Impl_.toX`関数は次のようになります。

```js
_Test.AbsPerson_Impl_.toX = function(this1) {
	switch(this1[1]) {
	case 0:
		var a = this1[2];
		return a;
	case 1:
		var b = this1[2];
		return b;
	}
};
```

この関数が怪しいので、開発者ツールを使って実行時の値を調査してみました。変数`this1`は次のような内容です。

![スクリーンショット 2014-11-23 23.47.43.png](https://qiita-image-store.s3.amazonaws.com/0/10815/d1bc9da0-4c6a-49f3-21ca-3f4a336592ad.png)

上の画像、26行目で`var a = this1[2]`という代入がありますが、`this1`は長さ2の配列なので`undefined`になります。

# 推測

`main.test`は、

```hx
static function test<T>(m : PersonInterface<T>) : T {
	return m.make();
}
```

という定義ですが、これの引数`m`に`new AliceFactory()`を割り当てると、クラス`AliceFactory<T>`は`PersonInterface<Alice>`を実装しているので、型パラメータ`T`は`Alice`になります。従って関数`test`の返り値の型は`Alice`になります。ただ、`PersonInterface<Alice>`で関数`make`は返り値の型が`AbsPerson<Alice>`であると主張しています。

```hx
interface PersonInterface<X> {
	public function make() : AbsPerson<X>;
}
```

`AbsPerson<Alice>`と`Alice`は`AbsPerson<Alice>`で暗黙の型変換が可能であるとしているので、この型付けは成功します。一方で、`AliceFactory.make`が返した値（型`Alice`）を`AbsPerson<Alice>`型へ変換する処理が **スキップ** されているようです。つまりここで、

```hx
@:from public static function fromAlice(a : Alice) : AbsPerson<Alice> {
	return new AbsPerson(A(a));
}
```

を実行し、型`Alice`を`AbsPerson<Alice>`へ変換するべきでしたが、何故か行なわれません。そして型`Alice`の値に対して型変換`_Test.AbsPerson_Impl_.toX`を実行して失敗、エラーや`undefined`が発生したと思われます。

# 解決策

明らかな解決法は、`Alice`を`AbsPerson<Alice>`へ強制的に型変換することです。

> https://gist.github.com/yoshimuraYuu/2ad43c0e6089632c8458
> http://try.haxe.org/#d3d8C

```hx:AliceFactory2.hx
class AliceFactory2 implements PersonInterface<Alice> {
	public function new() { }

	public function make() : AbsPerson<Alice> {
		return AliceConst;
	}
}
```

```hx:Test2.hx
class Test {
	static function test<T>(m : PersonInterface<T>) : T {
		return m.make();
	}

	static function main() {
		var x = test(new AliceFactory2());
		
		$type(x);
		trace(x);
	}
}
```

```console
$ haxe -main Test2.hx --interp -debug
Test.hx:10: AliceConst
Test.hx:9: characters 8-9 : Warning : Alice

$ haxe -main Test2.hx -js test.js && node test.js
Test.hx:9: characters 8-9 : Warning : Alice
[ 'AliceConst',
  0,
  __enum__: { __constructs__: [ 'AliceConst' ], AliceConst: [Circular] } ]
```

ただ、もっといい方法があればよいと思います。