🌟
【bugs.ruby Advent Calender】String のサブクラスで gsub を再定義したときのバグ報告【18日目】
bugs.ruby Advent Calender 18日目の記事です。
これはなに
今年1年間通してみてきた bugs.ruby のチケットの中から気になったものを1つずつ取り上げていく Advent Calender です。
取り上げるチケットは基本的にこのブログで取り上げたものになります。
記事のまとめは ここを参照 してください。
String のサブクラスで gsub を再定義したときのバグ報告
このチケットは以下のようにサブクラスで String#gsub
を再定義したときに #gsub
のブロック内で Regexp.last_match
が取得できないというバグ報告になります。
def call_gsub(str)
str.gsub(/^./) {
# ブロック内でマッチした値を取得する
pp Regexp.last_match
}
end
class MyString < String
def gsub(*args, &block)
super(*args, &block)
end
end
text = 'hoge'
# String オブジェクトの場合は Regexp.last_match が取得できる
call_gsub(String.new(text))
# => #<MatchData "h">
# 一方で String#gsub を再定義したオブジェクトだと Regexp.last_match は取得できない
call_gsub(MyString.new(text))
# => nil
これなんですが Regexp.last_match
などの正規表現関連のグローバル値は複数のフレームに渡って動作しないのが起因です。
と、いってもわたしもあんまりよくわかっていないんですが、今回のケースでは #gsub
を再定義したメソッド内であれば Regexp.last_match
が取得できます。
def call_gsub(str)
str.gsub(/^./)
pp call_gsub: Regexp.last_match
end
class MyString1 < String
end
class MyString2 < String
def gsub(*args, &block)
# このメソッド内で gsub でマッチしたときの正規表現の情報が参照できる
super(*args, &block)
ensure
pp ensure: $~
end
end
text = 'hoge'
# #gsub を再定義していない場合はブロック内でも取得できる
call_gsub(MyString1.new(text))
# {call_gsub: #<MatchData "h">}
# #gsub を再定義している場合はそのメソッド内で値を取得できる
call_gsub(MyString2.new(text))
# {call_gsub: nil}
# {ensure: #<MatchData "h">}
#gsub
を Ruby レイヤーで再定義することで参照することができるレイヤーがわかってしまう、みたいな感じなんですかね?
今年みた bugs.ruby のチケットでもかなり難しい内容でした。
String#gsub
みたいなのを再定義することはあんまりないと思うんですが、このあたりは Ruby の内部事情など知らないとなかなか気づきづらそうですねー。
Discussion