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
Prev Previous commit
Next Next commit
Handle superclasses
  • Loading branch information
kddnewton committed Feb 21, 2023
commit 2d5f9fc2d4af804662b470c64fe0479277a4b88c
56 changes: 42 additions & 14 deletions lib/syntax_tree/index.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ def initialize(line, column)

# This entry represents a class definition using the class keyword.
class ClassDefinition
attr_reader :nesting, :name, :location, :comments
attr_reader :nesting, :name, :superclass, :location, :comments

def initialize(nesting, name, location, comments)
def initialize(nesting, name, superclass, location, comments)
@nesting = nesting
@name = name
@superclass = superclass
@location = location
@comments = comments
end
Expand Down Expand Up @@ -182,7 +183,7 @@ def find_constant_path(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]
[index - 1, 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
Expand All @@ -195,7 +196,9 @@ def find_constant_path(insns, index)
index -= 1
end

names
[index - 1, names]
else
[index, []]
end
end

Expand All @@ -213,7 +216,24 @@ def index_iseq(iseq, file_comments)
_, name, class_iseq, flags = insn
next_nesting = current_nesting.dup

if (nesting = find_constant_path(insns, index - 2))
# This is the index we're going to search for the nested constant
# path within the declaration name.
constant_index = index - 2

# This is the superclass of the class being defined.
superclass = []

# If there is a superclass, then we're going to find it here and
# then update the constant_index as necessary.
if flags & VM_DEFINECLASS_FLAG_HAS_SUPERCLASS > 0
constant_index, superclass = find_constant_path(insns, index - 1)

if superclass.empty?
raise NotImplementedError, "superclass with non constant path"
end
end

if (_, nesting = find_constant_path(insns, constant_index))
# If there is a constant path in the class name, then we need to
# handle that by updating the nesting.
next_nesting << (nesting << name)
Expand Down Expand Up @@ -243,6 +263,7 @@ def index_iseq(iseq, file_comments)
results << ClassDefinition.new(
next_nesting,
name,
superclass,
location,
EntryComments.new(file_comments, location)
)
Expand Down Expand Up @@ -299,9 +320,23 @@ def visit_class(node)
location =
Location.new(node.location.start_line, node.location.start_column)

superclass =
if node.superclass
visited = visit(node.superclass)

if visited == [[]]
raise NotImplementedError, "superclass with non constant path"
end

visited
else
[]
end

results << ClassDefinition.new(
nesting.dup,
names.last,
superclass,
location,
comments_for(node)
)
Expand All @@ -315,14 +350,7 @@ def visit_const_ref(node)
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
visit(node.parent) << node.constant.value.to_sym
end

def visit_def(node)
Expand Down Expand Up @@ -376,7 +404,7 @@ def visit_statements(node)
end

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

Expand Down
30 changes: 30 additions & 0 deletions test/index_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,36 @@ def test_class_paths_nested
end
end

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

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

def test_class_path_superclass_unknown
source = "class Foo < bar; end"

assert_raises NotImplementedError do
Index.index(source, backend: Index::ParserBackend.new)
end

if defined?(RubyVM::InstructionSequence)
assert_raises NotImplementedError do
Index.index(source, backend: Index::ISeqBackend.new)
end
end
end

def test_class_comments
index_each("# comment1\n# comment2\nclass Foo; end") do |entry|
assert_equal :Foo, entry.name
Expand Down