class ReplTypeCompletor::RootScope - repl_type_completor: Ruby Standard Library Documentation

# class ReplTypeCompletor::RootScope

### Attributes

module_nesting[R]
self_object[R]

### Public Class Methods

new(binding, self_object, local_variables) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/scope.rb, line 10
    def initialize(binding, self_object, local_variables)
      @binding = binding
      @self_object = self_object
      @cache = {}
      modules = [*binding.eval('::Module.nesting'), Object]
      @module_nesting = modules.map { [_1, []] }
      binding_local_variables = binding.local_variables
      uninitialized_locals = local_variables - binding_local_variables
      uninitialized_locals.each { @cache[_1] = Types::NIL }
      @local_variables = (local_variables | binding_local_variables).map(&:to_s).to_set
      @global_variables = Kernel.global_variables.map(&:to_s).to_set
      @owned_constants_cache = {}
    end

type_by_name(name) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/scope.rb, line 84
    def self.type_by_name(name)
      if name.start_with? '@@'
        # "@@cvar" or "@@cvar::[module_id]::[module_path]"
        :cvar
      elsif name.start_with? '@'
        :ivar
      elsif name.start_with? '$'
        :gvar
      elsif name.start_with? '%'
        :internal
      elsif name[0].downcase != name[0] || name[0].match?(/\d/)
        # "ConstName" or "[module_id]::[const_path]"
        :const
      else
        :lvar
      end
    end

### Public Instance Methods

[](name) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/scope.rb, line 56
    def [](name)
      @cache[name] ||= (
        value = case RootScope.type_by_name name
        when :ivar
          begin
            Methods::OBJECT_INSTANCE_VARIABLE_GET_METHOD.bind_call(@self_object, name)
          rescue NameError
          end
        when :lvar
          begin
            @binding.local_variable_get(name)
          rescue NameError
          end
        when :gvar
          @binding.eval name if @global_variables.include? name
        end
        Types.type_from_object(value)
      )
    end

get_const(nesting, path, _key = nil) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/scope.rb, line 35
    def get_const(nesting, path, _key = nil)
      return unless nesting

      result = path.reduce nesting do |mod, name|
        return nil unless mod.is_a?(Module) && module_own_constant?(mod, name)
        mod.const_get name
      end
      Types.type_from_object result
    end

get_cvar(nesting, path, name, _key = nil) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/scope.rb, line 45
    def get_cvar(nesting, path, name, _key = nil)
      return Types::NIL unless nesting

      result = path.reduce nesting do |mod, n|
        return Types::NIL unless mod.is_a?(Module) && module_own_constant?(mod, n)
        mod.const_get n
      end
      value = result.class_variable_get name if result.is_a?(Module) && name.size >= 3 && result.class_variable_defined?(name)
      Types.type_from_object value
    end

global_variables() click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/scope.rb, line 82
      def global_variables() = @global_variables.to_a

      def self.type_by_name(name)
        if name.start_with? '@@'
          # "@@cvar" or "@@cvar::[module_id]::[module_path]"
          :cvar
        elsif name.start_with? '@'
          :ivar
        elsif name.start_with? '$'
          :gvar
        elsif name.start_with? '%'
          :internal
        elsif name[0].downcase != name[0] || name[0].match?(/\d/)
          # "ConstName" or "[module_id]::[const_path]"
          :const
        else
          :lvar
        end
      end
    end

