mirror of
https://github.com/ruby/ruby.git
synced 2026-10-28 16:36:20 +08:00
Add Thread::Backtrace::Location#syntax_tree
Returns the AST node at the location: the node of the surrounding method or block is retrieved and validated in the same way as Proc#syntax_tree, and then the node at the program counter is searched within it ([Feature #21795]). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6e65742a9f
commit
6408f147b1
3 files changed
+61
Notes:
git
2026-08-07 11:57:13 +00:00
No files matched your search
@@ -312,6 +312,18 @@ node_id_for_backtrace_location(rb_execution_context_t *ec, VALUE module, VALUE l
|
||||
return INT2NUM(node_id);
|
||||
}
|
||||
|
||||
static VALUE
|
||||
iseq_of_backtrace_location(rb_execution_context_t *ec, VALUE module, VALUE location)
|
||||
{
|
||||
if (!rb_frame_info_p(location)) {
|
||||
rb_raise(rb_eTypeError, "Thread::Backtrace::Location object expected");
|
||||
}
|
||||
|
||||
const rb_iseq_t *iseq = rb_get_iseq_from_frame_info(location);
|
||||
if (!iseq) return Qnil;
|
||||
return rb_iseqw_new(iseq);
|
||||
}
|
||||
|
||||
static VALUE
|
||||
ast_s_of(rb_execution_context_t *ec, VALUE module, VALUE body, VALUE keep_script_lines, VALUE error_tolerant, VALUE keep_tokens)
|
||||
{
|
||||
|
||||
@@ -472,3 +472,39 @@ class UnboundMethod
|
||||
RubyVM::InstructionSequence.of(self)&.syntax_tree
|
||||
end
|
||||
end
|
||||
|
||||
class Thread::Backtrace::Location
|
||||
# call-seq:
|
||||
# location.syntax_tree -> Prism::Node | RubyVM::AbstractSyntaxTree::Node | nil
|
||||
#
|
||||
# Returns the AST node at this location, by re-parsing the source file. See
|
||||
# RubyVM::InstructionSequence#syntax_tree for when +nil+ is returned.
|
||||
#
|
||||
# This method is experimental and might change without notice.
|
||||
def syntax_tree
|
||||
iseq = Primitive.iseq_of_backtrace_location(self)
|
||||
return nil unless iseq
|
||||
|
||||
node_id = Primitive.node_id_for_backtrace_location(self)
|
||||
return nil unless node_id
|
||||
|
||||
scope = iseq.syntax_tree
|
||||
return nil unless scope
|
||||
|
||||
if scope.is_a?(RubyVM::AbstractSyntaxTree::Node)
|
||||
return Primitive.ast_node_find(scope, node_id)
|
||||
end
|
||||
|
||||
return scope if scope.node_id == node_id
|
||||
|
||||
queue = [scope]
|
||||
while (node = queue.shift)
|
||||
node.compact_child_nodes.each do |child|
|
||||
return child if child.node_id == node_id
|
||||
queue << child
|
||||
end
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
end
|
||||
@@ -97,6 +97,19 @@ class TestProcSyntaxTree < Test::Unit::TestCase
|
||||
assert_equal iseq.source_hash, iseq.to_a[4][:source_hash]
|
||||
end
|
||||
|
||||
def test_backtrace_location_syntax_tree
|
||||
with_loaded_file("def proc_ast_test_loc = caller_locations(0, 1).first\n") do
|
||||
node = proc_ast_test_loc.syntax_tree
|
||||
if PRISM
|
||||
assert_equal :call_node, node.type
|
||||
assert_equal "caller_locations(0, 1)", node.slice
|
||||
else
|
||||
assert_equal :FCALL, node.type
|
||||
end
|
||||
ensure
|
||||
Object.remove_method(:proc_ast_test_loc)
|
||||
end
|
||||
end
|
||||
|
||||
def test_parse_y_syntax_tree
|
||||
assert_separately(%w[--parser=parse.y], "#{<<~"begin;"}\n#{<<~'end;'}")
|
||||
|
||||
Reference in new issue
Block a user