class SyntaxTree::Index::ISeqBackend

runtimes.
faster than using the Syntax Tree parser, but is not available on all
This backend creates the index using RubyVM::InstructionSequence, which is

def find_attr_arguments(insns, index)

def find_attr_arguments(insns, index)
  orig_argc = insns[index][1][:orig_argc]
  names = []
  current = index - 1
  while current >= 0 && names.length < orig_argc
    if insns[current].is_a?(Array) && insns[current][0] == :putobject
      names.unshift(insns[current][1])
    end
    current -= 1
  end
  names if insns[current] == [:putself] && names.length == orig_argc
end

def find_constant_path(insns, index)

def find_constant_path(insns, index)
  index -= 1 while index >= 0 &&
    (
      insns[index].is_a?(Integer) ||
        (
          insns[index].is_a?(Array) &&
            %i[swap topn].include?(insns[index][0])
        )
    )
  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.
    [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
    # we'll walk backwards to grab up all of the constants.
    names = []
    index -= 1
    until insns[index].is_a?(Array) &&
            insns[index][0] == :opt_getinlinecache
      if insns[index].is_a?(Array) && insns[index][0] == :getconstant
        names.unshift(insns[index][1])
      end
      index -= 1
    end
    [index - 1, names]
  else
    [index, []]
  end
end

def index(source)

def index(source)
  index_iseq(
    RubyVM::InstructionSequence.compile(source).to_a,
    FileComments.new(FileComments::StringSource.new(source))
  )
end

def index_file(filepath)

def index_file(filepath)
  index_iseq(
    RubyVM::InstructionSequence.compile_file(filepath).to_a,
    FileComments.new(FileComments::FileSource.new(filepath))
  )
end

def index_iseq(iseq, file_comments)

def index_iseq(iseq, file_comments)
  results = []
  queue = [[iseq, []]]
  while (current_iseq, current_nesting = queue.shift)
    file = current_iseq[5]
    line = current_iseq[8]
    insns = current_iseq[13]
    insns.each_with_index do |insn, index|
      case insn
      when Integer
        line = insn
        next
      when Array
        # continue on
      else
        # skip everything else
        next
      end
      case insn[0]
      when :defineclass
        _, name, class_iseq, flags = insn
        next_nesting = current_nesting.dup
        # 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?
            warn("#{file}:#{line}: superclass with non constant path")
            next
          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)
        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 insns[index - 2] != [:putself]
            warn(
              "#{file}:#{line}: singleton class with non-self receiver"
            )
            next
          end
        elsif flags & VM_DEFINECLASS_TYPE_MODULE > 0
          location = location_for(class_iseq)
          results << ModuleDefinition.new(
            next_nesting,
            name,
            location,
            EntryComments.new(file_comments, location)
          )
        else
          location = location_for(class_iseq)
          results << ClassDefinition.new(
            next_nesting,
            name,
            superclass,
            location,
            EntryComments.new(file_comments, location)
          )
        end
        queue << [class_iseq, next_nesting]
      when :definemethod
        location = location_for(insn[2])
        results << method_definition(
          current_nesting,
          insn[1],
          location,
          file_comments
        )
      when :definesmethod
        if insns[index - 1] != [:putself]
          warn("#{file}:#{line}: singleton method with non-self receiver")
          next
        end
        location = location_for(insn[2])
        results << SingletonMethodDefinition.new(
          current_nesting,
          insn[1],
          location,
          EntryComments.new(file_comments, location)
        )
      when :setconstant
        next_nesting = current_nesting.dup
        name = insn[1]
        _, nesting = find_constant_path(insns, index - 1)
        next_nesting << nesting if nesting.any?
        location = Location.new(line, :unknown)
        results << ConstantDefinition.new(
          next_nesting,
          name,
          location,
          EntryComments.new(file_comments, location)
        )
      when :opt_send_without_block, :send
        case insn[1][:mid]
        when :attr_reader, :attr_writer, :attr_accessor
          attr_names = find_attr_arguments(insns, index)
          next unless attr_names
          location = Location.new(line, :unknown)
          attr_names.each do |attr_name|
            if insn[1][:mid] != :attr_writer
              results << method_definition(
                current_nesting,
                attr_name,
                location,
                file_comments
              )
            end
            if insn[1][:mid] != :attr_reader
              results << method_definition(
                current_nesting,
                :"#{attr_name}=",
                location,
                file_comments
              )
            end
          end
        when :"core#set_method_alias"
          # Now we have to validate that the alias is happening with a
          # non-interpolated value. To do this we'll match the specific
          # pattern we're expecting.
          values =
            insns[(index - 4)...index].map do |previous|
              previous.is_a?(Array) ? previous[0] : previous
            end
          if values !=
               %i[putspecialobject putspecialobject putobject putobject]
            next
          end
          # Now that we know it's in the structure we want it, we can use
          # the values of the putobject to determine the alias.
          location = Location.new(line, :unknown)
          results << AliasMethodDefinition.new(
            current_nesting,
            insns[index - 2][1],
            location,
            EntryComments.new(file_comments, location)
          )
        end
      end
    end
  end
  results
end

def location_for(iseq)

def location_for(iseq)
  code_location = iseq[4][:code_location]
  Location.new(code_location[0], code_location[1])
end

def method_definition(nesting, name, location, file_comments)

def method_definition(nesting, name, location, file_comments)
  comments = EntryComments.new(file_comments, location)
  if nesting.last == [:singletonclass]
    SingletonMethodDefinition.new(
      nesting[0...-1],
      name,
      location,
      comments
    )
  else
    MethodDefinition.new(nesting, name, location, comments)
  end
end