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:
Yusuke EndohandClaude Fable 5 committed 2026-08-07 20:56:33 +09:00
1 parent 6e65742a9f
commit 6408f147b1
3 files changed
+61
Notes: git 2026-08-07 11:57:13 +00:00

No files matched your search

+12
View File
@@ -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)
{
+36
View File
@@ -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
+13
View File
@@ -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;'}")