From 86d9a6dcb61b47bcacfe98200cb6d47da6bb1134 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Thu, 30 Nov 2023 01:30:08 +0900 Subject: [ruby/irb] Use gem repl_type_completor, remove type_completion implementation (https://github.com/ruby/irb/pull/772) https://github.com/ruby/irb/commit/a4868a5373 --- lib/irb/completion.rb | 21 + lib/irb/context.rb | 22 +- lib/irb/type_completion/completor.rb | 241 ------ lib/irb/type_completion/methods.rb | 13 - lib/irb/type_completion/scope.rb | 412 ----------- lib/irb/type_completion/type_analyzer.rb | 1181 ------------------------------ lib/irb/type_completion/types.rb | 426 ----------- 7 files changed, 30 insertions(+), 2286 deletions(-) delete mode 100644 lib/irb/type_completion/completor.rb delete mode 100644 lib/irb/type_completion/methods.rb delete mode 100644 lib/irb/type_completion/scope.rb delete mode 100644 lib/irb/type_completion/type_analyzer.rb delete mode 100644 lib/irb/type_completion/types.rb (limited to 'lib/irb') diff --git a/lib/irb/completion.rb b/lib/irb/completion.rb index 9b29a787b1..af3b69eb27 100644 --- a/lib/irb/completion.rb +++ b/lib/irb/completion.rb @@ -93,6 +93,27 @@ module IRB end end + class TypeCompletor < BaseCompletor # :nodoc: + def initialize(context) + @context = context + end + + def inspect + ReplTypeCompletor.info + end + + def completion_candidates(preposing, target, _postposing, bind:) + result = ReplTypeCompletor.analyze(preposing + target, binding: bind, filename: @context.irb_path) + return [] unless result + result.completion_candidates.map { target + _1 } + end + + def doc_namespace(preposing, matched, _postposing, bind:) + result = ReplTypeCompletor.analyze(preposing + matched, binding: bind, filename: @context.irb_path) + result&.doc_namespace('') + end + end + class RegexpCompletor < BaseCompletor # :nodoc: using Module.new { refine ::Binding do diff --git a/lib/irb/context.rb b/lib/irb/context.rb index 3442fbf4da..ffbba4e8b1 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -176,26 +176,22 @@ module IRB RegexpCompletor.new end - TYPE_COMPLETION_REQUIRED_PRISM_VERSION = '0.18.0' - private def build_type_completor - unless Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.0.0') && RUBY_ENGINE != 'truffleruby' - warn 'TypeCompletion requires RUBY_VERSION >= 3.0.0' + if RUBY_ENGINE == 'truffleruby' + # Avoid SynatxError. truffleruby does not support endless method definition yet. + warn 'TypeCompletor is not supported on TruffleRuby yet' return end + begin - require 'prism' + require 'repl_type_completor' rescue LoadError => e - warn "TypeCompletion requires Prism: #{e.message}" + warn "TypeCompletor requires `gem repl_type_completor`: #{e.message}" return end - unless Gem::Version.new(Prism::VERSION) >= Gem::Version.new(TYPE_COMPLETION_REQUIRED_PRISM_VERSION) - warn "TypeCompletion requires Prism::VERSION >= #{TYPE_COMPLETION_REQUIRED_PRISM_VERSION}" - return - end - require 'irb/type_completion/completor' - TypeCompletion::Types.preload_in_thread - TypeCompletion::Completor.new + + ReplTypeCompletor.preload_rbs + TypeCompletor.new(self) end def save_history=(val) diff --git a/lib/irb/type_completion/completor.rb b/lib/irb/type_completion/completor.rb deleted file mode 100644 index df1e1c7790..0000000000 --- a/lib/irb/type_completion/completor.rb +++ /dev/null @@ -1,241 +0,0 @@ -# frozen_string_literal: true - -require 'prism' -require 'irb/completion' -require_relative 'type_analyzer' - -module IRB - module TypeCompletion - class Completor < BaseCompletor # :nodoc: - HIDDEN_METHODS = %w[Namespace TypeName] # defined by rbs, should be hidden - - class << self - attr_accessor :last_completion_error - end - - def inspect - name = 'TypeCompletion::Completor' - prism_info = "Prism: #{Prism::VERSION}" - if Types.rbs_builder - "#{name}(#{prism_info}, RBS: #{RBS::VERSION})" - elsif Types.rbs_load_error - "#{name}(#{prism_info}, RBS: #{Types.rbs_load_error.inspect})" - else - "#{name}(#{prism_info}, RBS: loading)" - end - end - - def completion_candidates(preposing, target, _postposing, bind:) - verbose, $VERBOSE = $VERBOSE, nil - @preposing = preposing - code = "#{preposing}#{target}" - @result = analyze code, bind - name, candidates = candidates_from_result(@result) - - all_symbols_pattern = /\A[ -\/:-@\[-`\{-~]*\z/ - candidates.map(&:to_s).select { !_1.match?(all_symbols_pattern) && _1.start_with?(name) }.uniq.sort.map do - target + _1[name.size..] - end - rescue Exception => e - handle_error(e) - [] - ensure - $VERBOSE = verbose - end - - def doc_namespace(preposing, matched, postposing, bind:) - verbose, $VERBOSE = $VERBOSE, nil - name = matched[/[a-zA-Z_0-9]*[!?=]?\z/] - method_doc = -> type do - type = type.types.find { _1.all_methods.include? name.to_sym } - case type - when Types::SingletonType - "#{Types.class_name_of(type.module_or_class)}.#{name}" - when Types::InstanceType - "#{Types.class_name_of(type.klass)}##{name}" - end - end - call_or_const_doc = -> type do - if name =~ /\A[A-Z]/ - type = type.types.grep(Types::SingletonType).find { _1.module_or_class.const_defined?(name) } - type.module_or_class == Object ? name : "#{Types.class_name_of(type.module_or_class)}::#{name}" if type - else - method_doc.call(type) - end - end - - value_doc = -> type do - return unless type - type.types.each do |t| - case t - when Types::SingletonType - return Types.class_name_of(t.module_or_class) - when Types::InstanceType - return Types.class_name_of(t.klass) - end - end - nil - end - - case @result - in [:call_or_const, type, _name, _self_call] - call_or_const_doc.call type - in [:const, type, _name, scope] - if type - call_or_const_doc.call type - else - value_doc.call scope[name] - end - in [:gvar, _name, scope] - value_doc.call scope["$#{name}"] - in [:ivar, _name, scope] - value_doc.call scope["@#{name}"] - in [:cvar, _name, scope] - value_doc.call scope["@@#{name}"] - in [:call, type, _name, _self_call] - method_doc.call type - in [:lvar_or_method, _name, scope] - if scope.local_variables.include?(name) - value_doc.call scope[name] - else - method_doc.call scope.self_type - end - else - end - rescue Exception => e - handle_error(e) - nil - ensure - $VERBOSE = verbose - end - - def candidates_from_result(result) - candidates = case result - in [:require, name] - retrieve_files_to_require_from_load_path - in [:require_relative, name] - retrieve_files_to_require_relative_from_current_dir - in [:call_or_const, type, name, self_call] - ((self_call ? type.all_methods : type.methods).map(&:to_s) - HIDDEN_METHODS) | type.constants - in [:const, type, name, scope] - if type - scope_constants = type.types.flat_map do |t| - scope.table_module_constants(t.module_or_class) if t.is_a?(Types::SingletonType) - end - (scope_constants.compact | type.constants.map(&:to_s)).sort - else - scope.constants.sort | ReservedWords - end - in [:ivar, name, scope] - ivars = scope.instance_variables.sort - name == '@' ? ivars + scope.class_variables.sort : ivars - in [:cvar, name, scope] - scope.class_variables - in [:gvar, name, scope] - scope.global_variables - in [:symbol, name] - Symbol.all_symbols.map { _1.inspect[1..] } - in [:call, type, name, self_call] - (self_call ? type.all_methods : type.methods).map(&:to_s) - HIDDEN_METHODS - in [:lvar_or_method, name, scope] - scope.self_type.all_methods.map(&:to_s) | scope.local_variables | ReservedWords - else - [] - end - [name || '', candidates] - end - - def analyze(code, binding = Object::TOPLEVEL_BINDING) - # Workaround for https://github.com/ruby/prism/issues/1592 - return if code.match?(/%[qQ]\z/) - - ast = Prism.parse(code, scopes: [binding.local_variables]).value - name = code[/(@@|@|\$)?\w*[!?=]?\z/] - *parents, target_node = find_target ast, code.bytesize - name.bytesize - return unless target_node - - calculate_scope = -> { TypeAnalyzer.calculate_target_type_scope(binding, parents, target_node).last } - calculate_type_scope = ->(node) { TypeAnalyzer.calculate_target_type_scope binding, [*parents, target_node], node } - - case target_node - when Prism::StringNode, Prism::InterpolatedStringNode - call_node, args_node = parents.last(2) - return unless call_node.is_a?(Prism::CallNode) && call_node.receiver.nil? - return unless args_node.is_a?(Prism::ArgumentsNode) && args_node.arguments.size == 1 - - case call_node.name - when :require - [:require, name.rstrip] - when :require_relative - [:require_relative, name.rstrip] - end - when Prism::SymbolNode - if parents.last.is_a? Prism::BlockArgumentNode # method(&:target) - receiver_type, _scope = calculate_type_scope.call target_node - [:call, receiver_type, name, false] - else - [:symbol, name] unless name.empty? - end - when Prism::CallNode - return [:lvar_or_method, name, calculate_scope.call] if target_node.receiver.nil? - - self_call = target_node.receiver.is_a? Prism::SelfNode - op = target_node.call_operator - receiver_type, _scope = calculate_type_scope.call target_node.receiver - receiver_type = receiver_type.nonnillable if op == '&.' - [op == '::' ? :call_or_const : :call, receiver_type, name, self_call] - when Prism::LocalVariableReadNode, Prism::LocalVariableTargetNode - [:lvar_or_method, name, calculate_scope.call] - when Prism::ConstantReadNode, Prism::ConstantTargetNode - if parents.last.is_a? Prism::ConstantPathNode - path_node = parents.last - if path_node.parent # A::B - receiver, scope = calculate_type_scope.call(path_node.parent) - [:const, receiver, name, scope] - else # ::A - scope = calculate_scope.call - [:const, Types::SingletonType.new(Object), name, scope] - end - else - [:const, nil, name, calculate_scope.call] - end - when Prism::GlobalVariableReadNode, Prism::GlobalVariableTargetNode - [:gvar, name, calculate_scope.call] - when Prism::InstanceVariableReadNode, Prism::InstanceVariableTargetNode - [:ivar, name, calculate_scope.call] - when Prism::ClassVariableReadNode, Prism::ClassVariableTargetNode - [:cvar, name, calculate_scope.call] - end - end - - def find_target(node, position) - location = ( - case node - when Prism::CallNode - node.message_loc - when Prism::SymbolNode - node.value_loc - when Prism::StringNode - node.content_loc - when Prism::InterpolatedStringNode - node.closing_loc if node.parts.empty? - end - ) - return [node] if location&.start_offset == position - - node.compact_child_nodes.each do |n| - match = find_target(n, position) - next unless match - match.unshift node - return match - end - - [node] if node.location.start_offset == position - end - - def handle_error(e) - Completor.last_completion_error = e - end - end - end -end diff --git a/lib/irb/type_completion/methods.rb b/lib/irb/type_completion/methods.rb deleted file mode 100644 index 8a88b6d0f9..0000000000 --- a/lib/irb/type_completion/methods.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -module IRB - module TypeCompletion - module Methods - OBJECT_SINGLETON_CLASS_METHOD = Object.instance_method(:singleton_class) - OBJECT_INSTANCE_VARIABLES_METHOD = Object.instance_method(:instance_variables) - OBJECT_INSTANCE_VARIABLE_GET_METHOD = Object.instance_method(:instance_variable_get) - OBJECT_CLASS_METHOD = Object.instance_method(:class) - MODULE_NAME_METHOD = Module.instance_method(:name) - end - end -end diff --git a/lib/irb/type_completion/scope.rb b/lib/irb/type_completion/scope.rb deleted file mode 100644 index 5a58a0ed65..0000000000 --- a/lib/irb/type_completion/scope.rb +++ /dev/null @@ -1,412 +0,0 @@ -# frozen_string_literal: true - -require 'set' -require_relative 'types' - -module IRB - module TypeCompletion - - class RootScope - attr_reader :module_nesting, :self_object - - 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 - - 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 - end - end -end diff --git a/lib/irb/type_completion/type_analyzer.rb b/lib/irb/type_completion/type_analyzer.rb deleted file mode 100644 index 344924c9fc..0000000000 --- a/lib/irb/type_completion/type_analyzer.rb +++ /dev/null @@ -1,1181 +0,0 @@ -# frozen_string_literal: true - -require 'set' -require_relative 'types' -require_relative 'scope' -require 'prism' - -module IRB - module TypeCompletion - class TypeAnalyzer - class DigTarget - def initialize(parents, receiver, &block) - @dig_ids = parents.to_h { [_1.__id__, true] } - @target_id = receiver.__id__ - @block = block - end - - def dig?(node) = @dig_ids[node.__id__] - def target?(node) = @target_id == node.__id__ - def resolve(type, scope) - @block.call type, scope - end - end - - OBJECT_METHODS = { - to_s: Types::STRING, - to_str: Types::STRING, - to_a: Types::ARRAY, - to_ary: Types::ARRAY, - to_h: Types::HASH, - to_hash: Types::HASH, - to_i: Types::INTEGER, - to_int: Types::INTEGER, - to_f: Types::FLOAT, - to_c: Types::COMPLEX, - to_r: Types::RATIONAL - } - - def initialize(dig_targets) - @dig_targets = dig_targets - end - - def evaluate(node, scope) - method = "evaluate_#{node.type}" - if respond_to? method - result = send method, node, scope - else - result = Types::NIL - end - @dig_targets.resolve result, scope if @dig_targets.target? node - result - end - - def evaluate_program_node(node, scope) - evaluate node.statements, scope - end - - def evaluate_statements_node(node, scope) - if node.body.empty? - Types::NIL - else - node.body.map { evaluate _1, scope }.last - end - end - - def evaluate_def_node(node, scope) - if node.receiver - self_type = evaluate node.receiver, scope - else - current_self_types = scope.self_type.types - self_types = current_self_types.map do |type| - if type.is_a?(Types::SingletonType) && type.module_or_class.is_a?(Class) - Types::InstanceType.new type.module_or_class - else - type - end - end - self_type = Types::UnionType[*self_types] - end - if @dig_targets.dig?(node.body) || @dig_targets.dig?(node.parameters) - params_table = node.locals.to_h { [_1.to_s, Types::NIL] } - method_scope = Scope.new( - scope, - { **params_table, Scope::BREAK_RESULT => nil, Scope::NEXT_RESULT => nil, Scope::RETURN_RESULT => nil }, - self_type: self_type, - trace_lvar: false, - trace_ivar: false - ) - if node.parameters - # node.parameters is Prism::ParametersNode - assign_parameters node.parameters, method_scope, [], {} - end - - if @dig_targets.dig?(node.body) - method_scope.conditional do |s| - evaluate node.body, s - end - end - method_scope.merge_jumps - scope.update method_scope - end - Types::SYMBOL - end - - def evaluate_integer_node(_node, _scope) = Types::INTEGER - - def evaluate_float_node(_node, _scope) = Types::FLOAT - - def evaluate_rational_node(_node, _scope) = Types::RATIONAL - - def evaluate_imaginary_node(_node, _scope) = Types::COMPLEX - - def evaluate_string_node(_node, _scope) = Types::STRING - - def evaluate_x_string_node(_node, _scope) - Types::UnionType[Types::STRING, Types::NIL] - end - - def evaluate_symbol_node(_node, _scope) = Types::SYMBOL - - def evaluate_regular_expression_node(_node, _scope) = Types::REGEXP - - def evaluate_string_concat_node(node, scope) - evaluate node.left, scope - evaluate node.right, scope - Types::STRING - end - - def evaluate_interpolated_string_node(node, scope) - node.parts.each { evaluate _1, scope } - Types::STRING - end - - def evaluate_interpolated_x_string_node(node, scope) - node.parts.each { evaluate _1, scope } - Types::STRING - end - - def evaluate_interpolated_symbol_node(node, scope) - node.parts.each { evaluate _1, scope } - Types::SYMBOL - end - - def evaluate_interpolated_regular_expression_node(node, scope) - node.parts.each { evaluate _1, scope } - Types::REGEXP - end - - def evaluate_embedded_statements_node(node, scope) - node.statements ? evaluate(node.statements, scope) : Types::NIL - Types::STRING - end - - def evaluate_embedded_variable_node(node, scope) - evaluate node.variable, scope - Types::STRING - end - - def evaluate_array_node(node, scope) - Types.array_of evaluate_list_splat_items(node.elements, scope) - end - - def evaluate_hash_node(node, scope) = evaluate_hash(node, scope) - def evaluate_keyword_hash_node(node, scope) = evaluate_hash(node, scope) - def evaluate_hash(node, scope) - keys = [] - values = [] - node.elements.each do |assoc| - case assoc - when Prism::AssocNode - keys << evaluate(assoc.key, scope) - values << evaluate(assoc.value, scope) - when Prism::AssocSplatNode - next unless assoc.value # def f(**); {**} - - hash = evaluate assoc.value, scope - unless hash.is_a?(Types::InstanceType) && hash.klass == Hash - hash = method_call hash, :to_hash, [], nil, nil, scope - end - if hash.is_a?(Types::InstanceType) && hash.klass == Hash - keys << hash.params[:K] if hash.params[:K] - values << hash.params[:V] if hash.params[:V] - end - end - end - if keys.empty? && values.empty? - Types::InstanceType.new Hash - else - Types::InstanceType.new Hash, K: Types::UnionType[*keys], V: Types::UnionType[*values] - end - end - - def evaluate_parentheses_node(node, scope) - node.body ? evaluate(node.body, scope) : Types::NIL - end - - def evaluate_constant_path_node(node, scope) - type, = evaluate_constant_node_info node, scope - type - end - - def evaluate_self_node(_node, scope) = scope.self_type - - def evaluate_true_node(_node, _scope) = Types::TRUE - - def evaluate_false_node(_node, _scope) = Types::FALSE - - def evaluate_nil_node(_node, _scope) = Types::NIL - - def evaluate_source_file_node(_node, _scope) = Types::STRING - - def evaluate_source_line_node(_node, _scope) = Types::INTEGER - - def evaluate_source_encoding_node(_node, _scope) = Types::InstanceType.new(Encoding) - - def evaluate_numbered_reference_read_node(_node, _scope) - Types::UnionType[Types::STRING, Types::NIL] - end - - def evaluate_back_reference_read_node(_node, _scope) - Types::UnionType[Types::STRING, Types::NIL] - end - - def evaluate_reference_read(node, scope) - scope[node.name.to_s] || Types::NIL - end - alias evaluate_constant_read_node evaluate_reference_read - alias evaluate_global_variable_read_node evaluate_reference_read - alias evaluate_local_variable_read_node evaluate_reference_read - alias evaluate_class_variable_read_node evaluate_reference_read - alias evaluate_instance_variable_read_node evaluate_reference_read - - - def evaluate_call_node(node, scope) - is_field_assign = node.name.match?(/[^<>=!\]]=\z/) || (node.name == :[]= && !node.call_operator) - receiver_type = node.receiver ? evaluate(node.receiver, scope) : scope.self_type - evaluate_method = lambda do |scope| - args_types, kwargs_types, block_sym_node, has_block = evaluate_call_node_arguments node, scope - - if block_sym_node - block_sym = block_sym_node.value - if @dig_targets.target? block_sym_node - # method(args, &:completion_target) - call_block_proc = ->(block_args, _self_type) do - block_receiver = block_args.first || Types::OBJECT - @dig_targets.resolve block_receiver, scope - Types::OBJECT - end - else - call_block_proc = ->(block_args, _self_type) do - block_receiver, *rest = block_args - block_receiver ? method_call(block_receiver || Types::OBJECT, block_sym, rest, nil, nil, scope) : Types::OBJECT - end - end - elsif node.block.is_a? Prism::BlockNode - call_block_proc = ->(block_args, block_self_type) do - scope.conditional do |s| - numbered_parameters = node.block.locals.grep(/\A_[1-9]/).map(&:to_s) - params_table = node.block.locals.to_h { [_1.to_s, Types::NIL] } - table = { **params_table, Scope::BREAK_RESULT => nil, Scope::NEXT_RESULT => nil } - block_scope = Scope.new s, table, self_type: block_self_type, trace_ivar: !block_self_type - # TODO kwargs - if node.block.parameters&.parameters - # node.block.parameters is Prism::BlockParametersNode - assign_parameters node.block.parameters.parameters, block_scope, block_args, {} - elsif !numbered_parameters.empty? - assign_numbered_parameters numbered_parameters, block_scope, block_args, {} - end - result = node.block.body ? evaluate(node.block.body, block_scope) : Types::NIL - block_scope.merge_jumps - s.update block_scope - nexts = block_scope[Scope::NEXT_RESULT] - breaks = block_scope[Scope::BREAK_RESULT] - if block_scope.terminated? - [Types::UnionType[*nexts], breaks] - else - [Types::UnionType[result, *nexts], breaks] - end - end - end - elsif has_block - call_block_proc = ->(_block_args, _self_type) { Types::OBJECT } - end - result = method_call receiver_type, node.name, args_types, kwargs_types, call_block_proc, scope - if is_field_assign - args_types.last || Types::NIL - else - result - end - end - if node.call_operator == '&.' - result = scope.conditional { evaluate_method.call _1 } - if receiver_type.nillable? - Types::UnionType[result, Types::NIL] - else - result - end - else - evaluate_method.call scope - end - end - - def evaluate_and_node(node, scope) = evaluate_and_or(node, scope, and_op: true) - def evaluate_or_node(node, scope) = evaluate_and_or(node, scope, and_op: false) - def evaluate_and_or(node, scope, and_op:) - left = evaluate node.left, scope - right = scope.conditional { evaluate node.right, _1 } - if and_op - Types::UnionType[right, Types::NIL, Types::FALSE] - else - Types::UnionType[left, right] - end - end - - def evaluate_call_operator_write_node(node, scope) = evaluate_call_write(node, scope, :operator, node.write_name) - def evaluate_call_and_write_node(node, scope) = evaluate_call_write(node, scope, :and, node.write_name) - def evaluate_call_or_write_node(node, scope) = evaluate_call_write(node, scope, :or, node.write_name) - def evaluate_index_operator_write_node(node, scope) = evaluate_call_write(node, scope, :operator, :[]=) - def evaluate_index_and_write_node(node, scope) = evaluate_call_write(node, scope, :and, :[]=) - def evaluate_index_or_write_node(node, scope) = evaluate_call_write(node, scope, :or, :[]=) - def evaluate_call_write(node, scope, operator, write_name) - receiver_type = evaluate node.receiver, scope - if write_name == :[]= - args_types, kwargs_types, block_sym_node, has_block = evaluate_call_node_arguments node, scope - else - args_types = [] - end - if block_sym_node - block_sym = block_sym_node.value - call_block_proc = ->(block_args, _self_type) do - block_receiver, *rest = block_args - block_receiver ? method_call(block_receiver || Types::OBJECT, block_sym, rest, nil, nil, scope) : Types::OBJECT - end - elsif has_block - call_block_proc = ->(_block_args, _self_type) { Types::OBJECT } - end - method = write_name.to_s.delete_suffix('=') - left = method_call receiver_type, method, args_types, kwargs_types, call_block_proc, scope - case operator - when :and - right = scope.conditional { evaluate node.value, _1 } - Types::UnionType[right, Types::NIL, Types::FALSE] - when :or - right = scope.conditional { evaluate node.value, _1 } - Types::UnionType[left, right] - else - right = evaluate node.value, scope - method_call left, node.operator, [right], nil, nil, scope, name_match: false - end - end - - def evaluate_variable_operator_write(node, scope) - left = scope[node.name.to_s] || Types::OBJECT - right = evaluate node.value, scope - scope[node.name.to_s] = method_call left, node.operator, [right], nil, nil, scope, name_match: false - end - alias evaluate_global_variable_operator_write_node evaluate_variable_operator_write - alias evaluate_local_variable_operator_write_node evaluate_variable_operator_write - alias evaluate_class_variable_operator_write_node evaluate_variable_operator_write - alias evaluate_instance_variable_operator_write_node evaluate_variable_operator_write - - def evaluate_variable_and_write(node, scope) - right = scope.conditional { evaluate node.value, scope } - scope[node.name.to_s] = Types::UnionType[right, Types::NIL, Types::FALSE] - end - alias evaluate_global_variable_and_write_node evaluate_variable_and_write - alias evaluate_local_variable_and_write_node evaluate_variable_and_write - alias evaluate_class_variable_and_write_node evaluate_variable_and_write - alias evaluate_instance_variable_and_write_node evaluate_variable_and_write - - def evaluate_variable_or_write(node, scope) - left = scope[node.name.to_s] || Types::OBJECT - right = scope.conditional { evaluate node.value, scope } - scope[node.name.to_s] = Types::UnionType[left, right] - end - alias evaluate_global_variable_or_write_node evaluate_variable_or_write - alias evaluate_local_variable_or_write_node evaluate_variable_or_write - alias evaluate_class_variable_or_write_node evaluate_variable_or_write - alias evaluate_instance_variable_or_write_node evaluate_variable_or_write - - def evaluate_constant_operator_write_node(node, scope) - left = scope[node.name.to_s] || Types::OBJECT - right = evaluate node.value, scope - scope[node.name.to_s] = method_call left, node.operator, [right], nil, nil, scope, name_match: false - end - - def evaluate_constant_and_write_node(node, scope) - right = scope.conditional { evaluate node.value, scope } - scope[node.name.to_s] = Types::UnionType[right, Types::NIL, Types::FALSE] - end - - def evaluate_constant_or_write_node(node, scope) - left = scope[node.name.to_s] || Types::OBJECT - right = scope.conditional { evaluate node.value, scope } - scope[node.name.to_s] = Types::UnionType[left, right] - end - - def evaluate_constant_path_operator_write_node(node, scope) - left, receiver, _parent_module, name = evaluate_constant_node_info node.target, scope - right = evaluate node.value, scope - value = method_call left, node.operator, [right], nil, nil, scope, name_match: false - const_path_write receiver, name, value, scope - value - end - - def evaluate_constant_path_and_write_node(node, scope) - _left, receiver, _parent_module, name = evaluate_constant_node_info node.target, scope - right = scope.conditional { evaluate node.value, scope } - value = Types::UnionType[right, Types::NIL, Types::FALSE] - const_path_write receiver, name, value, scope - value - end - - def evaluate_constant_path_or_write_node(node, scope) - left, receiver, _parent_module, name = evaluate_constant_node_info node.target, scope - right = scope.conditional { evaluate node.value, scope } - value = Types::UnionType[left, right] - const_path_write receiver, name, value, scope - value - end - - def evaluate_constant_path_write_node(node, scope) - receiver = evaluate node.target.parent, scope if node.target.parent - value = evaluate node.value, scope - const_path_write receiver, node.target.child.name.to_s, value, scope - value - end - - def evaluate_lambda_node(node, scope) - local_table = node.locals.to_h { [_1.to_s, Types::OBJECT] } - block_scope = Scope.new scope, { **local_table, Scope::BREAK_RESULT => nil, Scope::NEXT_RESULT => nil, Scope::RETURN_RESULT => nil } - block_scope.conditional do |s| - assign_parameters node.parameters.parameters, s, [], {} if node.parameters&.parameters - evaluate node.body, s if node.body - end - block_scope.merge_jumps - scope.update block_scope - Types::PROC - end - - def evaluate_reference_write(node, scope) - scope[node.name.to_s] = evaluate node.value, scope - end - alias evaluate_constant_write_node evaluate_reference_write - alias evaluate_global_variable_write_node evaluate_reference_write - alias evaluate_local_variable_write_node evaluate_reference_write - alias evaluate_class_variable_write_node evaluate_reference_write - alias evaluate_instance_variable_write_node evaluate_reference_write - - def evaluate_multi_write_node(node, scope) - evaluated_receivers = {} - evaluate_multi_write_receiver node, scope, evaluated_receivers - value = ( - if node.value.is_a? Prism::ArrayNode - if node.value.elements.any?(Prism::SplatNode) - evaluate node.value, scope - else - node.value.elements.map do |n| - evaluate n, scope - end - end - elsif node.value - evaluate node.value, scope - else - Types::NIL - end - ) - evaluate_multi_write node, value, scope, evaluated_receivers - value.is_a?(Array) ? Types.array_of(*value) : value - end - - def evaluate_if_node(node, scope) = evaluate_if_unless(node, scope) - def evaluate_unless_node(node, scope) = evaluate_if_unless(node, scope) - def evaluate_if_unless(node, scope) - evaluate node.predicate, scope - Types::UnionType[*scope.run_branches( - -> { node.statements ? evaluate(node.statements, _1) : Types::NIL }, - -> { node.consequent ? evaluate(node.consequent, _1) : Types::NIL } - )] - end - - def evaluate_else_node(node, scope) - node.statements ? evaluate(node.statements, scope) : Types::NIL - end - - def evaluate_while_until(node, scope) - inner_scope = Scope.new scope, { Scope::BREAK_RESULT => nil } - evaluate node.predicate, inner_scope - if node.statements - inner_scope.conditional do |s| - evaluate node.statements, s - end - end - inner_scope.merge_jumps - scope.update inner_scope - breaks = inner_scope[Scope::BREAK_RESULT] - breaks ? Types::UnionType[breaks, Types::NIL] : Types::NIL - end - alias evaluate_while_node evaluate_while_until - alias evaluate_until_node evaluate_while_until - - def evaluate_break_node(node, scope) = evaluate_jump(node, scope, :break) - def evaluate_next_node(node, scope) = evaluate_jump(node, scope, :next) - def evaluate_return_node(node, scope) = evaluate_jump(node, scope, :return) - def evaluate_jump(node, scope, mode) - internal_key = ( - case mode - when :break - Scope::BREAK_RESULT - when :next - Scope::NEXT_RESULT - when :return - Scope::RETURN_RESULT - end - ) - jump_value = ( - arguments = node.arguments&.arguments - if arguments.nil? || arguments.empty? - Types::NIL - elsif arguments.size == 1 && !arguments.first.is_a?(Prism::SplatNode) - evaluate arguments.first, scope - else - Types.array_of evaluate_list_splat_items(arguments, scope) - end - ) - scope.terminate_with internal_key, jump_value - Types::NIL - end - - def evaluate_yield_node(node, scope) - evaluate_list_splat_items node.arguments.arguments, scope if node.arguments - Types::OBJECT - end - - def evaluate_redo_node(_node, scope) - scope.terminate - Types::NIL - end - - def evaluate_retry_node(_node, scope) - scope.terminate - Types::NIL - end - - def evaluate_forwarding_super_node(_node, _scope) = Types::OBJECT - - def evaluate_super_node(node, scope) - evaluate_list_splat_items node.arguments.arguments, scope if node.arguments - Types::OBJECT - end - - def evaluate_begin_node(node, scope) - return_type = node.statements ? evaluate(node.statements, scope) : Types::NIL - if node.rescue_clause - if node.else_clause - return_types = scope.run_branches( - ->{ evaluate node.rescue_clause, _1 }, - ->{ evaluate node.else_clause, _1 } - ) - else - return_types = [ - return_type, - scope.conditional { evaluate node.rescue_clause, _1 } - ] - end - return_type = Types::UnionType[*return_types] - end - if node.ensure_clause&.statements - # ensure_clause is Prism::EnsureNode - evaluate node.ensure_clause.statements, scope - end - return_type - end - - def evaluate_rescue_node(node, scope) - run_rescue = lambda do |s| - if node.reference - error_classes_type = evaluate_list_splat_items node.exceptions, s - error_types = error_classes_type.types.filter_map do - Types::InstanceType.new _1.module_or_class if _1.is_a?(Types::SingletonType) - end - error_types << Types::InstanceType.new(StandardError) if error_types.empty? - error_type = Types::UnionType[*error_types] - case node.reference - when Prism::LocalVariableTargetNode, Prism::InstanceVariableTargetNode, Prism::ClassVariableTargetNode, Prism::GlobalVariableTargetNode, Prism::ConstantTargetNode - s[node.reference.name.to_s] = error_type - when Prism::CallNode - evaluate node.reference, s - end - end - node.statements ? evaluate(node.statements, s) : Types::NIL - end - if node.consequent # begin; rescue A; rescue B; end - types = scope.run_branches( - run_rescue, - -> { evaluate node.consequent, _1 } - ) - Types::UnionType[*types] - else - run_rescue.call scope - end - end - - def evaluate_rescue_modifier_node(node, scope) - a = evaluate node.expression, scope - b = scope.conditional { evaluate node.rescue_expression, _1 } - Types::UnionType[a, b] - end - - def evaluate_singleton_class_node(node, scope) - klass_types = evaluate(node.expression, scope).types.filter_map do |type| - Types::SingletonType.new type.klass if type.is_a? Types::InstanceType - end - klass_types = [Types::CLASS] if klass_types.empty? - table = node.locals.to_h { [_1.to_s, Types::NIL] } - sclass_scope = Scope.new( - scope, - { **table, Scope::BREAK_RESULT => nil, Scope::NEXT_RESULT => nil, Scope::RETURN_RESULT => nil }, - trace_ivar: false, - trace_lvar: false, - self_type: Types::UnionType[*klass_types] - ) - result = node.body ? evaluate(node.body, sclass_scope) : Types::NIL - scope.update sclass_scope - result - end - - def evaluate_class_node(node, scope) = evaluate_class_module(node, scope, true) - def evaluate_module_node(node, scope) = evaluate_class_module(node, scope, false) - def evaluate_class_module(node, scope, is_class) - unless node.constant_path.is_a?(Prism::ConstantReadNode) || node.constant_path.is_a?(Prism::ConstantPathNode) - # Incomplete class/module `class (statement[cursor_here])::Name; end` - evaluate node.constant_path, scope - return Types::NIL - end - const_type, _receiver, parent_module, name = evaluate_constant_node_info node.constant_path, scope - if is_class - select_class_type = -> { _1.is_a?(Types::SingletonType) && _1.module_or_class.is_a?(Class) } - module_types = const_type.types.select(&select_class_type) - module_types += evaluate(node.superclass, scope).types.select(&select_class_type) if node.superclass - module_types << Types::CLASS if module_types.empty? - else - module_types = const_type.types.select { _1.is_a?(Types::SingletonType) && !_1.module_or_class.is_a?(Class) } - module_types << Types::MODULE if module_types.empty? - end - return Types::NIL unless node.body - - table = node.locals.to_h { [_1.to_s, Types::NIL] } - if !name.empty? && (parent_module.is_a?(Module) || parent_module.nil?) - value = parent_module.const_get name if parent_module&.const_defined? name - unless value - value_type = scope[name] - value = value_type.module_or_class if value_type.is_a? Types::SingletonType - end - - if value.is_a? Module - nesting = [value, []] - else - if parent_module - nesting = [parent_module, [name]] - else - parent_nesting, parent_path = scope.module_nesting.first - nesting = [parent_nesting, parent_path + [name]] - end - nesting_key = [nesting[0].__id__, nesting[1]].join('::') - nesting_value = is_class ? Types::CLASS : Types::MODULE - end - else - # parent_module == :unknown - # TODO: dummy module - end - module_scope = Scope.new( - scope, - { **table, Scope::BREAK_RESULT => nil, Scope::NEXT_RESULT => nil, Scope::RETURN_RESULT => nil }, - trace_ivar: false, - trace_lvar: false, - self_type: Types::UnionType[*module_types], - nesting: nesting - ) - module_scope[nesting_key] = nesting_value if nesting_value - result = evaluate(node.body, module_scope) - scope.update module_scope - result - end - - def evaluate_for_node(node, scope) - node.statements - collection = evaluate node.collection, scope - inner_scope = Scope.new scope, { Scope::BREAK_RESULT => nil } - ary_type = method_call collection, :to_ary, [], nil, nil, nil, name_match: false - element_types = ary_type.types.filter_map do |ary| - ary.params[:Elem] if ary.is_a?(Types::InstanceType) && ary.klass == Array - end - element_type = Types::UnionType[*element_types] - inner_scope.conditional do |s| - evaluate_write node.index, element_type, s, nil - evaluate node.statements, s if node.statements - end - inner_scope.merge_jumps - scope.update inner_scope - breaks = inner_scope[Scope::BREAK_RESULT] - breaks ? Types::UnionType[breaks, collection] : collection - end - - def evaluate_case_node(node, scope) - evaluate(node.predicate, scope) if node.predicate - # TODO - branches = node.conditions.map do |condition| - ->(s) { evaluate_case_when_condition condition, s } - end - if node.consequent - branches << ->(s) { evaluate node.consequent, s } - else - branches << ->(_s) { Types::NIL } - end - Types::UnionType[*scope.run_branches(*branches)] - end - - def evaluate_case_match_node(node, scope) - target = evaluate(node.predicate, scope) - # TODO - branches = node.conditions.map do |condition| - ->(s) { evaluate_case_in_condition target, condition, s } - end - if node.consequent - branches << ->(s) { evaluate node.consequent, s } - end - Types::UnionType[*scope.run_branches(*branches)] - end - - def evaluate_match_required_node(node, scope) - value_type = evaluate node.value, scope - evaluate_match_pattern value_type, node.pattern, scope - Types::NIL # void value - end - - def evaluate_match_predicate_node(node, scope) - value_type = evaluate node.value, scope - scope.conditional { evaluate_match_pattern value_type, node.pattern, _1 } - Types::BOOLEAN - end - - def evaluate_range_node(node, scope) - beg_type = evaluate node.left, scope if node.left - end_type = evaluate node.right, scope if node.right - elem = (Types::UnionType[*[beg_type, end_type].compact]).nonnillable - Types::InstanceType.new Range, Elem: elem - end - - def evaluate_defined_node(node, scope) - scope.conditional { evaluate node.value, _1 } - Types::UnionType[Types::STRING, Types::NIL] - end - - def evaluate_flip_flop_node(node, scope) - scope.conditional { evaluate node.left, _1 } if node.left - scope.conditional { evaluate node.right, _1 } if node.right - Types::BOOLEAN - end - - def evaluate_multi_target_node(node, scope) - # Raw MultiTargetNode, incomplete code like `a,b`, `*a`. - evaluate_multi_write_receiver node, scope, nil - Types::NIL - end - - def evaluate_splat_node(node, scope) - # Raw SplatNode, incomplete code like `*a.` - evaluate_multi_write_receiver node.expression, scope, nil if node.expression - Types::NIL - end - - def evaluate_implicit_node(node, scope) - evaluate node.value, scope - end - - def evaluate_match_write_node(node, scope) - # /(?)(?)/ =~ string - evaluate node.call, scope - locals = node.targets.map(&:name) - locals.each { scope[_1.to_s] = Types::UnionType[Types::STRING, Types::NIL] } - Types::BOOLEAN - end - - def evaluate_match_last_line_node(_node, _scope) - Types::BOOLEAN - end - - def evaluate_interpolated_match_last_line_node(node, scope) - node.parts.each { evaluate _1, scope } - Types::BOOLEAN - end - - def evaluate_pre_execution_node(node, scope) - node.statements ? evaluate(node.statements, scope) : Types::NIL - end - - def evaluate_post_execution_node(node, scope) - node.statements && @dig_targets.dig?(node.statements) ? evaluate(node.statements, scope) : Types::NIL - end - - def evaluate_alias_method_node(_node, _scope) = Types::NIL - def evaluate_alias_global_variable_node(_node, _scope) = Types::NIL - def evaluate_undef_node(_node, _scope) = Types::NIL - def evaluate_missing_node(_node, _scope) = Types::NIL - - def evaluate_call_node_arguments(call_node, scope) - # call_node.arguments is Prism::ArgumentsNode - arguments = call_node.arguments&.arguments&.dup || [] - block_arg = call_node.block.expression if call_node.block.is_a?(Prism::BlockArgumentNode) - kwargs = arguments.pop.elements if arguments.last.is_a?(Prism::KeywordHashNode) - args_types = arguments.map do |arg| - case arg - when Prism::ForwardingArgumentsNode - # `f(a, ...)` treat like splat - nil - when Prism::SplatNode - evaluate arg.expression, scope if arg.expression - nil # TODO: splat - else - evaluate arg, scope - end - end - if kwargs - kwargs_types = kwargs.map do |arg| - case arg - when Prism::AssocNode - if arg.key.is_a?(Prism::SymbolNode) - [arg.key.value, evaluate(arg.value, scope)] - else - evaluate arg.key, scope - evaluate arg.value, scope - nil - end - when Prism::AssocSplatNode - evaluate arg.value, scope if arg.value - nil - end - end.compact.to_h - end - if block_arg.is_a? Prism::SymbolNode - block_sym_node = block_arg - elsif block_arg - evaluate block_arg, scope - end - [args_types, kwargs_types, block_sym_node, !!block_arg] - end - - def const_path_write(receiver, name, value, scope) - if receiver # receiver::A = value - singleton_type = receiver.types.find { _1.is_a? Types::SingletonType } - scope.set_const singleton_type.module_or_class, name, value if singleton_type - else # ::A = value - scope.set_const Object, name, value - end - end - - def assign_required_parameter(node, value, scope) - case node - when Prism::RequiredParameterNode - scope[node.name.to_s] = value || Types::OBJECT - when Prism::MultiTargetNode - parameters = [*node.lefts, *node.rest, *node.rights] - values = value ? sized_splat(value, :to_ary, parameters.size) : [] - parameters.zip values do |n, v| - assign_required_parameter n, v, scope - end - when Prism::SplatNode - splat_value = value ? Types.array_of(value) : Types::ARRAY - assign_required_parameter node.expression, splat_value, scope if node.expression - end - end - - def evaluate_constant_node_info(node, scope) - case node - when Prism::ConstantPathNode - name = node.child.name.to_s - if node.parent - receiver = evaluate node.parent, scope - if receiver.is_a? Types::SingletonType - parent_module = receiver.module_or_class - end - else - parent_module = Object - end - if parent_module - type = scope.get_const(parent_module, [name]) || Types::NIL - else - parent_module = :unknown - type = Types::NIL - end - when Prism::ConstantReadNode - name = node.name.to_s - type = scope[name] - end - @dig_targets.resolve type, scope if @dig_targets.target? node - [type, receiver, parent_module, name] - end - - - def assign_parameters(node, scope, args, kwargs) - args = args.dup - kwargs = kwargs.dup - size = node.requireds.size + node.optionals.size + (node.rest ? 1 : 0) + node.posts.size - args = sized_splat(args.first, :to_ary, size) if size >= 2 && args.size == 1 - reqs = args.shift node.requireds.size - if node.rest - # node.rest is Prism::RestParameterNode - posts = [] - opts = args.shift node.optionals.size - rest = args - else - posts = args.pop node.posts.size - opts = args - rest = [] - end - node.requireds.zip reqs do |n, v| - assign_required_parameter n, v, scope - end - node.optionals.zip opts do |n, v| - # n is Prism::OptionalParameterNode - values = [v] - values << evaluate(n.value, scope) if n.value - scope[n.name.to_s] = Types::UnionType[*values.compact] - end - node.posts.zip posts do |n, v| - assign_required_parameter n, v, scope - end - if node.rest&.name - # node.rest is Prism::RestParameterNode - scope[node.rest.name.to_s] = Types.array_of(*rest) - end - node.keywords.each do |n| - name = n.name.to_s.delete(':') - values = [kwargs.delete(name)] - # n is Prism::OptionalKeywordParameterNode (has n.value) or Prism::RequiredKeywordParameterNode (does not have n.value) - values << evaluate(n.value, scope) if n.respond_to?(:value) - scope[name] = Types::UnionType[*values.compact] - end - # node.keyword_rest is Prism::KeywordRestParameterNode or Prism::ForwardingParameterNode or Prism::NoKeywordsParameterNode - if node.keyword_rest.is_a?(Prism::KeywordRestParameterNode) && node.keyword_rest.name - scope[node.keyword_rest.name.to_s] = Types::InstanceType.new(Hash, K: Types::SYMBOL, V: Types::UnionType[*kwargs.values]) - end - if node.block&.name - # node.block is Prism::BlockParameterNode - scope[node.block.name.to_s] = Types::PROC - end - end - - def assign_numbered_parameters(numbered_parameters, scope, args, _kwargs) - return if numbered_parameters.empty? - max_num = numbered_parameters.map { _1[1].to_i }.max - if max_num == 1 - scope['_1'] = args.first || Types::NIL - else - args = sized_splat(args.first, :to_ary, max_num) if args.size == 1 - numbered_parameters.each do |name| - index = name[1].to_i - 1 - scope[name] = args[index] || Types::NIL - end - end - end - - def evaluate_case_when_condition(node, scope) - node.conditions.each { evaluate _1, scope } - node.statements ? evaluate(node.statements, scope) : Types::NIL - end - - def evaluate_case_in_condition(target, node, scope) - pattern = node.pattern - if pattern.is_a?(Prism::IfNode) || pattern.is_a?(Prism::UnlessNode) - cond_node = pattern.predicate - pattern = pattern.statements.body.first - end - evaluate_match_pattern(target, pattern, scope) - evaluate cond_node, scope if cond_node # TODO: conditional branch - node.statements ? evaluate(node.statements, scope) : Types::NIL - end - - def evaluate_match_pattern(value, pattern, scope) - # TODO: scope.terminate_with Scope::PATTERNMATCH_BREAK, Types::NIL - case pattern - when Prism::FindPatternNode - # TODO - evaluate_match_pattern Types::OBJECT, pattern.left, scope - pattern.requireds.each { evaluate_match_pattern Types::OBJECT, _1, scope } - evaluate_match_pattern Types::OBJECT, pattern.right, scope - when Prism::ArrayPatternNode - # TODO - pattern.requireds.each { evaluate_match_pattern Types::OBJECT, _1, scope } - evaluate_match_pattern Types::OBJECT, pattern.rest, scope if pattern.rest - pattern.posts.each { evaluate_match_pattern Types::OBJECT, _1, scope } - Types::ARRAY - when Prism::HashPatternNode - # TODO - pattern.elements.each { evaluate_match_pattern Types::OBJECT, _1, scope } - if pattern.respond_to?(:rest) && pattern.rest - evaluate_match_pattern Types::OBJECT, pattern.rest, scope - end - Types::HASH - when Prism::AssocNode - evaluate_match_pattern value, pattern.value, scope if pattern.value - Types::OBJECT - when Prism::AssocSplatNode - # TODO - evaluate_match_pattern Types::HASH, pattern.value, scope - Types::OBJECT - when Prism::PinnedVariableNode - evaluate pattern.variable, scope - when Prism::PinnedExpressionNode - evaluate pattern.expression, scope - when Prism::LocalVariableTargetNode - scope[pattern.name.to_s] = value - when Prism::AlternationPatternNode - Types::UnionType[evaluate_match_pattern(value, pattern.left, scope), evaluate_match_pattern(value, pattern.right, scope)] - when Prism::CapturePatternNode - capture_type = class_or_value_to_instance evaluate_match_pattern(value, pattern.value, scope) - value = capture_type unless capture_type.types.empty? || capture_type.types == [Types::OBJECT] - evaluate_match_pattern value, pattern.target, scope - when Prism::SplatNode - value = Types.array_of value - evaluate_match_pattern value, pattern.expression, scope if pattern.expression - value - else - # literal node - type = evaluate(pattern, scope) - class_or_value_to_instance(type) - end - end - - def class_or_value_to_instance(type) - instance_types = type.types.map do |t| - t.is_a?(Types::SingletonType) ? Types::InstanceType.new(t.module_or_class) : t - end - Types::UnionType[*instance_types] - end - - def evaluate_write(node, value, scope, evaluated_receivers) - case node - when Prism::MultiTargetNode - evaluate_multi_write node, value, scope, evaluated_receivers - when Prism::CallNode - evaluated_receivers&.[](node.receiver) || evaluate(node.receiver, scope) if node.receiver - when Prism::SplatNode - evaluate_write node.expression, Types.array_of(value), scope, evaluated_receivers if node.expression - when Prism::LocalVariableTargetNode, Prism::GlobalVariableTargetNode, Prism::InstanceVariableTargetNode, Prism::ClassVariableTargetNode, Prism::ConstantTargetNode - scope[node.name.to_s] = value - when Prism::ConstantPathTargetNode - receiver = evaluated_receivers&.[](node.parent) || evaluate(node.parent, scope) if node.parent - const_path_write receiver, node.child.name.to_s, value, scope - value - end - end - - def evaluate_multi_write(node, values, scope, evaluated_receivers) - pre_targets = node.lefts - splat_target = node.rest - post_targets = node.rights - size = pre_targets.size + (splat_target ? 1 : 0) + post_targets.size - values = values.is_a?(Array) ? values.dup : sized_splat(values, :to_ary, size) - pre_pairs = pre_targets.zip(values.shift(pre_targets.size)) - post_pairs = post_targets.zip(values.pop(post_targets.size)) - splat_pairs = splat_target ? [[splat_target, Types::UnionType[*values]]] : [] - (pre_pairs + splat_pairs + post_pairs).each do |target, value| - evaluate_write target, value || Types::NIL, scope, evaluated_receivers - end - end - - def evaluate_multi_write_receiver(node, scope, evaluated_receivers) - case node - when Prism::MultiWriteNode, Prism::MultiTargetNode - targets = [*node.lefts, *node.rest, *node.rights] - targets.each { evaluate_multi_write_receiver _1, scope, evaluated_receivers } - when Prism::CallNode - if node.receiver - receiver = evaluate(node.receiver, scope) - evaluated_receivers[node.receiver] = receiver if evaluated_receivers - end - if node.arguments - node.arguments.arguments&.each do |arg| - if arg.is_a? Prism::SplatNode - evaluate arg.expression, scope - else - evaluate arg, scope - end - end - end - when Prism::SplatNode - evaluate_multi_write_receiver node.expression, scope, evaluated_receivers if node.expression - end - end - - def evaluate_list_splat_items(list, scope) - items = list.flat_map do |node| - if node.is_a? Prism::SplatNode - next unless node.expression # def f(*); [*] - - splat = evaluate node.expression, scope - array_elem, non_array = partition_to_array splat.nonnillable, :to_a - [*array_elem, *non_array] - else - evaluate node, scope - end - end.compact.uniq - Types::UnionType[*items] - end - - def sized_splat(value, method, size) - array_elem, non_array = partition_to_array value, method - values = [Types::UnionType[*array_elem, *non_array]] - values += [array_elem] * (size - 1) if array_elem && size >= 1 - values - end - - def partition_to_array(value, method) - arrays, non_arrays = value.types.partition { _1.is_a?(Types::InstanceType) && _1.klass == Array } - non_arrays.select! do |type| - to_array_result = method_call type, method, [], nil, nil, nil, name_match: false - if to_array_result.is_a?(Types::InstanceType) && to_array_result.klass == Array - arrays << to_array_result - false - else - true - end - end - array_elem = arrays.empty? ? nil : Types::UnionType[*arrays.map { _1.params[:Elem] || Types::OBJECT }] - non_array = non_arrays.empty? ? nil : Types::UnionType[*non_arrays] - [array_elem, non_array] - end - - def method_call(receiver, method_name, args, kwargs, block, scope, name_match: true) - methods = Types.rbs_methods receiver, method_name.to_sym, args, kwargs, !!block - block_called = false - type_breaks = methods.map do |method, given_params, method_params| - receiver_vars = receiver.is_a?(Types::InstanceType) ? receiver.params : {} - free_vars = method.type.free_variables - receiver_vars.keys.to_set - vars = receiver_vars.merge Types.match_free_variables(free_vars, method_params, given_params) - if block && method.block - params_type = method.block.type.required_positionals.map do |func_param| - Types.from_rbs_type func_param.type, receiver, vars - end - self_type = Types.from_rbs_type method.block.self_type, receiver, vars if method.block.self_type - block_response, breaks = block.call params_type, self_type - block_called = true - vars.merge! Types.match_free_variables(free_vars - vars.keys.to_set, [method.block.type.return_type], [block_response]) - end - if Types.method_return_bottom?(method) - [nil, breaks] - else - [Types.from_rbs_type(method.type.return_type, receiver, vars || {}), breaks] - end - end - block&.call [], nil unless block_called - terminates = !type_breaks.empty? && type_breaks.map(&:first).all?(&:nil?) - types = type_breaks.map(&:first).compact - breaks = type_breaks.map(&:last).compact - types << OBJECT_METHODS[method_name.to_sym] if name_match && OBJECT_METHODS.has_key?(method_name.to_sym) - - if method_name.to_sym == :new - receiver.types.each do |type| - if type.is_a?(Types::SingletonType) && type.module_or_class.is_a?(Class) - types << Types::InstanceType.new(type.module_or_class) - end - end - end - scope&.terminate if terminates && breaks.empty? - Types::UnionType[*types, *breaks] - end - - def self.calculate_target_type_scope(binding, parents, target) - dig_targets = DigTarget.new(parents, target) do |type, scope| - return type, scope - end - program = parents.first - scope = Scope.from_binding(binding, program.locals) - new(dig_targets).evaluate program, scope - [Types::NIL, scope] - end - end - end -end diff --git a/lib/irb/type_completion/types.rb b/lib/irb/type_completion/types.rb deleted file mode 100644 index f0f2342ffe..0000000000 --- a/lib/irb/type_completion/types.rb +++ /dev/null @@ -1,426 +0,0 @@ -# frozen_string_literal: true - -require_relative 'methods' - -module IRB - module TypeCompletion - module Types - OBJECT_TO_TYPE_SAMPLE_SIZE = 50 - - singleton_class.attr_reader :rbs_builder, :rbs_load_error - - def self.preload_in_thread - return if @preload_started - - @preload_started = true - Thread.new do - load_rbs_builder - end - end - - def self.load_rbs_builder - require 'rbs' - require 'rbs/cli' - loader = RBS::CLI::LibraryOptions.new.loader - loader.add path: Pathname('sig') - @rbs_builder = RBS::DefinitionBuilder.new env: RBS::Environment.from_loader(loader).resolve_type_names - rescue LoadError, StandardError => e - @rbs_load_error = e - nil - end - - def self.class_name_of(klass) - klass = klass.superclass if klass.singleton_class? - Methods::MODULE_NAME_METHOD.bind_call klass - end - - def self.rbs_search_method(klass, method_name, singleton) - klass.ancestors.each do |ancestor| - name = class_name_of ancestor - next unless name && rbs_builder - type_name = RBS::TypeName(name).absolute! - definition = (singleton ? rbs_builder.build_singleton(type_name) : rbs_builder.build_instance(type_name)) rescue nil - method = definition.methods[method_name] if definition - return method if method - end - nil - end - - def self.method_return_type(type, method_name) - receivers = type.types.map do |t| - case t - in SingletonType - [t, t.module_or_class, true] - in InstanceType - [t, t.klass, false] - end - end - types = receivers.flat_map do |receiver_type, klass, singleton| - method = rbs_search_method klass, method_name, singleton - next [] unless method - method.method_types.map do |method| - from_rbs_type(method.type.return_type, receiver_type, {}) - end - end - UnionType[*types] - end - - def self.rbs_methods(type, method_name, args_types, kwargs_type, has_block) - return [] unless rbs_builder - - receivers = type.types.map do |t| - case t - in SingletonType - [t, t.module_or_class, true] - in InstanceType - [t, t.klass, false] - end - end - has_splat = args_types.include?(nil) - methods_with_score = receivers.flat_map do |receiver_type, klass, singleton| - method = rbs_search_method klass, method_name, singleton - next [] unless method - method.method_types.map do |method_type| - score = 0 - score += 2 if !!method_type.block == has_block - reqs = method_type.type.required_positionals - opts = method_type.type.optional_positionals - rest = method_type.type.rest_positionals - trailings = method_type.type.trailing_positionals - keyreqs = method_type.type.required_keywords - keyopts = method_type.type.optional_keywords - keyrest = method_type.type.rest_keywords - args = args_types - if kwargs_type&.any? && keyreqs.empty? && keyopts.empty? && keyrest.nil? - kw_value_type = UnionType[*kwargs_type.values] - args += [InstanceType.new(Hash, K: SYMBOL, V: kw_value_type)] - end - if has_splat - score += 1 if args.count(&:itself) <= reqs.size + opts.size + trailings.size - elsif reqs.size + trailings.size <= args.size && (rest || args.size <= reqs.size + opts.size + trailings.size) - score += 2 - centers = args[reqs.size...-trailings.size] - given = args.first(reqs.size) + centers.take(opts.size) + args.last(trailings.size) - expected = (reqs + opts.take(centers.size) + trailings).map(&:type) - if rest - given << UnionType[*centers.drop(opts.size)] - expected << rest.type - end - if given.any? - score += given.zip(expected).count do |t, e| - e = from_rbs_type e, receiver_type - intersect?(t, e) || (intersect?(STRING, e) && t.methods.include?(:to_str)) || (intersect?(INTEGER, e) && t.methods.include?(:to_int)) || (intersect?(ARRAY, e) && t.methods.include?(:to_ary)) - end.fdiv(given.size) - end - end - [[method_type, given || [], expected || []], score] - end - end - max_score = methods_with_score.map(&:last).max - methods_with_score.select { _2 == max_score }.map(&:first) - end - - def self.intersect?(a, b) - atypes = a.types.group_by(&:class) - btypes = b.types.group_by(&:class) - if atypes[SingletonType] && btypes[SingletonType] - aa, bb = [atypes, btypes].map {|types| types[SingletonType].map(&:module_or_class) } - return true if (aa & bb).any? - end - - aa, bb = [atypes, btypes].map {|types| (types[InstanceType] || []).map(&:klass) } - (aa.flat_map(&:ancestors) & bb).any? - end - - def self.type_from_object(object) - case object - when Array - InstanceType.new Array, { Elem: union_type_from_objects(object) } - when Hash - InstanceType.new Hash, { K: union_type_from_objects(object.keys), V: union_type_from_objects(object.values) } - when Module - SingletonType.new object - else - klass = Methods::OBJECT_SINGLETON_CLASS_METHOD.bind_call(object) rescue Methods::OBJECT_CLASS_METHOD.bind_call(object) - InstanceType.new klass - end - end - - def self.union_type_from_objects(objects) - values = objects.size <= OBJECT_TO_TYPE_SAMPLE_SIZE ? objects : objects.sample(OBJECT_TO_TYPE_SAMPLE_SIZE) - klasses = values.map { Methods::OBJECT_CLASS_METHOD.bind_call(_1) } - UnionType[*klasses.uniq.map { InstanceType.new _1 }] - end - - class SingletonType - attr_reader :module_or_class - def initialize(module_or_class) - @module_or_class = module_or_class - end - def transform() = yield(self) - def methods() = @module_or_class.methods - def all_methods() = methods | Kernel.methods - def constants() = @module_or_class.constants - def types() = [self] - def nillable?() = false - def nonnillable() = self - def inspect - "#{module_or_class}.itself" - end - end - - class InstanceType - attr_reader :klass, :params - def initialize(klass, params = {}) - @klass = klass - @params = params - end - def transform() = yield(self) - def methods() = rbs_methods.select { _2.public? }.keys | @klass.instance_methods - def all_methods() = rbs_methods.keys | @klass.instance_methods | @klass.private_instance_methods - def constants() = [] - def types() = [self] - def nillable?() = (@klass == NilClass) - def nonnillable() = self - def rbs_methods - name = Types.class_name_of(@klass) - return {} unless name && Types.rbs_builder - - type_name = RBS::TypeName(name).absolute! - Types.rbs_builder.build_instance(type_name).methods rescue {} - end - def inspect - if params.empty? - inspect_without_params - else - params_string = "[#{params.map { "#{_1}: #{_2.inspect}" }.join(', ')}]" - "#{inspect_without_params}#{params_string}" - end - end - def inspect_without_params - if klass == NilClass - 'nil' - elsif klass == TrueClass - 'true' - elsif klass == FalseClass - 'false' - else - klass.singleton_class? ? klass.superclass.to_s : klass.to_s - end - end - end - - NIL = InstanceType.new NilClass - OBJECT = InstanceType.new Object - TRUE = InstanceType.new TrueClass - FALSE = InstanceType.new FalseClass - SYMBOL = InstanceType.new Symbol - STRING = InstanceType.new String - INTEGER = InstanceType.new Integer - RANGE = InstanceType.new Range - REGEXP = InstanceType.new Regexp - FLOAT = InstanceType.new Float - RATIONAL = InstanceType.new Rational - COMPLEX = InstanceType.new Complex - ARRAY = InstanceType.new Array - HASH = InstanceType.new Hash - CLASS = InstanceType.new Class - MODULE = InstanceType.new Module - PROC = InstanceType.new Proc - - class UnionType - attr_reader :types - - def initialize(*types) - @types = [] - singletons = [] - instances = {} - collect = -> type do - case type - in UnionType - type.types.each(&collect) - in InstanceType - params = (instances[type.klass] ||= {}) - type.params.each do |k, v| - (params[k] ||= []) << v - end - in SingletonType - singletons << type - end - end - types.each(&collect) - @types = singletons.uniq + instances.map do |klass, params| - InstanceType.new(klass, params.transform_values { |v| UnionType[*v] }) - end - end - - def transform(&block) - UnionType[*types.map(&block)] - end - - def nillable? - types.any?(&:nillable?) - end - - def nonnillable - UnionType[*types.reject { _1.is_a?(InstanceType) && _1.klass == NilClass }] - end - - def self.[](*types) - type = new(*types) - if type.types.empty? - OBJECT - elsif type.types.size == 1 - type.types.first - else - type - end - end - - def methods() = @types.flat_map(&:methods).uniq - def all_methods() = @types.flat_map(&:all_methods).uniq - def constants() = @types.flat_map(&:constants).uniq - def inspect() = @types.map(&:inspect).join(' | ') - end - - BOOLEAN = UnionType[TRUE, FALSE] - - def self.array_of(*types) - type = types.size >= 2 ? UnionType[*types] : types.first || OBJECT - InstanceType.new Array, Elem: type - end - - def self.from_rbs_type(return_type, self_type, extra_vars = {}) - case return_type - when RBS::Types::Bases::Self - self_type - when RBS::Types::Bases::Bottom, RBS::Types::Bases::Nil - NIL - when RBS::Types::Bases::Any, RBS::Types::Bases::Void - OBJECT - when RBS::Types::Bases::Class - self_type.transform do |type| - case type - in SingletonType - InstanceType.new(self_type.module_or_class.is_a?(Class) ? Class : Module) - in InstanceType - SingletonType.new type.klass - end - end - UnionType[*types] - when RBS::Types::Bases::Bool - BOOLEAN - when RBS::Types::Bases::Instance - self_type.transform do |type| - if type.is_a?(SingletonType) && type.module_or_class.is_a?(Class) - InstanceType.new type.module_or_class - else - OBJECT - end - end - when RBS::Types::Union - UnionType[*return_type.types.map { from_rbs_type _1, self_type, extra_vars }] - when RBS::Types::Proc - PROC - when RBS::Types::Tuple - elem = UnionType[*return_type.types.map { from_rbs_type _1, self_type, extra_vars }] - InstanceType.new Array, Elem: elem - when RBS::Types::Record - InstanceType.new Hash, K: SYMBOL, V: OBJECT - when RBS::Types::Literal - InstanceType.new return_type.literal.class - when RBS::Types::Variable - if extra_vars.key? return_type.name - extra_vars[return_type.name] - elsif self_type.is_a? InstanceType - self_type.params[return_type.name] || OBJECT - elsif self_type.is_a? UnionType - types = self_type.types.filter_map do |t| - t.params[return_type.name] if t.is_a? InstanceType - end - UnionType[*types] - else - OBJECT - end - when RBS::Types::Optional - UnionType[from_rbs_type(return_type.type, self_type, extra_vars), NIL] - when RBS::Types::Alias - case return_type.name.name - when :int - INTEGER - when :boolish - BOOLEAN - when :string - STRING - else - # TODO: ??? - OBJECT - end - when RBS::Types::Interface - # unimplemented - OBJECT - when RBS::Types::ClassInstance - klass = return_type.name.to_namespace.path.reduce(Object) { _1.const_get _2 } - if return_type.args - args = return_type.args.map { from_rbs_type _1, self_type, extra_vars } - names = rbs_builder.build_singleton(return_type.name).type_params - params = names.map.with_index { [_1, args[_2] || OBJECT] }.to_h - end - InstanceType.new klass, params || {} - end - end - - def self.method_return_bottom?(method) - method.type.return_type.is_a? RBS::Types::Bases::Bottom - end - - def self.match_free_variables(vars, types, values) - accumulator = {} - types.zip values do |t, v| - _match_free_variable(vars, t, v, accumulator) if v - end - accumulator.transform_values { UnionType[*_1] } - end - - def self._match_free_variable(vars, rbs_type, value, accumulator) - case [rbs_type, value] - in [RBS::Types::Variable,] - (accumulator[rbs_type.name] ||= []) << value if vars.include? rbs_type.name - in [RBS::Types::ClassInstance, InstanceType] - names = rbs_builder.build_singleton(rbs_type.name).type_params - names.zip(rbs_type.args).each do |name, arg| - v = value.params[name] - _match_free_variable vars, arg, v, accumulator if v - end - in [RBS::Types::Tuple, InstanceType] if value.klass == Array - v = value.params[:Elem] - rbs_type.types.each do |t| - _match_free_variable vars, t, v, accumulator - end - in [RBS::Types::Record, InstanceType] if value.klass == Hash - # TODO - in [RBS::Types::Interface,] - definition = rbs_builder.build_interface rbs_type.name - convert = {} - definition.type_params.zip(rbs_type.args).each do |from, arg| - convert[from] = arg.name if arg.is_a? RBS::Types::Variable - end - return if convert.empty? - ac = {} - definition.methods.each do |method_name, method| - return_type = method_return_type value, method_name - method.defs.each do |method_def| - interface_return_type = method_def.type.type.return_type - _match_free_variable convert, interface_return_type, return_type, ac - end - end - convert.each do |from, to| - values = ac[from] - (accumulator[to] ||= []).concat values if values - end - else - end - end - end - end -end -- cgit v1.2.3