2022/03/10 今回の気になった bugs.ruby のチケット
今週はブロックの引数で Hash が分割して受け取れなくなったチケットがありました。
[Misc #18609] keyword decomposition in enumerable (question/guidance)
- Ruby 3.0 から次のように Hash をキーワード引数で受け取れなくなったというチケット
drafts = [ {name: 'draft4', mod: :Draft04, image: 'draft4.png'}, {name: 'draft6', mod: :Draft06, image: 'draft6.jpg'}, ] # Ruby 2.x 系だと Hash をキーワード引数で受け取る事ができた # Ruby 3.x 系だとキーワード引数の挙動が変わったのでこう書くことができなくなった drafts.each do |name: , mod: , image: | end
- これは Ruby 3.0 で Hash が暗黙的にキーワード引数に変換されなくなった弊害ですね
def test(a:, b:) end # Ruby 2.7 => OK: Hash がそのままキーワード引数に変換されていた # Ruby 3.0 => NG: Hash は暗黙的にキーワード引数に変換されなくなった test({ a: 1, b: 2 })
- 上記のように Hash をキーワード引数に渡したい場合は
**
を付けることで渡せます
def test(a:, b:) end # Ruby 2.7 => OK: Hash をキーワード引数に渡せる # Ruby 3.0 => OK: Hash をキーワード引数に渡せる test(**{ a: 1, b: 2 })
- これは Ruby 3.0 からの仕様なので Close されています
- ちなみにコメントで以下のようなヘルパメソッドが提案されている
module Enumerable def each_kw each{|v| yield(**v)} end end drafts.each_kw do |name: , mod: , image: | # ... end
- これ、普通に本体にほしい気がする
2022/03/03 今回の気になった bugs.ruby のチケット
今週はラテン文字を String#downcase
したときのバグ報告がありました。
[Feature #18603] Allow syntax like obj.method(arg)=value
- 次のようのような構文を許容する提案
obj.method(arg) = value
- これは以下と同じ意味になる
obj.__send__(:method=, arg, value)
- ユースケースとしては
dig
しつつ代入したい場合に有効らしい
obj.dig(0, :key, 1) = 20
- あるとなにかに利用できそうだけどメソッド呼び出しは
()
が省略できるのでなかなかややこしそうobj.method arg = value
みたいなコードは現状でもかけるので
- あと
def obj.method(arg) = value
と構文が似てるので混乱する、とコメントされていますね
[Bug #18590] String#downcase and CAPITAL LETTER I WITH DOT ABOVE
- ラテン文字
İ
のString#downcase
の結果はi
を期待する
'İ'.downcase # => "i̇"
- しかし
String#downcase
の結果に結合文字"̇"
が含まれてしまっているというバグ報告
'İ'.downcase.chars # => ["i", "̇"]
- ただ、Unicode の仕様として
İ
(U+0130
) の小文字がi
(U+0069 U+0307
) となるのは意図する挙動ぽいです - このあたりの仕様は参照するドキュメントによって違うみたいですね?
- https://www.unicode.org/Public/UCD/latest/ucd/UnicodeData.txt だと以下のように記載されており
0130;LATIN CAPITAL LETTER I WITH DOT ABOVE;Lu;0;L;0049 0307;;;;N;LATIN CAPITAL LETTER I DOT;;;0069;
- http://www.unicode.org/Public/UCD/latest/ucd/CaseFolding.txt だと以下のように記載されている
F
がフルケースホールディング
を表しT
がチュルク語族
を表しているF
の場合はU+0069 U+0307
になりT
の場合はU+0130
になる
0130; F; 0069 0307; # LATIN CAPITAL LETTER I WITH DOT ABOVE 0130; T; 0069; # LATIN CAPITAL LETTER I WITH DOT ABOVE
String#downcase
では引数に:turkic
を渡すとチュルク語族
に適した形で小文字に変換される
'İ'.downcase.chars # => ["i", "̇"] 'İ'.downcase(:turkic).chars # => ["i"]
String#downcase
のデフォルトの挙動としてはフルケースホールディング
になるのが意図しているので今回の報告は期待する挙動だったみたい- ただし、rdoc で参照しているドキュメントが正しくなかったのでそれに関しては別途修正されている
- https://github.com/ruby/ruby/pull/5607
- 元のドキュメントだと
İ
(U+0130
) の小文字がi
(U+0069
)と記載されていたのでそれに対する対応
[Feature #18551] Make Range#reverse_each to raise an exception if endless
- 終端無限
Range
に対してRange#reverse_each
を呼び出した時に例外を発生させる提案 - 現状は無限ループになる
- これは内部で
#to_a
を呼び出して配列に変換しようとしているから
- これは内部で
# 無限ループになる (1..).reverse_each { }
- 先端無限に対して
#each
を呼び出す場合は例外になるのでそれに合わせたいって感じみたい
# error: `each': can't iterate from NilClass (TypeError) (..1).each { }
- 懸念点として
Range#reverse_each
を新たに実装する必要があるがその場合のパフォーマンスや仕様の複雑さがコメントされている - 定期的に無限
Range
に対するこの手の話題が上がってる気がする - 個人的には全体的に一貫性の挙動にはなってほしい
[Bug #18577] Range#include? returns wrong result for beginless range with exclusive string end
(...'z')
の場合は終端の値を含めるべきではないが#include?
#member?
#===
はtrue
になり終端を含めた結果を返す- ただし
#cover?
はfalse
を返す - これは先端が無限の場合にのみ発生してる
- ただし
# 数値は終端を含まない (...10).include?(10) # => false (...10).member?(10) # => false (...10) ===(10) # => false (...10).cover?(10) # => false # 文字列は終端を含めている (...'z').include?('z') # => true (...'z').member?('z') # => true (...'z') ===('z') # => true (...'z').cover?('z') # => false # 先端が無限でない場合は発生しない ('a'...'z').include?('z') # => false ('a'...'z').member?('z') # => false ('a'...'z') ===('z') # => false ('a'...'z').cover?('z') # => false
- これは Ruby 3.2 で修正される
# Ruby 3.2 だと全部 false を返す (...'z').include?('z') # => false (...'z').member?('z') # => false (...'z') ===('z') # => false (...'z').cover?('z') # => false
2022/02/24 今回の気になった bugs.ruby のチケット
今週はアクセシビリティ関連のバグ報告がありました。
[Bug #18600 ] Aliased method visibility issue on Ruby 3.1
- 次のように親のメソッドを
public
にしてからalias_method
するとprivate
に戻ってしまっているというバグ報告
module M private def private_meth; end end class X include M public :private_meth alias_method :public_meth, :private_meth alias_method :private_meth, :public_meth end x = X.new p x.respond_to?(:private_meth) # Ruby 3.0.0 => true # Ruby 3.1.0 => false x.private_meth # Ruby 3.0.0 => OK # Ruby 3.1.0 => NG: private method `private_meth' called for #<X:0x00007fd46bca92b0> (NoMethodError)
- https://bugs.ruby-lang.org/projects/ruby-master/repository/git/revisions/71c746379d5872e250d90ae45c585760afaf9516 が原因で壊れたらしい
- 修正 PR: https://github.com/ruby/ruby/pull/5592
[Feature #18594] Add a #to_h method on URI::Generic
- 以下のように
uri
に対してHash
に変換するメソッドを追加する提案
require "uri" uri = ::URI.parse("https://example.com") pp uri.component # => [:scheme, :userinfo, :host, :port, :path, :query, :fragment] pp uri.select(*uri.component) # => ["https", nil, "example.com", 443, "", nil, nil] # こういうような Hash を返すメソッドを追加する提案 pp [uri.component, uri.select(*uri.component)].transpose.to_h # => {:scheme=>"https", :userinfo=>nil, :host=>"example.com", :port=>443, :path=>"", :query=>nil, :fragment=>nil}
- 便利そうだけどどういうユースケースがあるんだろう
[Feature #16295] Chainable aliases for String#-@ and String#+@
String#-@
やString#+@
をメソッドチェーン可能にするためのチケット+@
や-@
は優先順位が低いので以下のような挙動になる
# これは -("foo".size) と同じ意味になる -"foo".size # => -3 # 本来はこういう挙動を期待する (-"foo").size # => 3
- なので次のようなコードはエラーになってしまう
# error: undefined method `+@' for false:FalseClass (NoMethodError) +[1, 2, 3].to_s.frozen?
- このチケットでは以下のようにチェーンできるようにすることを議論している
# -@ や +@ をメソッド呼び出しのようにする "foo".-.size ary.to_s.+.frozen? # もしくは -@ や +@ と同等の名前付きのメソッドを別に用意する "foo".dedup.sizたのむかねもなかったe ary.to_s.mutable.frozen?
- ちなみに以下のように
-@
や+@
を直接呼び出すことはできる
"foo".-@.size # => 3 [1, 2, 3].to_s.+@.frozen? # false
- 色々と議論されているんですが以下のチケットに派生している
[Feature #18595] Alias String#-@
as String#dedup
- [Feature #16295] からの派生
String#-@
のエイリアスとしてString#dedup
を追加する提案String#+@
に関してはString#dup
で代替できるのでこのチケットでは-@
について議論されている- 現状は
String#freeze
が-@
の代替として利用できるんじゃない?とコメントされていて議論が進んでいる
2022/02/17 今回の気になった bugs.ruby のチケット
今週はパターンマッチの find 検索を正式に導入するチケットがありました。
[PR reline] Proposal for quick shell execution
irb
で.
から始まるコマンドを入力した時に shell コマンドが実行されるようにする提案pry
だとこの機能が実装されているらしい
.cat .ruby-version 2.7.5
- わたしは
pry
の機能を知らなかったんですが REPL で shell コマンドを実行したいときって結構あるんですかね - Ruby だと
`.cat .ruby-version`
- みたいに実行できるのでそれで十分な気もします
[Bug #13885] Random.urandom と securerandom について
Random.urandom
とsecurerandom
の仕様のチケット- チケット自体の内容よりも日本語で議論されていて普段どうやって議論されているのかがわかりやすいので気になる人は見てみるとよいかも
[Feature #18585] Promote find pattern to official feature
ary = [1, 2, 3] # warning: Find pattern is experimental, and the behavior may change in future versions of Ruby! if ary in [*, {a: 0, b: 1 | 2} => i, *] end
- Ruby 3.1 で消えるかと思ってたんですけどまだ残ってたみたいですね
[Feature #12962] Feature Proposal: Extend 'protected' to support module friendship
protected
で宣言した時に後から別のクラスからでも呼び出せるようにする機能の提案- C++ にあるような
friend
機能
- C++ にあるような
class A protected def foo "secrets" end end class D def call_foo A.new.foo end end # A のフレンドとして D を登録 A.friend D # D から A の protected を呼び出すことができるようになる D.new.call_foo # => "secrets"
- 他には以下のようにモジュールに対して使用したりとか
module MyLib module Internals end class A include Internals # Internals を friend することでこれを Internals を include しているクラスから # protected なメソッドを呼び出すことができるようになる friend Internals protected def foo "implementation" end end class B include Internals friend Internals protected def bar A.new.foo end end end class UserCode # include MyLib::Internals してないので protected なメソッドは呼べない def call_things [MyLib::A.new.foo, MyLib::B.new.bar] end end class FriendlyUserCode # include MyLib::Internals しているので protected なメソッドは呼べる include MyLib::Internals def call_things [MyLib::A.new.foo, MyLib::B.new.bar] end end UserCode.new.call_things # !> NoMethodError: protected method `foo'.. FriendlyUserCode.new.call_things # => ["implementation", "implementation"]
- モチベーションとしては機能としてはプライベートな API だけど他の API でも使いたい事があるので Ruby 的な意味での
private
ではなくてpublic
になっている事があるprivate
にてsend
で呼び出すこともできるが煩わしい
- このように機能としてはプライベートだが Ruby として
public
になっているとユーザが混乱するので明示的にprotected
をfriend
する仕組みがほしいらしい - Ruby でアクセシビリティを制御するのってむずかしいので
friend
でちゃんと意識して書くようになるのかはちょっと気になる - 個人的には
Refinements
でメソッドを定義しておいて必要な時にusing
すればいいんじゃないかと思っている
class A # プライベートな API は Refinements で定義しておく module Internals refine A do def foo "secrets" end end end end class D # 使用する箇所で明示的に using する using A::Internals def call_foo A.new.foo end end D.new.call_foo # => "secrets"
- 5年前のチケットだけど最近議論が再開してた
- 今日の開発者会議で議論され、この提案は Reject されています
2022/02/10 今回の気になった bugs.ruby のチケット
今週はエンコーディングの ASCII_8BIT
という名前を BINARY
に変える提案がありました。
[Feature #18576] Rename ASCII-8BIT
encoding to BINARY
- エンコーディングの
ASCII-8BIT
をBINARY
という名前に変更する提案 - 以下のようなエラーメッセージで
ASCII-8BIT
と表示されても分かりづらいらしい
>> "fée" + "\xFF".b (irb):3:in `+': incompatible character encodings: UTF-8 and ASCII-8BIT (Encoding::CompatibilityError)
- 現状は
BINARY
がASCII_8BIT
のエイリアスとして存在しているんですがそれを逆にする感じなんですかね
pp Encoding::ASCII_8BIT # => #<Encoding:ASCII-8BIT> pp Encoding::BINARY # => #<Encoding:ASCII-8BIT>
[Bug #18575] [BUG] unsupported: T_NONE
- 巨大な CSV ファイルを処理している時にクラッシュするというバグ報告
- 以下のコードで再現するらしい
require 'csv' parsed = CSV.parse("Foo,bAr,baZ\nfoo,bar,baz", headers: true) while true parsed.map do |row| obj = row.to_h obj.transform_keys! { |k| k.strip.downcase } end end
- 手元だと Ruby 3.0.2 で再現したけど 3.0.3 では動いていたので直ってるかも?
- CSV のバージョンに依存しているのかも?
- https://bugs.ruby-lang.org/issues/18575#note-3
[Feature #18573] Object#pack1
Array#pack
を使用する時に配列の要素が1つのケースがよくある- 標準ライブラリなどでもあるらしい
[codepoint].pack('U') [digest].pack('m0') [mail_body].pack('M') [ip_address].pack('N')
- このような際にわざわざ
Array
にするのが手間なのでObject#pack1
というメソッドを追加する提案
codepoint.pack('U') digest.pack('m0') mail_body.pack('M') ip_address.pack('N')
Object
に生やすのではなくてString.pack1(format, arg)
を追加するのはどうか、みたいなコメントとかもあります- ちなみに
Array#pack
は指定されたテンプレートに沿って文字列に変換するメソッド
# コードポイントから UTF-8 の文字列に変換する pp [12354].pack("U*") # => "あ"
[Bug #18572] Performance regression when invoking refined methods
- Ruby 2.7 と比較して
Refinement
で定義したメソッドの呼び出しが遅くなっているというバグ報告using
していなくても遅くなっている
- 以下、検証結果
require "benchmark_driver" source = <<~RUBY class Foo def original end def refined end end module FooRefinements refine Foo do def refined raise "never called" end end end FOO = Foo.new RUBY Benchmark.driver do |x| x.prelude %Q{ #{source} } x.report "no-op original", %{ FOO.original } x.report "no-op refined", %{ FOO.refined } end
- Ruby 3.2.0-dev
... Comparison: no-op original: 54831732.8 i/s no-op refined: 28231384.4 i/s - 1.94x slower
- Ruby 2.7.5
... Comparison: no-op refined: 57847396.8 i/s no-op original: 56289619.5 i/s - 1.03x slower
[Bug #18569] RubyVM::InstructionSequence#disasm returns nil for composed functions
Proc#>>
の結果をRubyVM::InstructionSequence#disasm
に渡すとnil
が返ってくるというバグ報告
# Proc オブジェクト渡すとそのブロックの命令が返ってくる first_proc = proc { |x| x + 2 } puts RubyVM::InstructionSequence.disasm(first_proc) # => # == disasm: #<ISeq:block in <main>@disasm.rb:3 (3,18)-(3,31)> (catch: FALSE) # local table (size: 1, argc: 1 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1]) # [ 1] x@0<Arg> # 0000 getlocal_WC_0 x@0 ( 3)[LiBc] # 0002 putobject 2 # 0004 opt_plus <calldata!mid:+, argc:1, ARGS_SIMPLE>[CcCr] # 0006 leave [Br] # ------------------------------------- # Proc#>> の結果を .disasm に渡すと nil が返ってくる pp RubyVM::InstructionSequence.disasm(first_proc >> first_proc) # => nil
- これは
Proc#>>
の結果が命令シーケンスを持っていないからが理由とのこと - 命令シーケンスが必要な場合は自分で
Proc
オブジェクトを生成する必要がある
first_proc = proc { |x| x + 2 } # これだと OK puts RubyVM::InstructionSequence.disasm(proc { |x| first_proc.call(x) + 2 }) # => # == disasm: #<ISeq:block in <main>@disasm.rb:3 (3,18)-(3,31)> (catch: FALSE) # local table (size: 1, argc: 1 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1]) # [ 1] x@0<Arg> # 0000 getlocal_WC_1 first_proc@0 ( 2)[LiBc] # 0002 getlocal_WC_0 x@0 # 0004 opt_send_without_block <calldata!mid:call, argc:1, ARGS_SIMPLE> # 0006 putobject 2 # 0008 opt_plus <calldata!mid:+, argc:1, ARGS_SIMPLE>[CcCr] # 0010 leave [Br]
[Bug #18578] Hash#shift を繰り返していると ruby が無応答になる。
- 以下のように
Hash#shift
を繰り返し呼び出すと Ruby の実行が止まってしまうことがるというバグ報告
H = {} 100.times{|n| while H.size < n k = Random.rand 0..1<<30 H[k] = 1 # たぶんここで止まる。 end warn "size: #{H.size} before shifting." 0 while H.shift warn "empty?: #{H.empty?}" } warn :exit
- このバグは Ruby 2.5 から発生している
Hash
が空になった後にHash#shift
を呼び出すと意図しない挙動になっていたぽい0 while H.shift
の変わりにH.shift until H.empty?
を使うと問題なく動作する- https://bugs.ruby-lang.org/issues/18578#note-3
- このバグは既に修正済み
2022/02/03 今回の気になった bugs.ruby のチケット
今週は String
周りでメソッドを追加するチケットの話などがありました。
[Feature #18564] Add Exception#detailed_message
- [Feature #18438] Add
Exception#additional_message
to show additional error information で議論した結果をまとめたチケット - エラー出力の内容を
Exception#message
ではなくてException#detailed_message
を使用するようになる- これによりエラー出力の内容を
Exception#detailed_message
で制御できるようになる
- これによりエラー出力の内容を
class MyClass < StandardError def message = "my error!" def detailed_message(highlight: false, **opt) super + "\nThis is\nan additional\nmessage" end end raise MyClass
$ ./ruby test.rb test.rb:8:in `<main>': my error! (MyClass) This is an additional message
Exception#detailed_message(highlight: false)
は内部でException#message
を呼び出しhighlight: true
だとエスケープシーケンスでハイライトされる
e = RuntimeError.new("my error!") p e.detailed_message #=> "my error! (RuntimeError)" p e.detailed_message(highlight: true) #=> "\e[1mmy error! (\e[1;4mRuntimeError\e[m\e[1m)\e[m"
[Feature #13110] Byte-based operations for String
String
にバイトベースの操作をするメソッドを追加するチケット
s = "あああいいいあああ" p s.byteindex(/ああ/, 4) #=> 18 x, y = Regexp.last_match.byteoffset(0) #=> [18, 24] s.bytesplice(x...y, "おおお") p s #=> "あああいいいおおおあ"
- ユースケースとしてはバイトベースで操作する場合に一旦
force_encoding(Encoding::ASCII_8BIT)
で変換する必要があるのでそれをなくしたいらしい - 既存のメソッドを比較したベンチマークは以下の通り
lexington:ruby$ cat bench.rb require "benchmark" s = File.read("README.ja.md") * 10 Benchmark.bmbm do |x| x.report("index") do pos = 0 n = 0 loop { break unless s.index(/\p{Han}/, pos) n += 1 _, pos = Regexp.last_match.offset(0) } end x.report("byteindex") do pos = 0 n = 0 loop { break unless s.byteindex(/\p{Han}/, pos) n += 1 _, pos = Regexp.last_match.byteoffset(0) } end end lexington:ruby$ ./ruby bench.rb Rehearsal --------------------------------------------- index 1.060000 0.010000 1.070000 ( 1.116932) byteindex 0.000000 0.010000 0.010000 ( 0.004501) ------------------------------------ total: 1.080000sec user system total real index 1.050000 0.000000 1.050000 ( 1.080099) byteindex 0.000000 0.000000 0.000000 ( 0.003814)
- 5年前のチケットだが最近
String#byteindex
String#byterindex
MatchData#byteoffset
が実装された PR が投げられていた
[Feature #18563] Add "graphemes" and "each_grapheme aliases
String#each_grapheme_cluster
メソッドのエイリアスとして#graphemes
と#each_grapheme
を追加するチケット他の言語でも
graphemes
という名前になっている事が多いらしい- JavaScript/TypeScript grapheme-splitter library: splitGraphemes
- PHP: grapheme_extract
- Zig ziglyph library: GraphemeIterator
- Golang uniseg library: NewGraphemes
- Matlab: splitGraphemes
- Python grapheme library: grapheme
- Elixir: graphemes
- Crystal uni_text_seg library: graphemes
- Nim nim-graphemes library: graphemes
- Rust unicode-segmentation library: graphemes
以下、matz のコメント
https://bugs.ruby-lang.org/issues/13780#note-10
grapheme sounds like an element in the grapheme cluster. How about each_grapheme_cluster? If everyone gets used to the grapheme as an alias of grapheme cluster, we'd love to add an alias each_grapheme.
Matz.
[Bug #11064] #singleton_methods for objects with special singleton_class returns an empty array
- 以下のように
nil
に特異メソッドを追加するとnil.singleton_methods
には追加されてないように見えるというバグ報告
# nil に特異メソッドを追加する def nil.bla 42 end # 以下は動作してるが nil.bla #=> 42 nil.singleton_method(:bla) #=> #<Method: NilClass#bla> # 以下は動作していない nil.singleton_methods #=> []
- これは
nil
の#singleton_class
がNilClass
を返しているためnil
には特異クラスは存在しておらずNilClass
がその役割を果たしている
# NilClass を返す p nil.singleton_class # => NilClass # なので nil の特異メソッドは NilClass のインスタンスメソッドとして定義される def nil.bla end p NilClass.instance_methods.include? :bla # => true
- これに対しては matz からは以下のような提案がされている
- 現状のままにする
-
nil.singleton_methods
がNilClass
のメソッドを返すようにする - https://bugs.ruby-lang.org/issues/11064#note-1
Ruby 3.1 でオブジェクトが生成された箇所が表示できるようになった
Ruby 3.1 の小ネタです。
Ruby 3.1 で objspace/trace
というライブラリが追加されました。
このライブラリを require
すると p
で出力するときに『オブジェクトが生成された箇所』が一緒に表示されるようになります。
# このライブラリを require するとオブジェクトが生成された場所がトレースされるようになる # require した後に生成されたオブジェクトが対象となる require "objspace/trace" obj = Object.new # p する時に obj が生成された場所が表示される p obj # => #<Object:0x00007fce8b2ea778> @ /path/to/test.rb:5 # こういうのも表示される str = 42.to_s p str # => "42" @ /path/to/test.rb:11 # クラス内で生成されたオブジェクトも取得できる class X attr_accessor :value def initialize @value = "hoge" end def hoge self.value = "bar" end end x = X.new p x.value # => "hoge" @ /path/to/test.rb:22 x.hoge p x.value # => "hoge" @ /path/to/test.rb:26
こんな感じで各オブジェクトが生成された場所が生成されます。
また objspace/trace
を使っている場合は objspace/trace is enabled
が表示されます。
注意点
objspace/trace
はあくまでも『オブジェクトが生成された場所』が表示されるのであって『変数が定義された場所ではない』という注意点があります。
なので、例えば次のように定数を変数に代入している場合は『定数が定義された位置』が表示されるようになります。
require "objspace/trace" C = Object.new obj = C # これは obj 変数が定義された場所ではなくて C が定義された場所が出力される p obj # => #<Object:0x00007f6e1111e438> @ /path/to/test.rb:3
他には数値や nil
などの値も表示されなかったり
require "objspace/trace" obj = 1 + 2 p obj obj2 = "42".to_i p obj2 obj3 = nil p obj3
# frozen_string_literal: true
している場合は文字列リテラルの生成位置も表示されません。
require "objspace/trace" str = "homu" # これは表示される p str # => "homu" @ /path/to/test.rb:3
require "objspace/trace" str = "homu" # これは表示される p str # => "homu" @ /path/to/test.rb:3
# frozen_string_literal: true require "objspace/trace" str = "homu" # これは表示されない p str # => "homu" # リテラル以外で生成された文字列は表示される str2 = str + str p str2 # => "homuhomu" @ /path/to/test.rb:12
おまけ
メタプロ的に定義されている場合でも問題なく取得できた。
require "objspace/trace" # 動的に生成している場合でも位置情報は取得できる eval(<<~EOS, binding, __FILE__, __LINE__ + 1) @value = "hoge" def hoge @value end EOS p hoge # => "hoge" @ /path/to/test.rb:5 # 動的に変数を定義した場合も取得できる bind = binding bind.local_variable_set(:value, "mami") p bind.local_variable_get(:value) # => "mami" @ /path/to/test.rb:14