Skip to content

Commit ce9de31

Browse files
committed
Better handle nested constant names
1 parent 4878c21 commit ce9de31

File tree

2 files changed

+90
-18
lines changed

2 files changed

+90
-18
lines changed

lib/syntax_tree/index.rb

Lines changed: 63 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -176,45 +176,79 @@ def location_for(iseq)
176176
Location.new(code_location[0], code_location[1])
177177
end
178178

179+
def find_constant_path(insns, index)
180+
insn = insns[index]
181+
182+
if insn.is_a?(Array) && insn[0] == :opt_getconstant_path
183+
# In this case we're on Ruby 3.2+ and we have an opt_getconstant_path
184+
# instruction, so we already know all of the symbols in the nesting.
185+
insn[1]
186+
elsif insn.is_a?(Symbol) && insn.match?(/\Alabel_\d+/)
187+
# Otherwise, if we have a label then this is very likely the
188+
# destination of an opt_getinlinecache instruction, in which case
189+
# we'll walk backwards to grab up all of the constants.
190+
names = []
191+
192+
index -= 1
193+
until insns[index][0] == :opt_getinlinecache
194+
names.unshift(insns[index][1]) if insns[index][0] == :getconstant
195+
index -= 1
196+
end
197+
198+
names
199+
end
200+
end
201+
179202
def index_iseq(iseq, file_comments)
180203
results = []
181204
queue = [[iseq, []]]
182205

183206
while (current_iseq, current_nesting = queue.shift)
184-
current_iseq[13].each_with_index do |insn, index|
207+
insns = current_iseq[13]
208+
insns.each_with_index do |insn, index|
185209
next unless insn.is_a?(Array)
186210

187211
case insn[0]
188212
when :defineclass
189213
_, name, class_iseq, flags = insn
214+
next_nesting = current_nesting.dup
215+
216+
if (nesting = find_constant_path(insns, index - 2))
217+
# If there is a constant path in the class name, then we need to
218+
# handle that by updating the nesting.
219+
next_nesting << (nesting << name)
220+
else
221+
# Otherwise we'll add the class name to the nesting.
222+
next_nesting << [name]
223+
end
190224

191225
if flags == VM_DEFINECLASS_TYPE_SINGLETON_CLASS
192226
# At the moment, we don't support singletons that aren't
193227
# defined on self. We could, but it would require more
194228
# emulation.
195-
if current_iseq[13][index - 2] != [:putself]
229+
if insns[index - 2] != [:putself]
196230
raise NotImplementedError,
197231
"singleton class with non-self receiver"
198232
end
199233
elsif flags & VM_DEFINECLASS_TYPE_MODULE > 0
200234
location = location_for(class_iseq)
201235
results << ModuleDefinition.new(
202-
current_nesting,
236+
next_nesting,
203237
name,
204238
location,
205239
EntryComments.new(file_comments, location)
206240
)
207241
else
208242
location = location_for(class_iseq)
209243
results << ClassDefinition.new(
210-
current_nesting,
244+
next_nesting,
211245
name,
212246
location,
213247
EntryComments.new(file_comments, location)
214248
)
215249
end
216250

