Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Better handle nested constant names
  • Loading branch information
kddnewton committed Feb 21, 2023
commit ce9de3114c537de85cc86f90bf603d56d7eba653
75 changes: 63 additions & 12 deletions lib/syntax_tree/index.rb
Original file line number Diff line number Diff line change
Expand Up @@ -176,45 +176,79 @@ def location_for(iseq)
Location.new(code_location[0], code_location[1])
end

def find_constant_path(insns, index)
insn = insns[index]

if insn.is_a?(Array) && insn[0] == :opt_getconstant_path
# In this case we're on Ruby 3.2+ and we have an opt_getconstant_path
# instruction, so we already know all of the symbols in the nesting.
insn[1]
elsif insn.is_a?(Symbol) && insn.match?(/\Alabel_\d+/)
# Otherwise, if we have a label then this is very likely the
# destination of an opt_getinlinecache instruction, in which case
# we'll walk backwards to grab up all of the constants.
names = []

index -= 1
until insns[index][0] == :opt_getinlinecache
names.unshift(insns[index][1]) if insns[index][0] == :getconstant
index -= 1
end

names
end
end

def index_iseq(iseq, file_comments)
results = []
queue = [[iseq, []]]

while (current_iseq, current_nesting = queue.shift)
current_iseq[13].each_with_index do |insn, index|
insns = current_iseq[13]
insns.each_with_index do |insn, index|
next unless insn.is_a?(Array)

case insn[0]
when :defineclass
_, name, class_iseq, flags = insn
next_nesting = current_nesting.dup

if (nesting = find_constant_path(insns, index - 2))
# If there is a constant path in the class name, then we need to
# handle that by updating the nesting.
next_nesting << (nesting << name)
else
# Otherwise we'll add the class name to the nesting.
next_nesting << [name]
end

if flags == VM_DEFINECLASS_TYPE_SINGLETON_CLASS
# At the moment, we don't support singletons that aren't
# defined on self. We could, but it would require more
# emulation.
if current_iseq[13][index - 2] != [:putself]
if insns[index - 2] != [:putself]
raise NotImplementedError,
"singleton class with non-self receiver"
end
elsif flags & VM_DEFINECLASS_TYPE_MODULE > 0
location = location_for(class_iseq)
results << ModuleDefinition.new(
current_nesting,
next_nesting,
name,
location,
EntryComments.new(file_comments, location)
)
else
location = location_for(class_iseq)
results << ClassDefinition.new(
current_nesting,
next_nesting,
name,
location,
EntryComments.new(file_comments, location)
)
end

queue << [class_iseq, current_nesting + [name]]
queue << [class_iseq, next_nesting]
when :definemethod
location = location_for(insn[2])
results << MethodDefinition.new(
Expand Down Expand Up @@ -259,24 +293,36 @@ def initialize

visit_methods do
def visit_class(node)
name = visit(node.constant).to_sym
names = visit(node.constant)
nesting << names

location =
Location.new(node.location.start_line, node.location.start_column)

results << ClassDefinition.new(
nesting.dup,
name,
names.last,
location,
comments_for(node)
)

nesting << name
super
nesting.pop
end

def visit_const_ref(node)
node.constant.value
[node.constant.value.to_sym]
end

def visit_const_path_ref(node)
names =
if node.parent.is_a?(ConstPathRef)
visit(node.parent)
else
[visit(node.parent)]
end

names << node.constant.value.to_sym
end

def visit_def(node)
Expand All @@ -302,18 +348,19 @@ def visit_def(node)
end

def visit_module(node)
name = visit(node.constant).to_sym
names = visit(node.constant)
nesting << names

location =
Location.new(node.location.start_line, node.location.start_column)

results << ModuleDefinition.new(
nesting.dup,
name,
names.last,
location,
comments_for(node)
)

nesting << name
super
nesting.pop
end
Expand All @@ -327,6 +374,10 @@ def visit_statements(node)
@statements = node
super
end

def visit_var_ref(node)
node.value.value.to_sym
end
end

private
Expand Down
33 changes: 27 additions & 6 deletions test/index_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ class IndexTest < Minitest::Test
def test_module
index_each("module Foo; end") do |entry|
assert_equal :Foo, entry.name
assert_empty entry.nesting
assert_equal [[:Foo]], entry.nesting
end
end

def test_module_nested
index_each("module Foo; module Bar; end; end") do |entry|
assert_equal :Bar, entry.name
assert_equal [:Foo], entry.nesting
assert_equal [[:Foo], [:Bar]], entry.nesting
end
end

Expand All @@ -28,14 +28,35 @@ def test_module_comments
def test_class
index_each("class Foo; end") do |entry|
assert_equal :Foo, entry.name
assert_empty entry.nesting
assert_equal [[:Foo]], entry.nesting
end
end

def test_class_paths_2
index_each("class Foo::Bar; end") do |entry|
assert_equal :Bar, entry.name
assert_equal [[:Foo, :Bar]], entry.nesting
end
end

def test_class_paths_3
index_each("class Foo::Bar::Baz; end") do |entry|
assert_equal :Baz, entry.name
assert_equal [[:Foo, :Bar, :Baz]], entry.nesting
end
end

def test_class_nested
index_each("class Foo; class Bar; end; end") do |entry|
assert_equal :Bar, entry.name
assert_equal [:Foo], entry.nesting
assert_equal [[:Foo], [:Bar]], entry.nesting
end
end

def test_class_paths_nested
index_each("class Foo; class Bar::Baz::Qux; end; end") do |entry|
assert_equal :Qux, entry.name
assert_equal [[:Foo], [:Bar, :Baz, :Qux]], entry.nesting
end
end

Expand All @@ -56,7 +77,7 @@ def test_method
def test_method_nested
index_each("class Foo; def foo; end; end") do |entry|
assert_equal :foo, entry.name
assert_equal [:Foo], entry.nesting
assert_equal [[:Foo]], entry.nesting
end
end

Expand All @@ -77,7 +98,7 @@ def test_singleton_method
def test_singleton_method_nested
index_each("class Foo; def self.foo; end; end") do |entry|
assert_equal :foo, entry.name
assert_equal [:Foo], entry.nesting
assert_equal [[:Foo]], entry.nesting
end
end

Expand Down