class IRB::TypeCompletion::RootScope
Attributes
module_nesting[R]
self_object[R]
Public Class Methods
new(binding, self_object, local_variables)
click to toggle source
# File irb/type_completion/scope.rb, line 12 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 irb/type_completion/scope.rb, line 86 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 irb/type_completion/scope.rb, line 58 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 irb/type_completion/scope.rb, line 37 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 irb/type_completion/scope.rb, line 47 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 irb/type_completion/scope.rb, line 84 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 irb/type_completion/scope.rb, line 26 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? end end
level_of(_name, _var_type)
click to toggle source
# File irb/type_completion/scope.rb, line 28 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 end
local_variables()
click to toggle source
# File irb/type_completion/scope.rb, line 82 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 end end
module_own_constant?(mod, name)
click to toggle source
# File irb/type_completion/scope.rb, line 32 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 irb/type_completion/scope.rb, line 30 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 end end
self_type()
click to toggle source
# File irb/type_completion/scope.rb, line 78 def self_type Types.type_from_object @self_object end