mrubyでブロックをコールバック関数として登録してあとから呼び出して使おうとしたところ、ブロック内のselfの値がおかしくなるケースがあった。

$block = nil
def register_callback(&block)
  block.call()  # ブロック内のselfは意図通りFooのインスタンス
  $block = block
end

class Foo
  def initialize()
    register_callback do
      p self  # (A)                          
    end
    # (B)                                                         
    # return []                                                   
  end
end

foo = Foo.new()
$block.call()  # ブロック内のselfが(B)の戻り値によって変わってしまう

ブロック内の(A)のselfは、Fooインスタンスを指すはず。initializeメソッドが戻り値を戻さない場合、そのように動く。しかし(B)で整数やnilなどのシンプルな値以外のオブジェクトを返すと、(A)のselfがその値に置き換わってしまう。

CRuby (2.0.0p195) でも試したが、そちらはどちらもFooインスタンスを指していて、意図通り動く。

追記

return [] とした場合の、initializeメソッドのダンプ:

irep 2 nregs=3 nlocals=2 pools=0 syms=1
000 OP_ENTER 0:0:0:0:0:0:0
001 OP_LAMBDA R2 I(+1) 3
002 OP_SETGLOBAL :$block R2
003 OP_ARRAY R0 R2 0
004 OP_RETURN R0

returnをコメントアウトした場合:

irep 2 nregs=3 nlocals=2 pools=0 syms=1
000 OP_ENTER 0:0:0:0:0:0:0
001 OP_LAMBDA R2 I(+1) 3
002 OP_SETGLOBAL :$block R2
003 OP_RETURN R2

コード生成が間違ってるっぽい?

追記2

レジスタR0は常にselfを保持している?が、return []としたときにR0に代入してしまっている?

追記3

codegenで常にR0を使うようになっている。