level() click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/scope.rb, line 24
      def level() = 0

      def level_of(_name, _var_type) = 0

      def mutable?() = false

      def module_own_constant?(mod, name)
        set = (@owned_constants_cache[mod] ||= Set.new(mod.constants.map(&:to_s)))
        set.include? name
      end

      def get_const(nesting, path, _key = nil)
        return unless nesting

        result = path.reduce nesting do |mod, name|
          return nil unless mod.is_a?(Module) && module_own_constant?(mod, name)
          mod.const_get name
        end
        Types.type_from_object result
      end

      def get_cvar(nesting, path, name, _key = nil)
        return Types::NIL unless nesting

        result = path.reduce nesting do |mod, n|
          return Types::NIL unless mod.is_a?(Module) && module_own_constant?(mod, n)
          mod.const_get n
        end
        value = result.class_variable_get name if result.is_a?(Module) && name.size >= 3 && result.class_variable_defined?(name)
        Types.type_from_object value
      end

      def [](name)
        @cache[name] ||= (
          value = case RootScope.type_by_name name
          when :ivar
            begin
              Methods::OBJECT_INSTANCE_VARIABLE_GET_METHOD.bind_call(@self_object, name)
            rescue NameError
            end
          when :lvar
            begin
              @binding.local_variable_get(name)
            rescue NameError
            end
          when :gvar
            @binding.eval name if @global_variables.include? name
          end
          Types.type_from_object(value)
        )
      end

      def self_type
        Types.type_from_object @self_object
      end

      def local_variables() = @local_variables.to_a

      def global_variables() = @global_variables.to_a

      def self.type_by_name(name)
        if name.start_with? '@@'
          # "@@cvar" or "@@cvar::[module_id]::[module_path]"
          :cvar
        elsif name.start_with? '@'
          :ivar
        elsif name.start_with? '$'
          :gvar
        elsif name.start_with? '%'
          :internal
        elsif name[0].downcase != name[0] || name[0].match?(/\d/)
          # "ConstName" or "[module_id]::[const_path]"
          :const
        else
          :lvar
        end
      end
    end

    class Scope
      BREAK_RESULT = '%break'
      NEXT_RESULT = '%next'
      RETURN_RESULT = '%return'
      PATTERNMATCH_BREAK = '%match'

      attr_reader :parent, :mergeable_changes, :level, :module_nesting

      def self.from_binding(binding, locals) = new(RootScope.new(binding, binding.receiver, locals))

      def initialize(parent, table = {}, trace_ivar: true, trace_lvar: true, self_type: nil, nesting: nil)
        @parent = parent
        @level = parent.level + 1
        @trace_ivar = trace_ivar
        @trace_lvar = trace_lvar
        @module_nesting = nesting ? [nesting, *parent.module_nesting] : parent.module_nesting
        @self_type = self_type
        @terminated = false
        @jump_branches = []
        @mergeable_changes = @table = table.transform_values { [level, _1] }
      end

      def mutable? = true

      def terminated?
        @terminated
      end

      def terminate_with(type, value)
        return if terminated?
        store_jump type, value, @mergeable_changes
        terminate
      end

      def store_jump(type, value, changes)
        return if terminated?
        if has_own?(type)
          changes[type] = [level, value]
          @jump_branches << changes
        elsif @parent.mutable?
          @parent.store_jump(type, value, changes)
        end
      end

      def terminate
        return if terminated?
        @terminated = true
        @table = @mergeable_changes.dup
      end

      def trace?(name)
        return false unless @parent
        type = RootScope.type_by_name(name)
        type == :ivar ? @trace_ivar : type == :lvar ? @trace_lvar : true
      end

      def level_of(name, var_type)
        case var_type
        when :ivar
          return level unless @trace_ivar
        when :gvar
          return 0
        end
        variable_level, = @table[name]
        variable_level || parent.level_of(name, var_type)
      end

      def get_const(nesting, path, key = nil)
        key ||= [nesting.__id__, path].join('::')
        _l, value = @table[key]
        value || @parent.get_const(nesting, path, key)
      end

      def get_cvar(nesting, path, name, key = nil)
        key ||= [name, nesting.__id__, path].join('::')
        _l, value = @table[key]
        value || @parent.get_cvar(nesting, path, name, key)
      end

      def [](name)
        type = RootScope.type_by_name(name)
        if type == :const
          return get_const(nil, nil, name) || Types::NIL if name.include?('::')

          module_nesting.each do |(nesting, path)|
            value = get_const nesting, [*path, name]
            return value if value
          end
          return Types::NIL
        elsif type == :cvar
          return get_cvar(nil, nil, nil, name) if name.include?('::')

          nesting, path = module_nesting.first
          return get_cvar(nesting, path, name)
        end
        level, value = @table[name]
        if level
          value
        elsif trace? name
          @parent[name]
        elsif type == :ivar
          self_instance_variable_get name
        end
      end

      def set_const(nesting, path, value)
        key = [nesting.__id__, path].join('::')
        @table[key] = [0, value]
      end

      def set_cvar(nesting, path, name, value)
        key = [name, nesting.__id__, path].join('::')
        @table[key] = [0, value]
      end

      def []=(name, value)
        type = RootScope.type_by_name(name)
        if type == :const
          if name.include?('::')
            @table[name] = [0, value]
          else
            parent_module, parent_path = module_nesting.first
            set_const parent_module, [*parent_path, name], value
          end
          return
        elsif type == :cvar
          if name.include?('::')
            @table[name] = [0, value]
          else
            parent_module, parent_path = module_nesting.first
            set_cvar parent_module, parent_path, name, value
          end
          return
        end
        variable_level = level_of name, type
        @table[name] = [variable_level, value] if variable_level
      end

      def self_type
        @self_type || @parent.self_type
      end

      def global_variables
        gvar_keys = @table.keys.select do |name|
          RootScope.type_by_name(name) == :gvar
        end
        gvar_keys | @parent.global_variables
      end

      def local_variables
        lvar_keys = @table.keys.select do |name|
          RootScope.type_by_name(name) == :lvar
        end
        lvar_keys |= @parent.local_variables if @trace_lvar
        lvar_keys
      end

      def table_constants
        constants = module_nesting.flat_map do |mod, path|
          prefix = [mod.__id__, *path].join('::') + '::'
          @table.keys.select { _1.start_with? prefix }.map { _1.delete_prefix(prefix).split('::').first }
        end.uniq
        constants |= @parent.table_constants if @parent.mutable?
        constants
      end

      def table_module_constants(mod)
        prefix = "#{mod.__id__}::"
        constants = @table.keys.select { _1.start_with? prefix }.map { _1.delete_prefix(prefix).split('::').first }
        constants |= @parent.table_constants if @parent.mutable?
        constants
      end

      def base_scope
        @parent.mutable? ? @parent.base_scope : @parent
      end

      def table_instance_variables
        ivars = @table.keys.select { RootScope.type_by_name(_1) == :ivar }
        ivars |= @parent.table_instance_variables if @parent.mutable? && @trace_ivar
        ivars
      end

      def instance_variables
        self_singleton_types = self_type.types.grep(Types::SingletonType)
        singleton_classes = self_type.types.grep(Types::InstanceType).map(&:klass).select(&:singleton_class?)
        base_self = base_scope.self_object
        self_instance_variables = singleton_classes.flat_map do |singleton_class|
          if singleton_class.respond_to? :attached_object
            Methods::OBJECT_INSTANCE_VARIABLES_METHOD.bind_call(singleton_class.attached_object).map(&:to_s)
          elsif singleton_class == Methods::OBJECT_SINGLETON_CLASS_METHOD.bind_call(base_self)
            Methods::OBJECT_INSTANCE_VARIABLES_METHOD.bind_call(base_self).map(&:to_s)
          else
            []
          end
        end
        [
          self_singleton_types.flat_map { _1.module_or_class.instance_variables.map(&:to_s) },
          self_instance_variables || [],
          table_instance_variables
        ].inject(:|)
      end

      def self_instance_variable_get(name)
        self_objects = self_type.types.grep(Types::SingletonType).map(&:module_or_class)
        singleton_classes = self_type.types.grep(Types::InstanceType).map(&:klass).select(&:singleton_class?)
        base_self = base_scope.self_object
        singleton_classes.each do |singleton_class|
          if singleton_class.respond_to? :attached_object
            self_objects << singleton_class.attached_object
          elsif singleton_class == base_self.singleton_class
            self_objects << base_self
          end
        end
        types = self_objects.map do |object|
          value = begin
            Methods::OBJECT_INSTANCE_VARIABLE_GET_METHOD.bind_call(object, name)
          rescue NameError
          end
          Types.type_from_object value
        end
        Types::UnionType[*types]
      end

      def table_class_variables
        cvars = @table.keys.filter_map { _1.split('::', 2).first if RootScope.type_by_name(_1) == :cvar }
        cvars |= @parent.table_class_variables if @parent.mutable?
        cvars
      end

      def class_variables
        cvars = table_class_variables
        m, = module_nesting.first
        cvars |= m.class_variables.map(&:to_s) if m.is_a? Module
        cvars
      end

      def constants
        module_nesting.flat_map do |nest,|
          nest.constants
        end.map(&:to_s) | table_constants
      end

      def merge_jumps
        if terminated?
          @terminated = false
          @table = @mergeable_changes
          merge @jump_branches
          @terminated = true
        else
          merge [*@jump_branches, {}]
        end
      end

      def conditional(&block)
        run_branches(block, ->(_s) {}).first || Types::NIL
      end

      def never(&block)
        block.call Scope.new(self, { BREAK_RESULT => nil, NEXT_RESULT => nil, PATTERNMATCH_BREAK => nil, RETURN_RESULT => nil })
      end

      def run_branches(*blocks)
        results = []
        branches = []
        blocks.each do |block|
          scope = Scope.new self
          result = block.call scope
          next if scope.terminated?
          results << result
          branches << scope.mergeable_changes
        end
        terminate if branches.empty?
        merge branches
        results
      end

      def has_own?(name)
        @table.key? name
      end

      def update(child_scope)
        current_level = level
        child_scope.mergeable_changes.each do |name, (level, value)|
          self[name] = value if level <= current_level
        end
      end

      protected

      def merge(branches)
        current_level = level
        merge = {}
        branches.each do |changes|
          changes.each do |name, (level, value)|
            next if current_level < level
            (merge[name] ||= []) << value
          end
        end
        merge.each do |name, values|
          values << self[name] unless values.size == branches.size
          values.compact!
          self[name] = Types::UnionType[*values.compact] unless values.empty?

