lib/steep/type_inference/context_array.rb



module Steep
  module TypeInference
    class ContextArray
      class Entry
        attr_reader :range, :context, :sub_entries

        def initialize(range:, context:)
          @range = range
          @context = context
          @sub_entries = Set[].compare_by_identity
        end
      end

      attr_reader :buffer
      attr_reader :root

      def initialize(buffer:, context:, range: 0..buffer.content.size)
        @buffer = buffer
        @root = Entry.new(range: range, context: context)
      end

      def range
        root.range
      end

      def self.from_source(source:, range: nil, context: nil)
        content = if source.node
                    source.node.location.expression.source_buffer.source
                  else
                    ""
                  end
        buffer = RBS::Buffer.new(name: source.path, content: content)
        new(buffer: buffer, context: context, range: range || 0..buffer.content.size)
      end

      def insert_context(range, context:, entry: self.root)
        entry.sub_entries.each do |sub|
          next if sub.range.begin <= range.begin && range.end <= sub.range.end
          next if range.begin <= sub.range.begin && sub.range.end <= range.end
          next if range.end <= sub.range.begin
          next if sub.range.end <= range.begin

          Steep.logger.error { "Range crossing: sub range=#{sub.range}, new range=#{range}" }
          raise
        end

        sup = entry.sub_entries.find do |sub|
          sub.range.begin < range.begin && range.end <= sub.range.end
        end

        if sup
          insert_context(range, context: context, entry: sup)
        else
          subs = entry.sub_entries.select do |sub|
            range.begin < sub.range.begin && sub.range.end <= range.end
          end

          new_entry = Entry.new(range: range, context: context)
          entry.sub_entries.subtract(subs)
          new_entry.sub_entries.merge(subs)
          entry.sub_entries << new_entry
        end
      end

      def each_entry
        if block_given?
          es = [root]

          until es.empty?
            e = es.pop
            es.push(*e.sub_entries.to_a)

            yield e
          end
        else
          enum_for :each_entry
        end
      end

      def context_at(index, entry: self.root)
        return nil if index < entry.range.begin || entry.range.end < index

        sub = entry.sub_entries.find do |sub|
          sub.range.begin <= index && index <= sub.range.end
        end

        if sub
          context_at(index, entry: sub)
        else
          entry.context
        end
      end

      def [](index)
        context_at(index)
      end

      def at(line:, column:)
        pos = buffer.loc_to_pos([line, column])
        self[pos]
      end

      def merge(subtree)
        subtree.each_entry do |entry|
          if entry.context
            insert_context entry.range, context: entry.context
          end
        end
      end
    end
  end
end