From: "kddnewton (Kevin Newton) via ruby-core" Date: 2025-01-04T20:14:49+00:00 Subject: [ruby-core:120477] [Ruby master Feature#20999] Add RubyVM object source support Issue #20999 has been updated by kddnewton (Kevin Newton). > Should a different issue be opened for this? Yeah, I would say make that a new issue for expanding `source_location` or making a new method. > There is still one outstanding issue, though, which is dealing with IRB. For instance, in my description above I show how you can use the AST in Ruby 3.3.6 to obtain the source of a function: Yeah, for that you're still going to need to rely on `RubyVM::InstructionSequence.of().script_lines`, because that's not exposed in any other way. It's on the iseq itself, so it would be possible to expose in another API, so it would need to be a feature request. Just for completeness, for the example code above you would replace `File.read(filepath)` with `RubyVM::InstructionSequence.of(callable).source_lines.join`. ---------------------------------------- Feature #20999: Add RubyVM object source support https://bugs.ruby-lang.org/issues/20999#change-111261 * Author: bkuhlmann (Brooke Kuhlmann) * Status: Open ---------------------------------------- Hello. ���� I'd like to propose adding the ability to acquire the source of any object within memory via the RubyVM. A couple use cases come to mind: - This would simplify the [Method Source](https://github.com/banister/method_source) gem implementation and possibly eliminate the need for the gem. - Another use case is this allows DSLs, like [Initable](https://alchemists.io/projects/initable), to elegantly acquire the source code of objects and/or functions (in my case, I'm most interested in the lazy evaluation of function bodies). ������ I'm also aware that the [RubyVM](https://docs.ruby-lang.org/en/master/RubyVM.html) documentation clearly stats this isn't meant for production use: > This module is for very limited purposes, such as debugging, prototyping, and research. Normal users must not use it. This module is not portable between Ruby implementations. ...but I'd like to entertain this proposed feature request, regardless. Here's an example, using the aforementioned [Initable](https://alchemists.io/projects/initable) gem, where I use the RubyVM to obtain the source of a `Proc`: ``` ruby class Demo include Initable[%i[req name], [:key, :default, proc { Object.new }]] end puts Demo.new("demo").inspect #> ``` With the above, I'm lazily obtaining the source code of the `Proc` in order to dynamically define the `#initialize` method (essentially a `module_eval` on `Demo`, simply speaking) using a nested array as specified by [Method#parameters](https://docs.ruby-lang.org/en/master/Method.html#method-i-parameters) because I don't want an instance of `Object` until initialization is necessary. ## Context Prior to the release of Ruby 3.4.0, you could do this: ``` ruby function = proc { Object.new } ast = RubyVM::AbstractSyntaxTree.of function ast.children.last.source # "Object.new" ``` Unfortunately, with the release of Ruby 3.4.0 -- which defaults to the [Prism](https://ruby.github.io/prism/rb/index.html) parser -- the ability to acquire source code is a bit more complicated. For example, to achieve what is shown above, you have to do this: ``` ruby function = proc { Object.new } RubyVM::InstructionSequence.of(function).script_lines # [ # "function = proc { Object.new }\n", # "RubyVM::InstructionSequence.of(function).script_lines\n", # "\n", # "" # ] ``` Definitely doable, but now requires more work to pluck `"Object.new"` from the body of the `Proc`. One solution is to use a regular expression to find and extract the first line of the result. Example: ``` ruby / proc # Proc statement. \s* # Optional space. \{ # Block open. (?.*?) # Source code body. \} # Block close. /x ``` Definitely doesn't account for all use cases (like when a `Proc` spans multiple lines or uses `do...end` syntax) but will get you close. ## How I think there are a couple of paths that might be nice to support this use case. ### Option A Teach `RubyVM::InstructionSequence` to respond to `#source` which would be similar to what was possible prior to Ruby 3.4.0. Example: ``` ruby function = proc { Object.new } RubyVM::InstructionSequence.of(function).source # "Object.new" ``` ### Option B This is something that Samuel Williams [mentioned](https://bugs.ruby-lang.org/issues/6012#note-13) in [Feature 6012](https://bugs.ruby-lang.org/issues/6012) which would be to provide a `Source` object as answered by `Method#source` and `Proc#source`. Example (using a `Proc`): ``` ruby # Implementation # Method#source (i.e. Source.new path, line_number, line_count, body) # Usage: function = proc { Object.new } method.source.code # "Object.new" method.source.path # "$HOME/demo.rb" method.source.location # [2, 0, 3, 3] ``` ### Option C It could be nice to support both Option A and B. -- https://bugs.ruby-lang.org/ ______________________________________________ ruby-core mailing list -- ruby-core@ml.ruby-lang.org To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/