level_of(_name, _var_type) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/scope.rb, line 26
      def level_of(_name, _var_type) = 0

      def mutable?() = false

      def module_own_constant?(mod, name)
        set = (@owned_constants_cache[mod] ||= Set.new(mod.constants.map(&:to_s)))
        set.include? name
      end

      def get_const(nesting, path, _key = nil)
        return unless nesting

        result = path.reduce nesting do |mod, name|
          return nil unless mod.is_a?(Module) && module_own_constant?(mod, name)
          mod.const_get name
        end
        Types.type_from_object result
      end

      def get_cvar(nesting, path, name, _key = nil)
        return Types::NIL unless nesting

        result = path.reduce nesting do |mod, n|
          return Types::NIL unless mod.is_a?(Module) && module_own_constant?(mod, n)
          mod.const_get n
        end
        value = result.class_variable_get name if result.is_a?(Module) && name.size >= 3 && result.class_variable_defined?(name)
        Types.type_from_object value
      end

      def [](name)
        @cache[name] ||= (
          value = case RootScope.type_by_name name
          when :ivar
            begin
              Methods::OBJECT_INSTANCE_VARIABLE_GET_METHOD.bind_call(@self_object, name)
            rescue NameError
            end
          when :lvar
            begin
              @binding.local_variable_get(name)
            rescue NameError
            end
          when :gvar
            @binding.eval name if @global_variables.include? name
          end
          Types.type_from_object(value)
        )
      end

      def self_type
        Types.type_from_object @self_object
      end

      def local_variables() = @local_variables.to_a

      def global_variables() = @global_variables.to_a

      def self.type_by_name(name)
        if name.start_with? '@@'
          # "@@cvar" or "@@cvar::[module_id]::[module_path]"
          :cvar
        elsif name.start_with? '@'
          :ivar
        elsif name.start_with? '$'
          :gvar
        elsif name.start_with? '%'
          :internal
        elsif name[0].downcase != name[0] || name[0].match?(/\d/)
          # "ConstName" or "[module_id]::[const_path]"
          :const
        else
          :lvar
        end
      end
    end

    class Scope
      BREAK_RESULT = '%break'
      NEXT_RESULT = '%next'
      RETURN_RESULT = '%return'
      PATTERNMATCH_BREAK = '%match'

      attr_reader :parent, :mergeable_changes, :level, :module_nesting

      def self.from_binding(binding, locals) = new(RootScope.new(binding, binding.receiver, locals))

      def initialize(parent, table = {}, trace_ivar: true, trace_lvar: true, self_type: nil, nesting: nil)
        @parent = parent
        @level = parent.level + 1
        @trace_ivar = trace_ivar
        @trace_lvar = trace_lvar
        @module_nesting = nesting ? [nesting, *parent.module_nesting] : parent.module_nesting
        @self_type = self_type
        @terminated = false
        @jump_branches = []
        @mergeable_changes = @table = table.transform_values { [level, _1] }
      end

      def mutable? = true

      def terminated?
        @terminated
      end

      def terminate_with(type, value)
        return if terminated?
        store_jump type, value, @mergeable_changes
        terminate
      end

      def store_jump(type, value, changes)
        return if terminated?
        if has_own?(type)
          changes[type] = [level, value]
          @jump_branches << changes
        elsif @parent.mutable?
          @parent.store_jump(type, value, changes)
        end
      end

      def terminate
        return if terminated?
        @terminated = true
        @table = @mergeable_changes.dup
      end

      def trace?(name)
        return false unless @parent
        type = RootScope.type_by_name(name)
        type == :ivar ? @trace_ivar : type == :lvar ? @trace_lvar : true
      end

      def level_of(name, var_type)
        case var_type
        when :ivar
          return level unless @trace_ivar
        when :gvar
          return 0
        end
        variable_level, = @table[name]
        variable_level || parent.level_of(name, var_type)
      end

      def get_const(nesting, path, key = nil)
        key ||= [nesting.__id__, path].join('::')
        _l, value = @table[key]
        value || @parent.get_const(nesting, path, key)
      end

      def get_cvar(nesting, path, name, key = nil)
        key ||= [name, nesting.__id__, path].join('::')
        _l, value = @table[key]
        value || @parent.get_cvar(nesting, path, name, key)
      end

      def [](name)
        type = RootScope.type_by_name(name)
        if type == :const
          return get_const(nil, nil, name) || Types::NIL if name.include?('::')

          module_nesting.each do |(nesting, path)|
            value = get_const nesting, [*path, name]
            return value if value
          end
          return Types::NIL
        elsif type == :cvar
          return get_cvar(nil, nil, nil, name) if name.include?('::')

          nesting, path = module_nesting.first
          return get_cvar(nesting, path, name)
        end
        level, value = @table[name]
        if level
          value
        elsif trace? name
          @parent[name]
        elsif type == :ivar
          self_instance_variable_get name
        end
      end

      def set_const(nesting, path, value)
        key = [nesting.__id__, path].join('::')
        @table[key] = [0, value]
      end

      def set_cvar(nesting, path, name, value)
        key = [name, nesting.__id__, path].join('::')
        @table[key] = [0, value]
      end

      def []=(name, value)
        type = RootScope.type_by_name(name)
        if type == :const
          if name.include?('::')
            @table[name] = [0, value]
          else
            parent_module, parent_path = module_nesting.first
            set_const parent_module, [*parent_path, name], value
          end
          return
        elsif type == :cvar
          if name.include?('::')
            @table[name] = [0, value]
          else
            parent_module, parent_path = module_nesting.first
            set_cvar parent_module, parent_path, name, value
          end
          return
        end
        variable_level = level_of name, type
        @table[name] = [variable_level, value] if variable_level
      end

      def self_type
        @self_type || @parent.self_type
      end

      def global_variables
        gvar_keys = @table.keys.select do |name|
          RootScope.type_by_name(name) == :gvar
        end
        gvar_keys | @parent.global_variables
      end

      def local_variables
        lvar_keys = @table.keys.select do |name|
          RootScope.type_by_name(name) == :lvar
        end
        lvar_keys |= @parent.local_variables if @trace_lvar
        lvar_keys
      end

      def table_constants
        constants = module_nesting.flat_map do |mod, path|
          prefix = [mod.__id__, *path].join('::') + '::'
          @table.keys.select { _1.start_with? prefix }.map { _1.delete_prefix(prefix).split('::').first }
        end.uniq
        constants |= @parent.table_constants if @parent.mutable?
        constants
      end

      def table_module_constants(mod)
        prefix = "#{mod.__id__}::"
        constants = @table.keys.select { _1.start_with? prefix }.map { _1.delete_prefix(prefix).split('::').first }
        constants |= @parent.table_constants if @parent.mutable?
        constants
      end

      def base_scope
        @parent.mutable? ? @parent.base_scope : @parent
      end

      def table_instance_variables
        ivars = @table.keys.select { RootScope.type_by_name(_1) == :ivar }
        ivars |= @parent.table_instance_variables if @parent.mutable? && @trace_ivar
        ivars
      end

      def instance_variables
        self_singleton_types = self_type.types.grep(Types::SingletonType)
        singleton_classes = self_type.types.grep(Types::InstanceType).map(&:klass).select(&:singleton_class?)
        base_self = base_scope.self_object
        self_instance_variables = singleton_classes.flat_map do |singleton_class|
          if singleton_class.respond_to? :attached_object
            Methods::OBJECT_INSTANCE_VARIABLES_METHOD.bind_call(singleton_class.attached_object).map(&:to_s)
          elsif singleton_class == Methods::OBJECT_SINGLETON_CLASS_METHOD.bind_call(base_self)
            Methods::OBJECT_INSTANCE_VARIABLES_METHOD.bind_call(base_self).map(&:to_s)
          else
            []
          end
        end
        [
          self_singleton_types.flat_map { _1.module_or_class.instance_variables.map(&:to_s) },
          self_instance_variables || [],
          table_instance_variables
        ].inject(:|)
      end

      def self_instance_variable_get(name)
        self_objects = self_type.types.grep(Types::SingletonType).map(&:module_or_class)
        singleton_classes = self_type.types.grep(Types::InstanceType).map(&:klass).select(&:singleton_class?)
        base_self = base_scope.self_object
        singleton_classes.each do |singleton_class|
          if singleton_class.respond_to? :attached_object
            self_objects << singleton_class.attached_object
          elsif singleton_class == base_self.singleton_class
            self_objects << base_self
          end
        end
        types = self_objects.map do |object|
          value = begin
            Methods::OBJECT_INSTANCE_VARIABLE_GET_METHOD.bind_call(object, name)
          rescue NameError
          end
          Types.type_from_object value
        end
        Types::UnionType[*types]
      end

      def table_class_variables
        cvars = @table.keys.filter_map { _1.split('::', 2).first if RootScope.type_by_name(_1) == :cvar }
        cvars |= @parent.table_class_variables if @parent.mutable?
        cvars
      end

      def class_variables
        cvars = table_class_variables
        m, = module_nesting.first
        cvars |= m.class_variables.map(&:to_s) if m.is_a? Module
        cvars
      end

      def constants
        module_nesting.flat_map do |nest,|
          nest.constants
        end.map(&:to_s) | table_constants
      end

      def merge_jumps
        if terminated?
          @terminated = false
          @table = @mergeable_changes
          merge @jump_branches
          @terminated = true
        else
          merge [*@jump_branches, {}]
        end
      end

      def conditional(&block)
        run_branches(block, ->(_s) {}).first || Types::NIL
      end

      def never(&block)
        block.call Scope.new(self, { BREAK_RESULT => nil, NEXT_RESULT => nil, PATTERNMATCH_BREAK => nil, RETURN_RESULT => nil })
      end

      def run_branches(*blocks)
        results = []
        branches = []
        blocks.each do |block|
          scope = Scope.new self
          result = block.call scope
          next if scope.terminated?
          results << result
          branches << scope.mergeable_changes
        end
        terminate if branches.empty?
        merge branches
        results
      end

      def has_own?(name)
        @table.key? name
      end

      def update(child_scope)
        current_level = level
        child_scope.mergeable_changes.each do |name, (level, value)|
          self[name] = value if level <= current_level
        end
      end

      protected

      def merge(branches)
        current_level = level
        merge = {}
        branches.each do |changes|
          changes.each do |name, (level, value)|
            next if current_level < level
            (merge[name] ||= []) << value
          end
        end
        merge.each do |name, values|
          values << self[name] unless values.size == branches.size
          values.compact!
          self[name] = Types::UnionType[*values.compact] unless values.empty?
        end

