Swanman's Horizon

性欲をもてあましつつなんらかの話をするよ。

ジェネリックレコード(クラス)と無名メソッドの組み合わせでバグ。

まずは再現コード。

// 以下Project1.dpr
program Project1;
{$APPTYPE CONSOLE}
uses
  SysUtils,
  Unit1 in 'Unit1.pas';
var
  foo: TFoo<TObject>;
  proc: TProc;
begin
  proc := foo.Bar(); // *1
  proc; // *2
end.

// 以下Unit1.pas
unit Unit1;
interface
uses
  SysUtils;
type
  TFoo<T> = class
  public
    function Bar(AValue: T): TProc; overload; // *3
    function Bar: TProc; overload; // *4
  end;
implementation
function TFoo<T>.Bar(AValue: T): TProc;
begin
  Result := procedure
    begin
      Writeln('foobar?'); // *5
    end;
end;
function TFoo<T>.Bar: TProc;
begin
  Result := procedure
    begin
      Writeln('foobar'); // *6
    end;
end;
end.

これを解説すると、*1で引数無しで呼び出すため、オーバーロードされたBarメソッドのうち、*4の方が実行される。そのため*1で返ってくる無名メソッドは*6のはず。ところが*2が実行されると、画面に表示されるのは「foobar」ではなく、何故か*5が実行されて「foobar?」と表示される。つまりオーバーロードされたメソッドでそれぞれ別の無名メソッドを返しても、実行されるのは一方になってしまう、というバグ。

適当に追加テストしたところ、このバグはUnit1に書かれたものをすべてProject1に移すと発生しなくなる。また、実行される無名メソッドは宣言部で一番最初に宣言されているもの(この場合*3)になる。なので*3と*4の位置を入れ替えると*6が実行されるようになるので問題ないように見えるが、今度は*3を実行すると*5ではなく*6が実行されてしまうのでダメ、と。

これD2009からあるのかなぁ。とりあえずオーバーロードじゃなくてそれぞれ別の名前にすれば回避できるけど(できなかった、追記参照)、それはそれで何か負けた気分になる。かといってQC*1に登録する気もあんまりない。べ、別に英語ができないから登録もできないってわけじゃないんだからねっ!これを見た時に「バグ報告してるのに『英語翻訳して本社に報告しなおせ』とかお前ら仮にも日本法人名乗ってんだから自分で報告しろよバーカたらい回しとか客に手間かけさせんじゃねうんこ!」と思っただけなんだからねっ!勘違いしないでよね!!

追記オーバーロードではなく別の名前にすると内部エラーで死にました。もうやだこのDelphi。せっかく買ったけどそろそろ本気でC#への移行を検討すべきかもしんない。

*1:CodeGearのバグ報告所