217-
queue << [class_iseq, current_nesting + [name]]
251+
queue << [class_iseq, next_nesting]
218252
when :definemethod
219253
location = location_for(insn[2])
220254
results << MethodDefinition.new(
@@ -259,24 +293,36 @@ def initialize
259293

260294
visit_methods do
261295
def visit_class(node)
262-
name = visit(node.constant).to_sym
296+
names = visit(node.constant)
297+
nesting << names
298+
263299
location =
264300
Location.new(node.location.start_line, node.location.start_column)
265301

266302
results << ClassDefinition.new(
267303
nesting.dup,
268-
name,
304+
names.last,
269305
location,
270306
comments_for(node)
271307
)
272308

273-
nesting << name
274309
super
275310
nesting.pop
276311
end
277312

278313
def visit_const_ref(node)
279-
node.constant.value
314+
[node.constant.value.to_sym]
315+
end
316+
317+
def visit_const_path_ref(node)
318+
names =
319+
if node.parent.is_a?(ConstPathRef)
320+
visit(node.parent)
321+
else
322+
[visit(node.parent)]
323+
end
324+
325+
names << node.constant.value.to_sym
280326
end
281327

282328
def visit_def(node)
@@ -302,18 +348,19 @@ def visit_def(node)
302348
end
303349

304350
def visit_module(node)
305-
name = visit(node.constant).to_sym
351+
names = visit(node.constant)
352+
nesting << names
353+
306354
location =
307355
Location.new(node.location.start_line, node.location.start_column)
308356

309357
results << ModuleDefinition.new(
310358
nesting.dup,
311-
name,
359+
names.last,
312360
location,
313361
comments_for(node)
314362
)
315363

316-
nesting << name
317364
super
318365
nesting.pop
319366
end
@@ -327,6 +374,10 @@ def visit_statements(node)
327374
@statements = node
328375
super
329376
end
377+
378+
def visit_var_ref(node)
379+
node.value.value.to_sym
380+
end
330381
end
331382

332383
private

test/index_test.rb

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ class IndexTest < Minitest::Test
77
def test_module
88
index_each("module Foo; end") do |entry|
99
assert_equal :Foo, entry.name
10-
assert_empty entry.nesting
10+
assert_equal [[:Foo]], entry.nesting
1111
end
1212
end
1313

1414
def test_module_nested
1515
index_each("module Foo; module Bar; end; end") do |entry|
1616
assert_equal :Bar, entry.name
17-
assert_equal [:Foo], entry.nesting
17+
assert_equal [[:Foo], [:Bar]], entry.nesting
1818
end
1919
end
2020

@@ -28,14 +28,35 @@ def test_module_comments
2828
def test_class
2929
index_each("class Foo; end") do |entry|
3030
assert_equal :Foo, entry.name
31-
assert_empty entry.nesting
31+
assert_equal [[:Foo]], entry.nesting
32+
end
33+
end
34+
35+
def test_class_paths_2
36+
index_each("class Foo::Bar; end") do |entry|
37+
assert_equal :Bar, entry.name
38+
assert_equal [[:Foo, :Bar]], entry.nesting
39+
end
40+
end
41+
42+
def test_class_paths_3
43+
index_each("class Foo::Bar::Baz; end") do |entry|
44+
assert_equal :Baz, entry.name
45+
assert_equal [[:Foo, :Bar, :Baz]], entry.nesting
3246
end
3347
end
3448

3549
def test_class_nested
3650
index_each("class Foo; class Bar; end; end") do |entry|
3751
assert_equal :Bar, entry.name
38-
assert_equal [:Foo], entry.nesting
52+
assert_equal [[:Foo], [:Bar]], entry.nesting
53+
end
54+
end
55+
56+
def test_class_paths_nested
57+
index_each("class Foo; class Bar::Baz::Qux; end; end") do |entry|
58+
assert_equal :Qux, entry.name
59+
assert_equal [[:Foo], [:Bar, :Baz, :Qux]], entry.nesting
3960
end
4061
end
4162

@@ -56,7 +77,7 @@ def test_method
5677
def test_method_nested
5778
index_each("class Foo; def foo; end; end") do |entry|
5879
assert_equal :foo, entry.name
59-
assert_equal [:Foo], entry.nesting
80+
assert_equal [[:Foo]], entry.nesting
6081
end
6182
end
6283

@@ -77,7 +98,7 @@ def test_singleton_method
7798
def test_singleton_method_nested
7899
index_each("class Foo; def self.foo; end; end") do |entry|
79100
assert_equal :foo, entry.name
80-
assert_equal [:Foo], entry.nesting
101+
assert_equal [[:Foo]], entry.nesting
81102
end
82103
end
83104

0 commit comments

Comments
 (0)