local_variables() click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/scope.rb, line 80
      def local_variables() = @local_variables.to_a

      def global_variables() = @global_variables.to_a

      def self.type_by_name(name)
        if name.start_with? '@@'
          # "@@cvar" or "@@cvar::[module_id]::[module_path]"
          :cvar
        elsif name.start_with? '@'
          :ivar
        elsif name.start_with? '$'
          :gvar
        elsif name.start_with? '%'
          :internal
        elsif name[0].downcase != name[0] || name[0].match?(/\d/)
          # "ConstName" or "[module_id]::[const_path]"
          :const
        else
          :lvar
        end
      end
    end

    class Scope
      BREAK_RESULT = '%break'
      NEXT_RESULT = '%next'
      RETURN_RESULT = '%return'
      PATTERNMATCH_BREAK = '%match'

      attr_reader :parent, :mergeable_changes, :level, :module_nesting

      def self.from_binding(binding, locals) = new(RootScope.new(binding, binding.receiver, locals))

      def initialize(parent, table = {}, trace_ivar: true, trace_lvar: true, self_type: nil, nesting: nil)
        @parent = parent
        @level = parent.level + 1
        @trace_ivar = trace_ivar
        @trace_lvar = trace_lvar
        @module_nesting = nesting ? [nesting, *parent.module_nesting] : parent.module_nesting
        @self_type = self_type
        @terminated = false
        @jump_branches = []
        @mergeable_changes = @table = table.transform_values { [level, _1] }
      end

      def mutable? = true

      def terminated?
        @terminated
      end

      def terminate_with(type, value)
        return if terminated?
        store_jump type, value, @mergeable_changes
        terminate
      end

      def store_jump(type, value, changes)
        return if terminated?
        if has_own?(type)
          changes[type] = [level, value]
          @jump_branches << changes
        elsif @parent.mutable?
          @parent.store_jump(type, value, changes)
        end
      end

      def terminate
        return if terminated?
        @terminated = true
        @table = @mergeable_changes.dup
      end

      def trace?(name)
        return false unless @parent
        type = RootScope.type_by_name(name)
        type == :ivar ? @trace_ivar : type == :lvar ? @trace_lvar : true
      end

      def level_of(name, var_type)
        case var_type
        when :ivar
          return level unless @trace_ivar
        when :gvar
          return 0
        end
        variable_level, = @table[name]
        variable_level || parent.level_of(name, var_type)
      end

      def get_const(nesting, path, key = nil)
        key ||= [nesting.__id__, path].join('::')
        _l, value = @table[key]
        value || @parent.get_const(nesting, path, key)
      end

      def get_cvar(nesting, path, name, key = nil)
        key ||= [name, nesting.__id__, path].join('::')
        _l, value = @table[key]
        value || @parent.get_cvar(nesting, path, name, key)
      end

      def [](name)
        type = RootScope.type_by_name(name)
        if type == :const
          return get_const(nil, nil, name) || Types::NIL if name.include?('::')

          module_nesting.each do |(nesting, path)|
            value = get_const nesting, [*path, name]
            return value if value
          end
          return Types::NIL
        elsif type == :cvar
          return get_cvar(nil, nil, nil, name) if name.include?('::')

          nesting, path = module_nesting.first
          return get_cvar(nesting, path, name)
        end
        level, value = @table[name]
        if level
          value
        elsif trace? name
          @parent[name]
        elsif type == :ivar
          self_instance_variable_get name
        end
      end

      def set_const(nesting, path, value)
        key = [nesting.__id__, path].join('::')
        @table[key] = [0, value]
      end

      def set_cvar(nesting, path, name, value)
        key = [name, nesting.__id__, path].join('::')
        @table[key] = [0, value]
      end

      def []=(name, value)
        type = RootScope.type_by_name(name)
        if type == :const
          if name.include?('::')
            @table[name] = [0, value]
          else
            parent_module, parent_path = module_nesting.first
            set_const parent_module, [*parent_path, name], value
          end
          return
        elsif type == :cvar
          if name.include?('::')
            @table[name] = [0, value]
          else
            parent_module, parent_path = module_nesting.first
            set_cvar parent_module, parent_path, name, value
          end
          return
        end
        variable_level = level_of name, type
        @table[name] = [variable_level, value] if variable_level
      end

      def self_type
        @self_type || @parent.self_type
      end

      def global_variables
        gvar_keys = @table.keys.select do |name|
          RootScope.type_by_name(name) == :gvar
        end
        gvar_keys | @parent.global_variables
      end

      def local_variables
        lvar_keys = @table.keys.select do |name|
          RootScope.type_by_name(name) == :lvar
        end
        lvar_keys |= @parent.local_variables if @trace_lvar
        lvar_keys
      end

      def table_constants
        constants = module_nesting.flat_map do |mod, path|
          prefix = [mod.__id__, *path].join('::') + '::'
          @table.keys.select { _1.start_with? prefix }.map { _1.delete_prefix(prefix).split('::').first }
        end.uniq
        constants |= @parent.table_constants if @parent.mutable?
        constants
      end

      def table_module_constants(mod)
        prefix = "#{mod.__id__}::"
        constants = @table.keys.select { _1.start_with? prefix }.map { _1.delete_prefix(prefix).split('::').first }
        constants |= @parent.table_constants if @parent.mutable?
        constants
      end

      def base_scope
        @parent.mutable? ? @parent.base_scope : @parent
      end

      def table_instance_variables
        ivars = @table.keys.select { RootScope.type_by_name(_1) == :ivar }
        ivars |= @parent.table_instance_variables if @parent.mutable? && @trace_ivar
        ivars
      end

      def instance_variables
        self_singleton_types = self_type.types.grep(Types::SingletonType)
        singleton_classes = self_type.types.grep(Types::InstanceType).map(&:klass).select(&:singleton_class?)
        base_self = base_scope.self_object
        self_instance_variables = singleton_classes.flat_map do |singleton_class|
          if singleton_class.respond_to? :attached_object
            Methods::OBJECT_INSTANCE_VARIABLES_METHOD.bind_call(singleton_class.attached_object).map(&:to_s)
          elsif singleton_class == Methods::OBJECT_SINGLETON_CLASS_METHOD.bind_call(base_self)
            Methods::OBJECT_INSTANCE_VARIABLES_METHOD.bind_call(base_self).map(&:to_s)
          else
            []
          end
        end
        [
          self_singleton_types.flat_map { _1.module_or_class.instance_variables.map(&:to_s) },
          self_instance_variables || [],
          table_instance_variables
        ].inject(:|)
      end

      def self_instance_variable_get(name)
        self_objects = self_type.types.grep(Types::SingletonType).map(&:module_or_class)
        singleton_classes = self_type.types.grep(Types::InstanceType).map(&:klass).select(&:singleton_class?)
        base_self = base_scope.self_object
        singleton_classes.each do |singleton_class|
          if singleton_class.respond_to? :attached_object
            self_objects << singleton_class.attached_object
          elsif singleton_class == base_self.singleton_class
            self_objects << base_self
          end
        end
        types = self_objects.map do |object|
          value = begin
            Methods::OBJECT_INSTANCE_VARIABLE_GET_METHOD.bind_call(object, name)
          rescue NameError
          end
          Types.type_from_object value
        end
        Types::UnionType[*types]
      end

      def table_class_variables
        cvars = @table.keys.filter_map { _1.split('::', 2).first if RootScope.type_by_name(_1) == :cvar }
        cvars |= @parent.table_class_variables if @parent.mutable?
        cvars
      end

      def class_variables
        cvars = table_class_variables
        m, = module_nesting.first
        cvars |= m.class_variables.map(&:to_s) if m.is_a? Module
        cvars
      end

      def constants
        module_nesting.flat_map do |nest,|
          nest.constants
        end.map(&:to_s) | table_constants
      end

      def merge_jumps
        if terminated?
          @terminated = false
          @table = @mergeable_changes
          merge @jump_branches
          @terminated = true
        else
          merge [*@jump_branches, {}]
        end
      end

      def conditional(&block)
        run_branches(block, ->(_s) {}).first || Types::NIL
      end

      def never(&block)
        block.call Scope.new(self, { BREAK_RESULT => nil, NEXT_RESULT => nil, PATTERNMATCH_BREAK => nil, RETURN_RESULT => nil })
      end

      def run_branches(*blocks)
        results = []
        branches = []
        blocks.each do |block|
          scope = Scope.new self
          result = block.call scope
          next if scope.terminated?
          results << result
          branches << scope.mergeable_changes
        end
        terminate if branches.empty?
        merge branches
        results
      end

      def has_own?(name)
        @table.key? name
      end

      def update(child_scope)
        current_level = level
        child_scope.mergeable_changes.each do |name, (level, value)|
          self[name] = value if level <= current_level
        end
      end

      protected

      def merge(branches)
        current_level = level
        merge = {}
        branches.each do |changes|
          changes.each do |name, (level, value)|
            next if current_level < level
            (merge[name] ||= []) << value
          end
        end
        merge.each do |name, values|
          values << self[name] unless values.size == branches.size
          values.compact!
          self[name] = Types::UnionType[*values.compact] unless values.empty?
        end
      end

