如何定位Ruby中yield返回时对应块内next语句所在的源代码行号
解决方案
有三种可行的实现方式,按依赖程度从低到高排列:
方案1:修改块逻辑(无额外依赖,兼容性最好)
直接在next语句中返回当前行号__LINE__,yield可以直接拿到这个返回值:
def test_method result = [] next_line = yield puts "next 触发的行号:#{next_line}" result end def another_method a = 1 b = 2 test_method do next __LINE__ if a == 1 next __LINE__ if b == 200 end end
运行后直接输出对应next语句的行号,性能最优,所有Ruby实现都支持。
方案2:使用MRI标准库RubyVM::StackTrace(无额外gem依赖)
如果不能修改块的代码,Ruby 2.6+的MRI环境可以用内置的RubyVM::StackTrace获取完整栈帧信息,直接定位块内执行的行号:
def test_method result = [] result << yield # 遍历栈帧找到对应块的执行帧 block_frame = RubyVM::StackTrace.new.find { |f| f.label == "block in another_method" } puts "next 触发的行号:#{block_frame.lineno}" result end def another_method a = 1 b = 2 test_method do next if a == 1 next if b == 200 end end
该方案不需要修改块代码,但仅支持MRI Ruby,不兼容JRuby、TruffleRuby等其他实现。
方案3:使用binding_of_caller gem(全功能实现)
如果需要跨方法获取任意调用层级的上下文信息,可以安装binding_of_caller gem实现:
- 首先安装依赖:
gem install binding_of_caller - 代码实现:
require 'binding_of_caller' def test_method result = [] result << yield # 找到块对应的绑定帧 block_binding = binding.callers.find { |f| f.frame_type == :block && f.eval('__method__') == :another_method } puts "next 触发的行号:#{block_binding.eval('__LINE__')}" result end def another_method a = 1 b = 2 test_method do next if a == 1 next if b == 200 end end
该方案不需要修改块代码,兼容绝大多数主流Ruby实现,但需要引入第三方依赖。
原方案问题说明
你原来用的caller方法只能拿到方法调用层级的栈帧,块内next属于块内的跳转逻辑,不属于方法调用行为,所以只会返回another_method调用test_method的行号,无法定位块内的实际执行位置。
内容的提问来源于stack exchange,提问作者ArMD
相关产品推荐
相关产品推荐