module_own_constant?(mod, name) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/scope.rb, line 30
    def module_own_constant?(mod, name)
      set = (@owned_constants_cache[mod] ||= Set.new(mod.constants.map(&:to_s)))
      set.include? name
    end

mutable?() click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/scope.rb, line 28
      def mutable?() = false

      def module_own_constant?(mod, name)
        set = (@owned_constants_cache[mod] ||= Set.new(mod.constants.map(&:to_s)))
        set.include? name
      end

      def get_const(nesting, path, _key = nil)
        return unless nesting

        result = path.reduce nesting do |mod, name|
          return nil unless mod.is_a?(Module) && module_own_constant?(mod, name)
          mod.const_get name
        end
        Types.type_from_object result
      end

      def get_cvar(nesting, path, name, _key = nil)
        return Types::NIL unless nesting

        result = path.reduce nesting do |mod, n|
          return Types::NIL unless mod.is_a?(Module) && module_own_constant?(mod, n)
          mod.const_get n
        end
        value = result.class_variable_get name if result.is_a?(Module) && name.size >= 3 && result.class_variable_defined?(name)
        Types.type_from_object value
      end

      def [](name)
        @cache[name] ||= (
          value = case RootScope.type_by_name name
          when :ivar
            begin
              Methods::OBJECT_INSTANCE_VARIABLE_GET_METHOD.bind_call(@self_object, name)
            rescue NameError
            end
          when :lvar
            begin
              @binding.local_variable_get(name)
            rescue NameError
            end
          when :gvar
            @binding.eval name if @global_variables.include? name
          end
          Types.type_from_object(value)
        )
      end

      def self_type
        Types.type_from_object @self_object
      end

      def local_variables() = @local_variables.to_a

      def global_variables() = @global_variables.to_a

      def self.type_by_name(name)
        if name.start_with? '@@'
          # "@@cvar" or "@@cvar::[module_id]::[module_path]"
          :cvar
        elsif name.start_with? '@'
          :ivar
        elsif name.start_with? '$'
          :gvar
        elsif name.start_with? '%'
          :internal
        elsif name[0].downcase != name[0] || name[0].match?(/\d/)
          # "ConstName" or "[module_id]::[const_path]"
          :const
        else
          :lvar
        end
      end
    end

    class Scope
      BREAK_RESULT = '%break'
      NEXT_RESULT = '%next'
      RETURN_RESULT = '%return'
      PATTERNMATCH_BREAK = '%match'

      attr_reader :parent, :mergeable_changes, :level, :module_nesting

      def self.from_binding(binding, locals) = new(RootScope.new(binding, binding.receiver, locals))

      def initialize(parent, table = {}, trace_ivar: true, trace_lvar: true, self_type: nil, nesting: nil)
        @parent = parent
        @level = parent.level + 1
        @trace_ivar = trace_ivar
        @trace_lvar = trace_lvar
        @module_nesting = nesting ? [nesting, *parent.module_nesting] : parent.module_nesting
        @self_type = self_type
        @terminated = false
        @jump_branches = []
        @mergeable_changes = @table = table.transform_values { [level, _1] }
      end

      def mutable? = true

      def terminated?
        @terminated
      end

      def terminate_with(type, value)
        return if terminated?
        store_jump type, value, @mergeable_changes
        terminate
      end

      def store_jump(type, value, changes)
        return if terminated?
        if has_own?(type)
          changes[type] = [level, value]
          @jump_branches << changes
        elsif @parent.mutable?
          @parent.store_jump(type, value, changes)
        end
      end

      def terminate
        return if terminated?
        @terminated = true
        @table = @mergeable_changes.dup
      end

      def trace?(name)
        return false unless @parent
        type = RootScope.type_by_name(name)
        type == :ivar ? @trace_ivar : type == :lvar ? @trace_lvar : true
      end

      def level_of(name, var_type)
        case var_type
        when :ivar
          return level unless @trace_ivar
        when :gvar
          return 0
        end
        variable_level, = @table[name]
        variable_level || parent.level_of(name, var_type)
      end

      def get_const(nesting, path, key = nil)
        key ||= [nesting.__id__, path].join('::')
        _l, value = @table[key]
        value || @parent.get_const(nesting, path, key)
      end

      def get_cvar(nesting, path, name, key = nil)
        key ||= [name, nesting.__id__, path].join('::')
        _l, value = @table[key]
        value || @parent.get_cvar(nesting, path, name, key)
      end

      def [](name)
        type = RootScope.type_by_name(name)
        if type == :const
          return get_const(nil, nil, name) || Types::NIL if name.include?('::')

          module_nesting.each do |(nesting, path)|
            value = get_const nesting, [*path, name]
            return value if value
          end
          return Types::NIL
        elsif type == :cvar
          return get_cvar(nil, nil, nil, name) if name.include?('::')

          nesting, path = module_nesting.first
          return get_cvar(nesting, path, name)
        end
        level, value = @table[name]
        if level
          value
        elsif trace? name
          @parent[name]
        elsif type == :ivar
          self_instance_variable_get name
        end
      end

      def set_const(nesting, path, value)
        key = [nesting.__id__, path].join('::')
        @table[key] = [0, value]
      end

      def set_cvar(nesting, path, name, value)
        key = [name, nesting.__id__, path].join('::')
        @table[key] = [0, value]
      end

      def []=(name, value)
        type = RootScope.type_by_name(name)
        if type == :const
          if name.include?('::')
            @table[name] = [0, value]
          else
            parent_module, parent_path = module_nesting.first
            set_const parent_module, [*parent_path, name], value
          end
          return
        elsif type == :cvar
          if name.include?('::')
            @table[name] = [0, value]
          else
            parent_module, parent_path = module_nesting.first
            set_cvar parent_module, parent_path, name, value
          end
          return
        end
        variable_level = level_of name, type
        @table[name] = [variable_level, value] if variable_level
      end

      def self_type
        @self_type || @parent.self_type
      end

      def global_variables
        gvar_keys = @table.keys.select do |name|
          RootScope.type_by_name(name) == :gvar
        end
        gvar_keys | @parent.global_variables
      end

      def local_variables
        lvar_keys = @table.keys.select do |name|
          RootScope.type_by_name(name) == :lvar
        end
        lvar_keys |= @parent.local_variables if @trace_lvar
        lvar_keys
      end

      def table_constants
        constants = module_nesting.flat_map do |mod, path|
          prefix = [mod.__id__, *path].join('::') + '::'
          @table.keys.select { _1.start_with? prefix }.map { _1.delete_prefix(prefix).split('::').first }
        end.uniq
        constants |= @parent.table_constants if @parent.mutable?
        constants
      end

      def table_module_constants(mod)
        prefix = "#{mod.__id__}::"
        constants = @table.keys.select { _1.start_with? prefix }.map { _1.delete_prefix(prefix).split('::').first }
        constants |= @parent.table_constants if @parent.mutable?
        constants
      end

      def base_scope
        @parent.mutable? ? @parent.base_scope : @parent
      end

      def table_instance_variables
        ivars = @table.keys.select { RootScope.type_by_name(_1) == :ivar }
        ivars |= @parent.table_instance_variables if @parent.mutable? && @trace_ivar
        ivars
      end

      def instance_variables
        self_singleton_types = self_type.types.grep(Types::SingletonType)
        singleton_classes = self_type.types.grep(Types::InstanceType).map(&:klass).select(&:singleton_class?)
        base_self = base_scope.self_object
        self_instance_variables = singleton_classes.flat_map do |singleton_class|
          if singleton_class.respond_to? :attached_object
            Methods::OBJECT_INSTANCE_VARIABLES_METHOD.bind_call(singleton_class.attached_object).map(&:to_s)
          elsif singleton_class == Methods::OBJECT_SINGLETON_CLASS_METHOD.bind_call(base_self)
            Methods::OBJECT_INSTANCE_VARIABLES_METHOD.bind_call(base_self).map(&:to_s)
          else
            []
          end
        end
        [
          self_singleton_types.flat_map { _1.module_or_class.instance_variables.map(&:to_s) },
          self_instance_variables || [],
          table_instance_variables
        ].inject(:|)
      end

      def self_instance_variable_get(name)
        self_objects = self_type.types.grep(Types::SingletonType).map(&:module_or_class)
        singleton_classes = self_type.types.grep(Types::InstanceType).map(&:klass).select(&:singleton_class?)
        base_self = base_scope.self_object
        singleton_classes.each do |singleton_class|
          if singleton_class.respond_to? :attached_object
            self_objects << singleton_class.attached_object
          elsif singleton_class == base_self.singleton_class
            self_objects << base_self
          end
        end
        types = self_objects.map do |object|
          value = begin
            Methods::OBJECT_INSTANCE_VARIABLE_GET_METHOD.bind_call(object, name)
          rescue NameError
          end
          Types.type_from_object value
        end
        Types::UnionType[*types]
      end

      def table_class_variables
        cvars = @table.keys.filter_map { _1.split('::', 2).first if RootScope.type_by_name(_1) == :cvar }
        cvars |= @parent.table_class_variables if @parent.mutable?
        cvars
      end

      def class_variables
        cvars = table_class_variables
        m, = module_nesting.first
        cvars |= m.class_variables.map(&:to_s) if m.is_a? Module
        cvars
      end

      def constants
        module_nesting.flat_map do |nest,|
          nest.constants
        end.map(&:to_s) | table_constants
      end

      def merge_jumps
        if terminated?
          @terminated = false
          @table = @mergeable_changes
          merge @jump_branches
          @terminated = true
        else
          merge [*@jump_branches, {}]
        end
      end

      def conditional(&block)
        run_branches(block, ->(_s) {}).first || Types::NIL
      end

      def never(&block)
        block.call Scope.new(self, { BREAK_RESULT => nil, NEXT_RESULT => nil, PATTERNMATCH_BREAK => nil, RETURN_RESULT => nil })
      end

      def run_branches(*blocks)
        results = []
        branches = []
        blocks.each do |block|
          scope = Scope.new self
          result = block.call scope
          next if scope.terminated?
          results << result
          branches << scope.mergeable_changes
        end
        terminate if branches.empty?
        merge branches
        results
      end

      def has_own?(name)
        @table.key? name
      end

      def update(child_scope)
        current_level = level
        child_scope.mergeable_changes.each do |name, (level, value)|
          self[name] = value if level <= current_level
        end
      end

      protected

      def merge(branches)
        current_level = level
        merge = {}
        branches.each do |changes|
          changes.each do |name, (level, value)|
            next if current_level < level
            (merge[name] ||= []) << value
          end
        end
        merge.each do |name, values|
          values << self[name] unless values.size == branches.size
          values.compact!
          self[name] = Types::UnionType[*values.compact] unless values.empty?
        end

self_type() click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/scope.rb, line 76
    def self_type
      Types.type_from_object @self_object
    end