class ReplTypeCompletor::TypeAnalyzer::DigTarget - repl_type_completor: Ruby Standard Library Documentation

# class ReplTypeCompletor::TypeAnalyzer::DigTarget

### Constants

OBJECT_METHODS

### Public Class Methods

calculate_target_type_scope(binding, parents, target) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 1180
    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

new(parents, receiver, &block) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 11
    def initialize(parents, receiver, &block)
      @dig_ids = parents.to_h { [_1.__id__, true] }
      @target_id = receiver.__id__
      @block = block
    end

### Public Instance Methods

assign_numbered_parameters(maximum, scope, args, _kwargs) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 958
    def assign_numbered_parameters(maximum, scope, args, _kwargs)
      if maximum == 1
        scope['_1'] = args.first || Types::NIL
      else
        args = sized_splat(args.first, :to_ary, maximum) if args.size == 1
        maximum.times do |index|
          scope["_#{index + 1}"] = args[index] || Types::NIL
        end
      end
    end

assign_parameters(node, scope, args, kwargs) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 910
    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
        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
      # Prism::ImplicitRestNode (tap{|a,|}) does not have a name
      if node.rest.is_a?(Prism::RestParameterNode) && node.rest.name
        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

assign_required_parameter(node, value, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 868
    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

class_or_value_to_instance(type) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 1038
    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

const_path_write(receiver, name, value, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 859
    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

dig?(node) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 17
      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_it_local_variable_read_node(_node, scope)
      # `it` is not a normal local variable. It can be overridden like `tap{p it; it=1}`.
      # Use the name `_1` instead of `it` to avoid conflict.
      scope['_1'] || Types::NIL
    end

    def evaluate_call_node(node, scope)
      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|
              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
              case node.block.parameters
              when Prism::NumberedParametersNode
                assign_numbered_parameters node.block.parameters.maximum, block_scope, block_args, {}
              when Prism::BlockParametersNode
                assign_parameters node.block.parameters.parameters, block_scope, block_args, {}
              when Prism::ItParametersNode
                scope['_1'] = block_args.first || Types::NIL
              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 node.attribute_write?
          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.binary_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.binary_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.binary_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.binary_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.name.to_s, value, scope
      value
    end

    def evaluate_lambda_node(node, scope)
      local_table = node.locals.to_h { [_1.to_s, Types::OBJECT] }

      # `it` is not added to local_table because it is not a normal local variable.
      # We need to explicitly add it to the scope.
      local_table['_1'] = Types::OBJECT if node.parameters.is_a?(Prism::ItParametersNode)

      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.is_a?(Prism::ParametersNode) && 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 node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.subsequent ? evaluate(node.subsequent, _1) : Types::NIL }
      )]
    end

    def evaluate_unless_node(node, scope)
      evaluate node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.else_clause ? evaluate(node.else_clause, _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]
          evaluate_write node.reference, error_type, s, nil
        end
        node.statements ? evaluate(node.statements, s) : Types::NIL
      end
      if node.subsequent # begin; rescue A; rescue B; end
        types = scope.run_branches(
          run_rescue,
          -> { evaluate node.subsequent, _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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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)
      # /(?<a>)(?<b>)/ =~ 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)
      name = node.name.to_s
      case node
      when Prism::ConstantPathNode
        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
        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
        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
      # Prism::ImplicitRestNode (tap{|a,|}) does not have a name
      if node.rest.is_a?(Prism::RestParameterNode) && node.rest.name
        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(maximum, scope, args, _kwargs)
      if maximum == 1
        scope['_1'] = args.first || Types::NIL
      else
        args = sized_splat(args.first, :to_ary, maximum) if args.size == 1
        maximum.times do |index|
          scope["_#{index + 1}"] = 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)
      pattern = pattern.body while pattern.is_a?(Prism::ParenthesesNode)

      # 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::CallTargetNode, Prism::IndexTargetNode
        evaluated_receivers&.[](node.receiver) || evaluate_multi_write_receiver(node, scope, nil)
      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.name.to_s, value, scope
      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::CallTargetNode, Prism::CallNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        receiver
      when Prism::IndexTargetNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        if node.arguments
          node.arguments.arguments&.each do |arg|
            if arg.is_a? Prism::SplatNode
              evaluate arg.expression, scope if arg.expression
            else
              evaluate arg, scope
            end
          end
        end
        evaluate node.block.expression, scope if node.block&.expression
        receiver
      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 && method.block.type.respond_to?(:required_positionals)
          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|

evaluate(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 42
    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

evaluate_alias_global_variable_node(_node, _scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 813
      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)
        name = node.name.to_s
        case node
        when Prism::ConstantPathNode
          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
          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
          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
        # Prism::ImplicitRestNode (tap{|a,|}) does not have a name
        if node.rest.is_a?(Prism::RestParameterNode) && node.rest.name
          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(maximum, scope, args, _kwargs)
        if maximum == 1
          scope['_1'] = args.first || Types::NIL
        else
          args = sized_splat(args.first, :to_ary, maximum) if args.size == 1
          maximum.times do |index|
            scope["_#{index + 1}"] = 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)
        pattern = pattern.body while pattern.is_a?(Prism::ParenthesesNode)

        # 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::CallTargetNode, Prism::IndexTargetNode
          evaluated_receivers&.[](node.receiver) || evaluate_multi_write_receiver(node, scope, nil)
        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.name.to_s, value, scope
        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::CallTargetNode, Prism::CallNode
          receiver = evaluate(node.receiver, scope)
          evaluated_receivers[node.receiver] = receiver if evaluated_receivers
          receiver
        when Prism::IndexTargetNode
          receiver = evaluate(node.receiver, scope)
          evaluated_receivers[node.receiver] = receiver if evaluated_receivers
          if node.arguments
            node.arguments.arguments&.each do |arg|
              if arg.is_a? Prism::SplatNode
                evaluate arg.expression, scope if arg.expression
              else
                evaluate arg, scope
              end
            end
          end
          evaluate node.block.expression, scope if node.block&.expression
          receiver
        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 && method.block.type.respond_to?(:required_positionals)
            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

evaluate_alias_method_node(_node, _scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 812
    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)
      name = node.name.to_s
      case node
      when Prism::ConstantPathNode
        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
        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
        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
      # Prism::ImplicitRestNode (tap{|a,|}) does not have a name
      if node.rest.is_a?(Prism::RestParameterNode) && node.rest.name
        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(maximum, scope, args, _kwargs)
      if maximum == 1
        scope['_1'] = args.first || Types::NIL
      else
        args = sized_splat(args.first, :to_ary, maximum) if args.size == 1
        maximum.times do |index|
          scope["_#{index + 1}"] = 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)
      pattern = pattern.body while pattern.is_a?(Prism::ParenthesesNode)

      # 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::CallTargetNode, Prism::IndexTargetNode
        evaluated_receivers&.[](node.receiver) || evaluate_multi_write_receiver(node, scope, nil)
      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.name.to_s, value, scope
      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::CallTargetNode, Prism::CallNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        receiver
      when Prism::IndexTargetNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        if node.arguments
          node.arguments.arguments&.each do |arg|
            if arg.is_a? Prism::SplatNode
              evaluate arg.expression, scope if arg.expression
            else
              evaluate arg, scope
            end
          end
        end
        evaluate node.block.expression, scope if node.block&.expression
        receiver
      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 && method.block.type.respond_to?(:required_positionals)
          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

evaluate_and_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 307
    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.binary_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.binary_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.binary_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.binary_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.name.to_s, value, scope
      value
    end

    def evaluate_lambda_node(node, scope)
      local_table = node.locals.to_h { [_1.to_s, Types::OBJECT] }

      # `it` is not added to local_table because it is not a normal local variable.
      # We need to explicitly add it to the scope.
      local_table['_1'] = Types::OBJECT if node.parameters.is_a?(Prism::ItParametersNode)

      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.is_a?(Prism::ParametersNode) && 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 node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.subsequent ? evaluate(node.subsequent, _1) : Types::NIL }
      )]
    end

    def evaluate_unless_node(node, scope)
      evaluate node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.else_clause ? evaluate(node.else_clause, _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]
          evaluate_write node.reference, error_type, s, nil
        end
        node.statements ? evaluate(node.statements, s) : Types::NIL
      end
      if node.subsequent # begin; rescue A; rescue B; end
        types = scope.run_branches(
          run_rescue,
          -> { evaluate node.subsequent, _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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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)
      # /(?<a>)(?<b>)/ =~ 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)
      name = node.name.to_s
      case node
      when Prism::ConstantPathNode
        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
        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
        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
      # Prism::ImplicitRestNode (tap{|a,|}) does not have a name
      if node.rest.is_a?(Prism::RestParameterNode) && node.rest.name
        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(maximum, scope, args, _kwargs)
      if maximum == 1
        scope['_1'] = args.first || Types::NIL
      else
        args = sized_splat(args.first, :to_ary, maximum) if args.size == 1
        maximum.times do |index|
          scope["_#{index + 1}"] = 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)
      pattern = pattern.body while pattern.is_a?(Prism::ParenthesesNode)

      # 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::CallTargetNode, Prism::IndexTargetNode
        evaluated_receivers&.[](node.receiver) || evaluate_multi_write_receiver(node, scope, nil)
      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.name.to_s, value, scope
      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::CallTargetNode, Prism::CallNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        receiver
      when Prism::IndexTargetNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        if node.arguments
          node.arguments.arguments&.each do |arg|
            if arg.is_a? Prism::SplatNode
              evaluate arg.expression, scope if arg.expression
            else
              evaluate arg, scope
            end
          end
        end
        evaluate node.block.expression, scope if node.block&.expression
        receiver
      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 && method.block.type.respond_to?(:required_positionals)
          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

evaluate_and_or(node, scope, and_op:) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 309
    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

evaluate_array_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 158
    def evaluate_array_node(node, scope)
      Types.array_of evaluate_list_splat_items(node.elements, scope)
    end

evaluate_back_reference_read_node(_node, _scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 219
    def evaluate_back_reference_read_node(_node, _scope)
      Types::UnionType[Types::STRING, Types::NIL]
    end

evaluate_begin_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 567
    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

evaluate_break_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 517
    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]
          evaluate_write node.reference, error_type, s, nil
        end
        node.statements ? evaluate(node.statements, s) : Types::NIL
      end
      if node.subsequent # begin; rescue A; rescue B; end
        types = scope.run_branches(
          run_rescue,
          -> { evaluate node.subsequent, _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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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)
      # /(?<a>)(?<b>)/ =~ 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)
      name = node.name.to_s
      case node
      when Prism::ConstantPathNode
        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
        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
        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
      # Prism::ImplicitRestNode (tap{|a,|}) does not have a name
      if node.rest.is_a?(Prism::RestParameterNode) && node.rest.name
        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(maximum, scope, args, _kwargs)
      if maximum == 1
        scope['_1'] = args.first || Types::NIL
      else
        args = sized_splat(args.first, :to_ary, maximum) if args.size == 1
        maximum.times do |index|
          scope["_#{index + 1}"] = 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)
      pattern = pattern.body while pattern.is_a?(Prism::ParenthesesNode)

      # 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::CallTargetNode, Prism::IndexTargetNode
        evaluated_receivers&.[](node.receiver) || evaluate_multi_write_receiver(node, scope, nil)
      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.name.to_s, value, scope
      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::CallTargetNode, Prism::CallNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        receiver
      when Prism::IndexTargetNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        if node.arguments
          node.arguments.arguments&.each do |arg|
            if arg.is_a? Prism::SplatNode
              evaluate arg.expression, scope if arg.expression
            else
              evaluate arg, scope
            end
          end
        end
        evaluate node.block.expression, scope if node.block&.expression
        receiver
      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 && method.block.type.respond_to?(:required_positionals)
          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

evaluate_call_and_write_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 320
    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.binary_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.binary_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.binary_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.binary_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.name.to_s, value, scope
      value
    end

    def evaluate_lambda_node(node, scope)
      local_table = node.locals.to_h { [_1.to_s, Types::OBJECT] }

      # `it` is not added to local_table because it is not a normal local variable.
      # We need to explicitly add it to the scope.
      local_table['_1'] = Types::OBJECT if node.parameters.is_a?(Prism::ItParametersNode)

      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.is_a?(Prism::ParametersNode) && 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 node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.subsequent ? evaluate(node.subsequent, _1) : Types::NIL }
      )]
    end

    def evaluate_unless_node(node, scope)
      evaluate node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.else_clause ? evaluate(node.else_clause, _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]
          evaluate_write node.reference, error_type, s, nil
        end
        node.statements ? evaluate(node.statements, s) : Types::NIL
      end
      if node.subsequent # begin; rescue A; rescue B; end
        types = scope.run_branches(
          run_rescue,
          -> { evaluate node.subsequent, _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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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)
      # /(?<a>)(?<b>)/ =~ 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)
      name = node.name.to_s
      case node
      when Prism::ConstantPathNode
        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
        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
        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
      # Prism::ImplicitRestNode (tap{|a,|}) does not have a name
      if node.rest.is_a?(Prism::RestParameterNode) && node.rest.name
        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(maximum, scope, args, _kwargs)
      if maximum == 1
        scope['_1'] = args.first || Types::NIL
      else
        args = sized_splat(args.first, :to_ary, maximum) if args.size == 1
        maximum.times do |index|
          scope["_#{index + 1}"] = 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)
      pattern = pattern.body while pattern.is_a?(Prism::ParenthesesNode)

      # 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::CallTargetNode, Prism::IndexTargetNode
        evaluated_receivers&.[](node.receiver) || evaluate_multi_write_receiver(node, scope, nil)
      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.name.to_s, value, scope
      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::CallTargetNode, Prism::CallNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        receiver
      when Prism::IndexTargetNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        if node.arguments
          node.arguments.arguments&.each do |arg|
            if arg.is_a? Prism::SplatNode
              evaluate arg.expression, scope if arg.expression
            else
              evaluate arg, scope
            end
          end
        end
        evaluate node.block.expression, scope if node.block&.expression
        receiver
      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 && method.block.type.respond_to?(:required_positionals)
          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_call_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 238
    def evaluate_call_node(node, scope)
      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|
              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
              case node.block.parameters
              when Prism::NumberedParametersNode
                assign_numbered_parameters node.block.parameters.maximum, block_scope, block_args, {}
              when Prism::BlockParametersNode
                assign_parameters node.block.parameters.parameters, block_scope, block_args, {}
              when Prism::ItParametersNode
                scope['_1'] = block_args.first || Types::NIL
              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 node.attribute_write?
          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

evaluate_call_node_arguments(call_node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 817
    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

evaluate_call_operator_write_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 319
    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.binary_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.binary_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.binary_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.binary_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.name.to_s, value, scope
      value
    end

    def evaluate_lambda_node(node, scope)
      local_table = node.locals.to_h { [_1.to_s, Types::OBJECT] }

      # `it` is not added to local_table because it is not a normal local variable.
      # We need to explicitly add it to the scope.
      local_table['_1'] = Types::OBJECT if node.parameters.is_a?(Prism::ItParametersNode)

      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.is_a?(Prism::ParametersNode) && 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 node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.subsequent ? evaluate(node.subsequent, _1) : Types::NIL }
      )]
    end

    def evaluate_unless_node(node, scope)
      evaluate node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.else_clause ? evaluate(node.else_clause, _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]
          evaluate_write node.reference, error_type, s, nil
        end
        node.statements ? evaluate(node.statements, s) : Types::NIL
      end
      if node.subsequent # begin; rescue A; rescue B; end
        types = scope.run_branches(
          run_rescue,
          -> { evaluate node.subsequent, _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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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)
      # /(?<a>)(?<b>)/ =~ 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)
      name = node.name.to_s
      case node
      when Prism::ConstantPathNode
        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
        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
        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
      # Prism::ImplicitRestNode (tap{|a,|}) does not have a name
      if node.rest.is_a?(Prism::RestParameterNode) && node.rest.name
        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(maximum, scope, args, _kwargs)
      if maximum == 1
        scope['_1'] = args.first || Types::NIL
      else
        args = sized_splat(args.first, :to_ary, maximum) if args.size == 1
        maximum.times do |index|
          scope["_#{index + 1}"] = 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)
      pattern = pattern.body while pattern.is_a?(Prism::ParenthesesNode)

      # 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::CallTargetNode, Prism::IndexTargetNode
        evaluated_receivers&.[](node.receiver) || evaluate_multi_write_receiver(node, scope, nil)
      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.name.to_s, value, scope
      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::CallTargetNode, Prism::CallNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        receiver
      when Prism::IndexTargetNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        if node.arguments
          node.arguments.arguments&.each do |arg|
            if arg.is_a? Prism::SplatNode
              evaluate arg.expression, scope if arg.expression
            else
              evaluate arg, scope
            end
          end
        end
        evaluate node.block.expression, scope if node.block&.expression
        receiver
      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 && method.block.type.respond_to?(:required_positionals)
          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

evaluate_call_or_write_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 321
    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.binary_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.binary_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.binary_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.binary_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.name.to_s, value, scope
      value
    end

    def evaluate_lambda_node(node, scope)
      local_table = node.locals.to_h { [_1.to_s, Types::OBJECT] }

      # `it` is not added to local_table because it is not a normal local variable.
      # We need to explicitly add it to the scope.
      local_table['_1'] = Types::OBJECT if node.parameters.is_a?(Prism::ItParametersNode)

      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.is_a?(Prism::ParametersNode) && 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 node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.subsequent ? evaluate(node.subsequent, _1) : Types::NIL }
      )]
    end

    def evaluate_unless_node(node, scope)
      evaluate node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.else_clause ? evaluate(node.else_clause, _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]
          evaluate_write node.reference, error_type, s, nil
        end
        node.statements ? evaluate(node.statements, s) : Types::NIL
      end
      if node.subsequent # begin; rescue A; rescue B; end
        types = scope.run_branches(
          run_rescue,
          -> { evaluate node.subsequent, _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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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)
      # /(?<a>)(?<b>)/ =~ 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)
      name = node.name.to_s
      case node
      when Prism::ConstantPathNode
        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
        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
        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
      # Prism::ImplicitRestNode (tap{|a,|}) does not have a name
      if node.rest.is_a?(Prism::RestParameterNode) && node.rest.name
        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(maximum, scope, args, _kwargs)
      if maximum == 1
        scope['_1'] = args.first || Types::NIL
      else
        args = sized_splat(args.first, :to_ary, maximum) if args.size == 1
        maximum.times do |index|
          scope["_#{index + 1}"] = 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)
      pattern = pattern.body while pattern.is_a?(Prism::ParenthesesNode)

      # 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::CallTargetNode, Prism::IndexTargetNode
        evaluated_receivers&.[](node.receiver) || evaluate_multi_write_receiver(node, scope, nil)
      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.name.to_s, value, scope
      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::CallTargetNode, Prism::CallNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        receiver
      when Prism::IndexTargetNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        if node.arguments
          node.arguments.arguments&.each do |arg|
            if arg.is_a? Prism::SplatNode
              evaluate arg.expression, scope if arg.expression
            else
              evaluate arg, scope
            end
          end
        end
        evaluate node.block.expression, scope if node.block&.expression
        receiver
      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 && method.block.type.respond_to?(:required_positionals)
          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_call_write(node, scope, operator, write_name) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 325
    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.binary_operator, [right], nil, nil, scope, name_match: false
      end
    end

evaluate_case_in_condition(target, node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 974
    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

evaluate_case_match_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 729
    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.else_clause
        branches << ->(s) { evaluate node.else_clause, s }
      end
      Types::UnionType[*scope.run_branches(*branches)]
    end

evaluate_case_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 715
    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.else_clause
        branches << ->(s) { evaluate node.else_clause, s }
      else
        branches << ->(_s) { Types::NIL }
      end
      Types::UnionType[*scope.run_branches(*branches)]
    end

evaluate_case_when_condition(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 969
    def evaluate_case_when_condition(node, scope)
      node.conditions.each { evaluate _1, scope }
      node.statements ? evaluate(node.statements, scope) : Types::NIL
    end

evaluate_class_module(node, scope, is_class) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 640
    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

evaluate_class_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 638
    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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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)
      # /(?<a>)(?<b>)/ =~ 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)
      name = node.name.to_s
      case node
      when Prism::ConstantPathNode
        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
        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
        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
      # Prism::ImplicitRestNode (tap{|a,|}) does not have a name
      if node.rest.is_a?(Prism::RestParameterNode) && node.rest.name
        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(maximum, scope, args, _kwargs)
      if maximum == 1
        scope['_1'] = args.first || Types::NIL
      else
        args = sized_splat(args.first, :to_ary, maximum) if args.size == 1
        maximum.times do |index|
          scope["_#{index + 1}"] = 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)
      pattern = pattern.body while pattern.is_a?(Prism::ParenthesesNode)

      # 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::CallTargetNode, Prism::IndexTargetNode
        evaluated_receivers&.[](node.receiver) || evaluate_multi_write_receiver(node, scope, nil)
      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.name.to_s, value, scope
      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::CallTargetNode, Prism::CallNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        receiver
      when Prism::IndexTargetNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        if node.arguments
          node.arguments.arguments&.each do |arg|
            if arg.is_a? Prism::SplatNode
              evaluate arg.expression, scope if arg.expression
            else
              evaluate arg, scope
            end
          end
        end
        evaluate node.block.expression, scope if node.block&.expression
        receiver
      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 && method.block.type.respond_to?(:required_positionals)
          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]

evaluate_constant_and_write_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 391
    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

evaluate_constant_node_info(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 884
    def evaluate_constant_node_info(node, scope)
      name = node.name.to_s
      case node
      when Prism::ConstantPathNode
        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
        type = scope[name]
      end
      @dig_targets.resolve type, scope if @dig_targets.target? node
      [type, receiver, parent_module, name]
    end

evaluate_constant_operator_write_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 385
    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.binary_operator, [right], nil, nil, scope, name_match: false
    end

evaluate_constant_or_write_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 396
    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

evaluate_constant_path_and_write_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 410
    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

evaluate_constant_path_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 196
    def evaluate_constant_path_node(node, scope)
      type, = evaluate_constant_node_info node, scope
      type
    end

evaluate_constant_path_operator_write_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 402
    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.binary_operator, [right], nil, nil, scope, name_match: false
      const_path_write receiver, name, value, scope
      value
    end

evaluate_constant_path_or_write_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 418
    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

evaluate_constant_path_write_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 426
    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.name.to_s, value, scope
      value
    end

evaluate_def_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 65
    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

evaluate_defined_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 760
    def evaluate_defined_node(node, scope)
      scope.conditional { evaluate node.value, _1 }
      Types::UnionType[Types::STRING, Types::NIL]
    end

evaluate_else_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 497
    def evaluate_else_node(node, scope)
      node.statements ? evaluate(node.statements, scope) : Types::NIL
    end

evaluate_embedded_statements_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 148
    def evaluate_embedded_statements_node(node, scope)
      node.statements ? evaluate(node.statements, scope) : Types::NIL
      Types::STRING
    end

evaluate_embedded_variable_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 153
    def evaluate_embedded_variable_node(node, scope)
      evaluate node.variable, scope
      Types::STRING
    end

evaluate_false_node(_node, _scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 205
    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_it_local_variable_read_node(_node, scope)
      # `it` is not a normal local variable. It can be overridden like `tap{p it; it=1}`.
      # Use the name `_1` instead of `it` to avoid conflict.
      scope['_1'] || Types::NIL
    end

    def evaluate_call_node(node, scope)
      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|
              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
              case node.block.parameters
              when Prism::NumberedParametersNode
                assign_numbered_parameters node.block.parameters.maximum, block_scope, block_args, {}
              when Prism::BlockParametersNode
                assign_parameters node.block.parameters.parameters, block_scope, block_args, {}
              when Prism::ItParametersNode
                scope['_1'] = block_args.first || Types::NIL
              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 node.attribute_write?
          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.binary_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.binary_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.binary_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.binary_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.name.to_s, value, scope
      value
    end

    def evaluate_lambda_node(node, scope)
      local_table = node.locals.to_h { [_1.to_s, Types::OBJECT] }

      # `it` is not added to local_table because it is not a normal local variable.
      # We need to explicitly add it to the scope.
      local_table['_1'] = Types::OBJECT if node.parameters.is_a?(Prism::ItParametersNode)

      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.is_a?(Prism::ParametersNode) && 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 node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.subsequent ? evaluate(node.subsequent, _1) : Types::NIL }
      )]
    end

    def evaluate_unless_node(node, scope)
      evaluate node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.else_clause ? evaluate(node.else_clause, _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]
          evaluate_write node.reference, error_type, s, nil
        end
        node.statements ? evaluate(node.statements, s) : Types::NIL
      end
      if node.subsequent # begin; rescue A; rescue B; end
        types = scope.run_branches(
          run_rescue,
          -> { evaluate node.subsequent, _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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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)
      # /(?<a>)(?<b>)/ =~ 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)
      name = node.name.to_s
      case node
      when Prism::ConstantPathNode
        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
        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
        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
      # Prism::ImplicitRestNode (tap{|a,|}) does not have a name
      if node.rest.is_a?(Prism::RestParameterNode) && node.rest.name
        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(maximum, scope, args, _kwargs)
      if maximum == 1
        scope['_1'] = args.first || Types::NIL
      else
        args = sized_splat(args.first, :to_ary, maximum) if args.size == 1
        maximum.times do |index|
          scope["_#{index + 1}"] = 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)
      pattern = pattern.body while pattern.is_a?(Prism::ParenthesesNode)

      # 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::CallTargetNode, Prism::IndexTargetNode
        evaluated_receivers&.[](node.receiver) || evaluate_multi_write_receiver(node, scope, nil)
      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.name.to_s, value, scope
      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::CallTargetNode, Prism::CallNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        receiver
      when Prism::IndexTargetNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        if node.arguments
          node.arguments.arguments&.each do |arg|
            if arg.is_a? Prism::SplatNode
              evaluate arg.expression, scope if arg.expression
            else
              evaluate arg, scope
            end
          end
        end
        evaluate node.block.expression, scope if node.block&.expression
        receiver
      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 && method.block.type.respond_to?(:required_positionals)
          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 =

evaluate_flip_flop_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 765
    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

evaluate_float_node(_node, _scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 106
    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_it_local_variable_read_node(_node, scope)
      # `it` is not a normal local variable. It can be overridden like `tap{p it; it=1}`.
      # Use the name `_1` instead of `it` to avoid conflict.
      scope['_1'] || Types::NIL
    end

    def evaluate_call_node(node, scope)
      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|
              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
              case node.block.parameters
              when Prism::NumberedParametersNode
                assign_numbered_parameters node.block.parameters.maximum, block_scope, block_args, {}
              when Prism::BlockParametersNode
                assign_parameters node.block.parameters.parameters, block_scope, block_args, {}
              when Prism::ItParametersNode
                scope['_1'] = block_args.first || Types::NIL
              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 node.attribute_write?
          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.binary_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.binary_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.binary_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.binary_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.name.to_s, value, scope
      value
    end

    def evaluate_lambda_node(node, scope)
      local_table = node.locals.to_h { [_1.to_s, Types::OBJECT] }

      # `it` is not added to local_table because it is not a normal local variable.
      # We need to explicitly add it to the scope.
      local_table['_1'] = Types::OBJECT if node.parameters.is_a?(Prism::ItParametersNode)

      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.is_a?(Prism::ParametersNode) && 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 node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.subsequent ? evaluate(node.subsequent, _1) : Types::NIL }
      )]
    end

    def evaluate_unless_node(node, scope)
      evaluate node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.else_clause ? evaluate(node.else_clause, _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]
          evaluate_write node.reference, error_type, s, nil
        end
        node.statements ? evaluate(node.statements, s) : Types::NIL
      end
      if node.subsequent # begin; rescue A; rescue B; end
        types = scope.run_branches(
          run_rescue,
          -> { evaluate node.subsequent, _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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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)
      # /(?<a>)(?<b>)/ =~ 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)
      name = node.name.to_s
      case node
      when Prism::ConstantPathNode
        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
        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
        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
      # Prism::ImplicitRestNode (tap{|a,|}) does not have a name
      if node.rest.is_a?(Prism::RestParameterNode) && node.rest.name
        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(maximum, scope, args, _kwargs)
      if maximum == 1
        scope['_1'] = args.first || Types::NIL
      else
        args = sized_splat(args.first, :to_ary, maximum) if args.size == 1
        maximum.times do |index|
          scope["_#{index + 1}"] = 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)
      pattern = pattern.body while pattern.is_a?(Prism::ParenthesesNode)

      # 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::CallTargetNode, Prism::IndexTargetNode
        evaluated_receivers&.[](node.receiver) || evaluate_multi_write_receiver(node, scope, nil)
      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.name.to_s, value, scope
      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::CallTargetNode, Prism::CallNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        receiver
      when Prism::IndexTargetNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        if node.arguments
          node.arguments.arguments&.each do |arg|
            if arg.is_a? Prism::SplatNode
              evaluate arg.expression, scope if arg.expression
            else
              evaluate arg, scope
            end
          end
        end
        evaluate node.block.expression, scope if node.block&.expression
        receiver
      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 && method.block.type.respond_to?(:required_positionals)
          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,

evaluate_for_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 696
    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

evaluate_forwarding_super_node(_node, _scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 560
    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]
          evaluate_write node.reference, error_type, s, nil
        end
        node.statements ? evaluate(node.statements, s) : Types::NIL
      end
      if node.subsequent # begin; rescue A; rescue B; end
        types = scope.run_branches(
          run_rescue,
          -> { evaluate node.subsequent, _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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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)
      # /(?<a>)(?<b>)/ =~ 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)
      name = node.name.to_s
      case node
      when Prism::ConstantPathNode
        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
        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
        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
      # Prism::ImplicitRestNode (tap{|a,|}) does not have a name
      if node.rest.is_a?(Prism::RestParameterNode) && node.rest.name
        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(maximum, scope, args, _kwargs)
      if maximum == 1
        scope['_1'] = args.first || Types::NIL
      else
        args = sized_splat(args.first, :to_ary, maximum) if args.size == 1
        maximum.times do |index|
          scope["_#{index + 1}"] = 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)
      pattern = pattern.body while pattern.is_a?(Prism::ParenthesesNode)

      # 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::CallTargetNode, Prism::IndexTargetNode
        evaluated_receivers&.[](node.receiver) || evaluate_multi_write_receiver(node, scope, nil)
      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.name.to_s, value, scope
      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::CallTargetNode, Prism::CallNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        receiver
      when Prism::IndexTargetNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        if node.arguments
          node.arguments.arguments&.each do |arg|
            if arg.is_a? Prism::SplatNode
              evaluate arg.expression, scope if arg.expression
            else
              evaluate arg, scope
            end
          end
        end
        evaluate node.block.expression, scope if node.block&.expression
        receiver
      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 && method.block.type.respond_to?(:required_positionals)
          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,

evaluate_hash(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 164
    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

evaluate_hash_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 162
    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_it_local_variable_read_node(_node, scope)
      # `it` is not a normal local variable. It can be overridden like `tap{p it; it=1}`.
      # Use the name `_1` instead of `it` to avoid conflict.
      scope['_1'] || Types::NIL
    end

    def evaluate_call_node(node, scope)
      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|
              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
              case node.block.parameters
              when Prism::NumberedParametersNode
                assign_numbered_parameters node.block.parameters.maximum, block_scope, block_args, {}
              when Prism::BlockParametersNode
                assign_parameters node.block.parameters.parameters, block_scope, block_args, {}
              when Prism::ItParametersNode
                scope['_1'] = block_args.first || Types::NIL
              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 node.attribute_write?
          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.binary_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.binary_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.binary_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.binary_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.name.to_s, value, scope
      value
    end

    def evaluate_lambda_node(node, scope)
      local_table = node.locals.to_h { [_1.to_s, Types::OBJECT] }

      # `it` is not added to local_table because it is not a normal local variable.
      # We need to explicitly add it to the scope.
      local_table['_1'] = Types::OBJECT if node.parameters.is_a?(Prism::ItParametersNode)

      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.is_a?(Prism::ParametersNode) && 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 node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.subsequent ? evaluate(node.subsequent, _1) : Types::NIL }
      )]
    end

    def evaluate_unless_node(node, scope)
      evaluate node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.else_clause ? evaluate(node.else_clause, _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]
          evaluate_write node.reference, error_type, s, nil
        end
        node.statements ? evaluate(node.statements, s) : Types::NIL
      end
      if node.subsequent # begin; rescue A; rescue B; end
        types = scope.run_branches(
          run_rescue,
          -> { evaluate node.subsequent, _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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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)
      # /(?<a>)(?<b>)/ =~ 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)
      name = node.name.to_s
      case node
      when Prism::ConstantPathNode
        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
        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
        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
      # Prism::ImplicitRestNode (tap{|a,|}) does not have a name
      if node.rest.is_a?(Prism::RestParameterNode) && node.rest.name
        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(maximum, scope, args, _kwargs)
      if maximum == 1
        scope['_1'] = args.first || Types::NIL
      else
        args = sized_splat(args.first, :to_ary, maximum) if args.size == 1
        maximum.times do |index|
          scope["_#{index + 1}"] = 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)
      pattern = pattern.body while pattern.is_a?(Prism::ParenthesesNode)

      # 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::CallTargetNode, Prism::IndexTargetNode
        evaluated_receivers&.[](node.receiver) || evaluate_multi_write_receiver(node, scope, nil)
      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.name.to_s, value, scope
      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::CallTargetNode, Prism::CallNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        receiver
      when Prism::IndexTargetNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        if node.arguments
          node.arguments.arguments&.each do |arg|
            if arg.is_a? Prism::SplatNode
              evaluate arg.expression, scope if arg.expression
            else
              evaluate arg, scope
            end
          end
        end
        evaluate node.block.expression, scope if node.block&.expression
        receiver
      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 && method.block.type.respond_to?(:required_positionals)
          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

evaluate_if_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 481
    def evaluate_if_node(node, scope)
      evaluate node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.subsequent ? evaluate(node.subsequent, _1) : Types::NIL }
      )]
    end

evaluate_imaginary_node(_node, _scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 110
    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_it_local_variable_read_node(_node, scope)
      # `it` is not a normal local variable. It can be overridden like `tap{p it; it=1}`.
      # Use the name `_1` instead of `it` to avoid conflict.
      scope['_1'] || Types::NIL
    end

    def evaluate_call_node(node, scope)
      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|
              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
              case node.block.parameters
              when Prism::NumberedParametersNode
                assign_numbered_parameters node.block.parameters.maximum, block_scope, block_args, {}
              when Prism::BlockParametersNode
                assign_parameters node.block.parameters.parameters, block_scope, block_args, {}
              when Prism::ItParametersNode
                scope['_1'] = block_args.first || Types::NIL
              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 node.attribute_write?
          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.binary_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.binary_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.binary_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.binary_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.name.to_s, value, scope
      value
    end

    def evaluate_lambda_node(node, scope)
      local_table = node.locals.to_h { [_1.to_s, Types::OBJECT] }

      # `it` is not added to local_table because it is not a normal local variable.
      # We need to explicitly add it to the scope.
      local_table['_1'] = Types::OBJECT if node.parameters.is_a?(Prism::ItParametersNode)

      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.is_a?(Prism::ParametersNode) && 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 node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.subsequent ? evaluate(node.subsequent, _1) : Types::NIL }
      )]
    end

    def evaluate_unless_node(node, scope)
      evaluate node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.else_clause ? evaluate(node.else_clause, _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]
          evaluate_write node.reference, error_type, s, nil
        end
        node.statements ? evaluate(node.statements, s) : Types::NIL
      end
      if node.subsequent # begin; rescue A; rescue B; end
        types = scope.run_branches(
          run_rescue,
          -> { evaluate node.subsequent, _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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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)
      # /(?<a>)(?<b>)/ =~ 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)
      name = node.name.to_s
      case node
      when Prism::ConstantPathNode
        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
        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
        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
      # Prism::ImplicitRestNode (tap{|a,|}) does not have a name
      if node.rest.is_a?(Prism::RestParameterNode) && node.rest.name
        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(maximum, scope, args, _kwargs)
      if maximum == 1
        scope['_1'] = args.first || Types::NIL
      else
        args = sized_splat(args.first, :to_ary, maximum) if args.size == 1
        maximum.times do |index|
          scope["_#{index + 1}"] = 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)
      pattern = pattern.body while pattern.is_a?(Prism::ParenthesesNode)

      # 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::CallTargetNode, Prism::IndexTargetNode
        evaluated_receivers&.[](node.receiver) || evaluate_multi_write_receiver(node, scope, nil)
      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.name.to_s, value, scope
      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::CallTargetNode, Prism::CallNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        receiver
      when Prism::IndexTargetNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        if node.arguments
          node.arguments.arguments&.each do |arg|
            if arg.is_a? Prism::SplatNode
              evaluate arg.expression, scope if arg.expression
            else
              evaluate arg, scope
            end
          end
        end
        evaluate node.block.expression, scope if node.block&.expression
        receiver
      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 && method.block.type.respond_to?(:required_positionals)
          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

evaluate_implicit_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 783
    def evaluate_implicit_node(node, scope)
      evaluate node.value, scope
    end

evaluate_index_and_write_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 323
    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.binary_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.binary_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.binary_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.binary_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.name.to_s, value, scope
      value
    end

    def evaluate_lambda_node(node, scope)
      local_table = node.locals.to_h { [_1.to_s, Types::OBJECT] }

      # `it` is not added to local_table because it is not a normal local variable.
      # We need to explicitly add it to the scope.
      local_table['_1'] = Types::OBJECT if node.parameters.is_a?(Prism::ItParametersNode)

      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.is_a?(Prism::ParametersNode) && 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 node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.subsequent ? evaluate(node.subsequent, _1) : Types::NIL }
      )]
    end

    def evaluate_unless_node(node, scope)
      evaluate node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.else_clause ? evaluate(node.else_clause, _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]
          evaluate_write node.reference, error_type, s, nil
        end
        node.statements ? evaluate(node.statements, s) : Types::NIL
      end
      if node.subsequent # begin; rescue A; rescue B; end
        types = scope.run_branches(
          run_rescue,
          -> { evaluate node.subsequent, _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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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)
      # /(?<a>)(?<b>)/ =~ 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)
      name = node.name.to_s
      case node
      when Prism::ConstantPathNode
        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
        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
        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
      # Prism::ImplicitRestNode (tap{|a,|}) does not have a name
      if node.rest.is_a?(Prism::RestParameterNode) && node.rest.name
        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(maximum, scope, args, _kwargs)
      if maximum == 1
        scope['_1'] = args.first || Types::NIL
      else
        args = sized_splat(args.first, :to_ary, maximum) if args.size == 1
        maximum.times do |index|
          scope["_#{index + 1}"] = 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)
      pattern = pattern.body while pattern.is_a?(Prism::ParenthesesNode)

      # 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::CallTargetNode, Prism::IndexTargetNode
        evaluated_receivers&.[](node.receiver) || evaluate_multi_write_receiver(node, scope, nil)
      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.name.to_s, value, scope
      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::CallTargetNode, Prism::CallNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        receiver
      when Prism::IndexTargetNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        if node.arguments
          node.arguments.arguments&.each do |arg|
            if arg.is_a? Prism::SplatNode
              evaluate arg.expression, scope if arg.expression
            else
              evaluate arg, scope
            end
          end
        end
        evaluate node.block.expression, scope if node.block&.expression
        receiver
      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 && method.block.type.respond_to?(:required_positionals)
          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,

evaluate_index_operator_write_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 322
    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.binary_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.binary_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.binary_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.binary_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.name.to_s, value, scope
      value
    end

    def evaluate_lambda_node(node, scope)
      local_table = node.locals.to_h { [_1.to_s, Types::OBJECT] }

      # `it` is not added to local_table because it is not a normal local variable.
      # We need to explicitly add it to the scope.
      local_table['_1'] = Types::OBJECT if node.parameters.is_a?(Prism::ItParametersNode)

      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.is_a?(Prism::ParametersNode) && 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 node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.subsequent ? evaluate(node.subsequent, _1) : Types::NIL }
      )]
    end

    def evaluate_unless_node(node, scope)
      evaluate node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.else_clause ? evaluate(node.else_clause, _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]
          evaluate_write node.reference, error_type, s, nil
        end
        node.statements ? evaluate(node.statements, s) : Types::NIL
      end
      if node.subsequent # begin; rescue A; rescue B; end
        types = scope.run_branches(
          run_rescue,
          -> { evaluate node.subsequent, _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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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)
      # /(?<a>)(?<b>)/ =~ 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)
      name = node.name.to_s
      case node
      when Prism::ConstantPathNode
        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
        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
        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
      # Prism::ImplicitRestNode (tap{|a,|}) does not have a name
      if node.rest.is_a?(Prism::RestParameterNode) && node.rest.name
        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(maximum, scope, args, _kwargs)
      if maximum == 1
        scope['_1'] = args.first || Types::NIL
      else
        args = sized_splat(args.first, :to_ary, maximum) if args.size == 1
        maximum.times do |index|
          scope["_#{index + 1}"] = 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)
      pattern = pattern.body while pattern.is_a?(Prism::ParenthesesNode)

      # 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::CallTargetNode, Prism::IndexTargetNode
        evaluated_receivers&.[](node.receiver) || evaluate_multi_write_receiver(node, scope, nil)
      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.name.to_s, value, scope
      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::CallTargetNode, Prism::CallNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        receiver
      when Prism::IndexTargetNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        if node.arguments
          node.arguments.arguments&.each do |arg|
            if arg.is_a? Prism::SplatNode
              evaluate arg.expression, scope if arg.expression
            else
              evaluate arg, scope
            end
          end
        end
        evaluate node.block.expression, scope if node.block&.expression
        receiver
      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 && method.block.type.respond_to?(:required_positionals)
          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

evaluate_index_or_write_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 324
    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.binary_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.binary_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.binary_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.binary_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.name.to_s, value, scope
      value
    end

    def evaluate_lambda_node(node, scope)
      local_table = node.locals.to_h { [_1.to_s, Types::OBJECT] }

      # `it` is not added to local_table because it is not a normal local variable.
      # We need to explicitly add it to the scope.
      local_table['_1'] = Types::OBJECT if node.parameters.is_a?(Prism::ItParametersNode)

      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.is_a?(Prism::ParametersNode) && 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 node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.subsequent ? evaluate(node.subsequent, _1) : Types::NIL }
      )]
    end

    def evaluate_unless_node(node, scope)
      evaluate node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.else_clause ? evaluate(node.else_clause, _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]
          evaluate_write node.reference, error_type, s, nil
        end
        node.statements ? evaluate(node.statements, s) : Types::NIL
      end
      if node.subsequent # begin; rescue A; rescue B; end
        types = scope.run_branches(
          run_rescue,
          -> { evaluate node.subsequent, _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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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)
      # /(?<a>)(?<b>)/ =~ 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)
      name = node.name.to_s
      case node
      when Prism::ConstantPathNode
        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
        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
        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
      # Prism::ImplicitRestNode (tap{|a,|}) does not have a name
      if node.rest.is_a?(Prism::RestParameterNode) && node.rest.name
        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(maximum, scope, args, _kwargs)
      if maximum == 1
        scope['_1'] = args.first || Types::NIL
      else
        args = sized_splat(args.first, :to_ary, maximum) if args.size == 1
        maximum.times do |index|
          scope["_#{index + 1}"] = 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)
      pattern = pattern.body while pattern.is_a?(Prism::ParenthesesNode)

      # 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::CallTargetNode, Prism::IndexTargetNode
        evaluated_receivers&.[](node.receiver) || evaluate_multi_write_receiver(node, scope, nil)
      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.name.to_s, value, scope
      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::CallTargetNode, Prism::CallNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        receiver
      when Prism::IndexTargetNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        if node.arguments
          node.arguments.arguments&.each do |arg|
            if arg.is_a? Prism::SplatNode
              evaluate arg.expression, scope if arg.expression
            else
              evaluate arg, scope
            end
          end
        end
        evaluate node.block.expression, scope if node.block&.expression
        receiver
      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 && method.block.type.respond_to?(:required_positionals)
          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

evaluate_integer_node(_node, _scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 104
    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_it_local_variable_read_node(_node, scope)
      # `it` is not a normal local variable. It can be overridden like `tap{p it; it=1}`.
      # Use the name `_1` instead of `it` to avoid conflict.
      scope['_1'] || Types::NIL
    end

    def evaluate_call_node(node, scope)
      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|
              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
              case node.block.parameters
              when Prism::NumberedParametersNode
                assign_numbered_parameters node.block.parameters.maximum, block_scope, block_args, {}
              when Prism::BlockParametersNode
                assign_parameters node.block.parameters.parameters, block_scope, block_args, {}
              when Prism::ItParametersNode
                scope['_1'] = block_args.first || Types::NIL
              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 node.attribute_write?
          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.binary_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.binary_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.binary_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.binary_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.name.to_s, value, scope
      value
    end

    def evaluate_lambda_node(node, scope)
      local_table = node.locals.to_h { [_1.to_s, Types::OBJECT] }

      # `it` is not added to local_table because it is not a normal local variable.
      # We need to explicitly add it to the scope.
      local_table['_1'] = Types::OBJECT if node.parameters.is_a?(Prism::ItParametersNode)

      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.is_a?(Prism::ParametersNode) && 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 node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.subsequent ? evaluate(node.subsequent, _1) : Types::NIL }
      )]
    end

    def evaluate_unless_node(node, scope)
      evaluate node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.else_clause ? evaluate(node.else_clause, _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]
          evaluate_write node.reference, error_type, s, nil
        end
        node.statements ? evaluate(node.statements, s) : Types::NIL
      end
      if node.subsequent # begin; rescue A; rescue B; end
        types = scope.run_branches(
          run_rescue,
          -> { evaluate node.subsequent, _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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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)
      # /(?<a>)(?<b>)/ =~ 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)
      name = node.name.to_s
      case node
      when Prism::ConstantPathNode
        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
        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
        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
      # Prism::ImplicitRestNode (tap{|a,|}) does not have a name
      if node.rest.is_a?(Prism::RestParameterNode) && node.rest.name
        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(maximum, scope, args, _kwargs)
      if maximum == 1
        scope['_1'] = args.first || Types::NIL
      else
        args = sized_splat(args.first, :to_ary, maximum) if args.size == 1
        maximum.times do |index|
          scope["_#{index + 1}"] = 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)
      pattern = pattern.body while pattern.is_a?(Prism::ParenthesesNode)

      # 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::CallTargetNode, Prism::IndexTargetNode
        evaluated_receivers&.[](node.receiver) || evaluate_multi_write_receiver(node, scope, nil)
      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.name.to_s, value, scope
      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::CallTargetNode, Prism::CallNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        receiver
      when Prism::IndexTargetNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        if node.arguments
          node.arguments.arguments&.each do |arg|
            if arg.is_a? Prism::SplatNode
              evaluate arg.expression, scope if arg.expression
            else
              evaluate arg, scope
            end
          end
        end
        evaluate node.block.expression, scope if node.block&.expression
        receiver
      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 && method.block.type.respond_to?(:required_positionals)
          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

evaluate_interpolated_match_last_line_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 799
    def evaluate_interpolated_match_last_line_node(node, scope)
      node.parts.each { evaluate _1, scope }
      Types::BOOLEAN
    end

evaluate_interpolated_regular_expression_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 143
    def evaluate_interpolated_regular_expression_node(node, scope)
      node.parts.each { evaluate _1, scope }
      Types::REGEXP
    end

evaluate_interpolated_string_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 128
    def evaluate_interpolated_string_node(node, scope)
      node.parts.each { evaluate _1, scope }
      Types::STRING
    end

evaluate_interpolated_symbol_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 138
    def evaluate_interpolated_symbol_node(node, scope)
      node.parts.each { evaluate _1, scope }
      Types::SYMBOL
    end

evaluate_interpolated_x_string_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 133
    def evaluate_interpolated_x_string_node(node, scope)
      node.parts.each { evaluate _1, scope }
      Types::STRING
    end

evaluate_it_local_variable_read_node(_node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 232
    def evaluate_it_local_variable_read_node(_node, scope)
      # `it` is not a normal local variable. It can be overridden like `tap{p it; it=1}`.
      # Use the name `_1` instead of `it` to avoid conflict.
      scope['_1'] || Types::NIL
    end

evaluate_jump(node, scope, mode) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 520
    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

evaluate_keyword_hash_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 163
    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_it_local_variable_read_node(_node, scope)
      # `it` is not a normal local variable. It can be overridden like `tap{p it; it=1}`.
      # Use the name `_1` instead of `it` to avoid conflict.
      scope['_1'] || Types::NIL
    end

    def evaluate_call_node(node, scope)
      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|
              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
              case node.block.parameters
              when Prism::NumberedParametersNode
                assign_numbered_parameters node.block.parameters.maximum, block_scope, block_args, {}
              when Prism::BlockParametersNode
                assign_parameters node.block.parameters.parameters, block_scope, block_args, {}
              when Prism::ItParametersNode
                scope['_1'] = block_args.first || Types::NIL
              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 node.attribute_write?
          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.binary_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.binary_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.binary_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.binary_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.name.to_s, value, scope
      value
    end

    def evaluate_lambda_node(node, scope)
      local_table = node.locals.to_h { [_1.to_s, Types::OBJECT] }

      # `it` is not added to local_table because it is not a normal local variable.
      # We need to explicitly add it to the scope.
      local_table['_1'] = Types::OBJECT if node.parameters.is_a?(Prism::ItParametersNode)

      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.is_a?(Prism::ParametersNode) && 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 node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.subsequent ? evaluate(node.subsequent, _1) : Types::NIL }
      )]
    end

    def evaluate_unless_node(node, scope)
      evaluate node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.else_clause ? evaluate(node.else_clause, _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]
          evaluate_write node.reference, error_type, s, nil
        end
        node.statements ? evaluate(node.statements, s) : Types::NIL
      end
      if node.subsequent # begin; rescue A; rescue B; end
        types = scope.run_branches(
          run_rescue,
          -> { evaluate node.subsequent, _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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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)
      # /(?<a>)(?<b>)/ =~ 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)
      name = node.name.to_s
      case node
      when Prism::ConstantPathNode
        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
        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
        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
      # Prism::ImplicitRestNode (tap{|a,|}) does not have a name
      if node.rest.is_a?(Prism::RestParameterNode) && node.rest.name
        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(maximum, scope, args, _kwargs)
      if maximum == 1
        scope['_1'] = args.first || Types::NIL
      else
        args = sized_splat(args.first, :to_ary, maximum) if args.size == 1
        maximum.times do |index|
          scope["_#{index + 1}"] = 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)
      pattern = pattern.body while pattern.is_a?(Prism::ParenthesesNode)

      # 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::CallTargetNode, Prism::IndexTargetNode
        evaluated_receivers&.[](node.receiver) || evaluate_multi_write_receiver(node, scope, nil)
      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.name.to_s, value, scope
      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::CallTargetNode, Prism::CallNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        receiver
      when Prism::IndexTargetNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        if node.arguments
          node.arguments.arguments&.each do |arg|
            if arg.is_a? Prism::SplatNode
              evaluate arg.expression, scope if arg.expression
            else
              evaluate arg, scope
            end
          end
        end
        evaluate node.block.expression, scope if node.block&.expression
        receiver
      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 && method.block.type.respond_to?(:required_positionals)
          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

evaluate_lambda_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 433
    def evaluate_lambda_node(node, scope)
      local_table = node.locals.to_h { [_1.to_s, Types::OBJECT] }

      # `it` is not added to local_table because it is not a normal local variable.
      # We need to explicitly add it to the scope.
      local_table['_1'] = Types::OBJECT if node.parameters.is_a?(Prism::ItParametersNode)

      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.is_a?(Prism::ParametersNode) && node.parameters.parameters
        evaluate node.body, s if node.body
      end
      block_scope.merge_jumps
      scope.update block_scope
      Types::PROC
    end

evaluate_list_splat_items(list, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 1103
    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

evaluate_match_last_line_node(_node, _scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 795
    def evaluate_match_last_line_node(_node, _scope)
      Types::BOOLEAN
    end

evaluate_match_pattern(value, pattern, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 985
    def evaluate_match_pattern(value, pattern, scope)
      pattern = pattern.body while pattern.is_a?(Prism::ParenthesesNode)

      # 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

evaluate_match_predicate_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 747
    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

evaluate_match_required_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 741
    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

evaluate_match_write_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 787
    def evaluate_match_write_node(node, scope)
      # /(?<a>)(?<b>)/ =~ 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

evaluate_missing_node(_node, _scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 815
      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)
        name = node.name.to_s
        case node
        when Prism::ConstantPathNode
          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
          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
          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
        # Prism::ImplicitRestNode (tap{|a,|}) does not have a name
        if node.rest.is_a?(Prism::RestParameterNode) && node.rest.name
          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(maximum, scope, args, _kwargs)
        if maximum == 1
          scope['_1'] = args.first || Types::NIL
        else
          args = sized_splat(args.first, :to_ary, maximum) if args.size == 1
          maximum.times do |index|
            scope["_#{index + 1}"] = 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)
        pattern = pattern.body while pattern.is_a?(Prism::ParenthesesNode)

        # 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::CallTargetNode, Prism::IndexTargetNode
          evaluated_receivers&.[](node.receiver) || evaluate_multi_write_receiver(node, scope, nil)
        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.name.to_s, value, scope
        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::CallTargetNode, Prism::CallNode
          receiver = evaluate(node.receiver, scope)
          evaluated_receivers[node.receiver] = receiver if evaluated_receivers
          receiver
        when Prism::IndexTargetNode
          receiver = evaluate(node.receiver, scope)
          evaluated_receivers[node.receiver] = receiver if evaluated_receivers
          if node.arguments
            node.arguments.arguments&.each do |arg|
              if arg.is_a? Prism::SplatNode
                evaluate arg.expression, scope if arg.expression
              else
                evaluate arg, scope
              end
            end
          end
          evaluate node.block.expression, scope if node.block&.expression
          receiver
        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 && method.block.type.respond_to?(:required_positionals)
            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

evaluate_module_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 639
    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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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)
      # /(?<a>)(?<b>)/ =~ 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)
      name = node.name.to_s
      case node
      when Prism::ConstantPathNode
        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
        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
        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
      # Prism::ImplicitRestNode (tap{|a,|}) does not have a name
      if node.rest.is_a?(Prism::RestParameterNode) && node.rest.name
        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(maximum, scope, args, _kwargs)
      if maximum == 1
        scope['_1'] = args.first || Types::NIL
      else
        args = sized_splat(args.first, :to_ary, maximum) if args.size == 1
        maximum.times do |index|
          scope["_#{index + 1}"] = 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)
      pattern = pattern.body while pattern.is_a?(Prism::ParenthesesNode)

      # 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::CallTargetNode, Prism::IndexTargetNode
        evaluated_receivers&.[](node.receiver) || evaluate_multi_write_receiver(node, scope, nil)
      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.name.to_s, value, scope
      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::CallTargetNode, Prism::CallNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        receiver
      when Prism::IndexTargetNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        if node.arguments
          node.arguments.arguments&.each do |arg|
            if arg.is_a? Prism::SplatNode
              evaluate arg.expression, scope if arg.expression
            else
              evaluate arg, scope
            end
          end
        end
        evaluate node.block.expression, scope if node.block&.expression
        receiver
      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 && method.block.type.respond_to?(:required_positionals)
          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]

evaluate_multi_target_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 771
    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

evaluate_multi_write(node, values, scope, evaluated_receivers) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 1061
    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

evaluate_multi_write_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 459
    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

evaluate_multi_write_receiver(node, scope, evaluated_receivers) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 1075
    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::CallTargetNode, Prism::CallNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        receiver
      when Prism::IndexTargetNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        if node.arguments
          node.arguments.arguments&.each do |arg|
            if arg.is_a? Prism::SplatNode
              evaluate arg.expression, scope if arg.expression
            else
              evaluate arg, scope
            end
          end
        end
        evaluate node.block.expression, scope if node.block&.expression
        receiver
      when Prism::SplatNode
        evaluate_multi_write_receiver node.expression, scope, evaluated_receivers if node.expression
      end
    end

evaluate_next_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 518
    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]
          evaluate_write node.reference, error_type, s, nil
        end
        node.statements ? evaluate(node.statements, s) : Types::NIL
      end
      if node.subsequent # begin; rescue A; rescue B; end
        types = scope.run_branches(
          run_rescue,
          -> { evaluate node.subsequent, _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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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)
      # /(?<a>)(?<b>)/ =~ 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)
      name = node.name.to_s
      case node
      when Prism::ConstantPathNode
        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
        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
        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
      # Prism::ImplicitRestNode (tap{|a,|}) does not have a name
      if node.rest.is_a?(Prism::RestParameterNode) && node.rest.name
        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(maximum, scope, args, _kwargs)
      if maximum == 1
        scope['_1'] = args.first || Types::NIL
      else
        args = sized_splat(args.first, :to_ary, maximum) if args.size == 1
        maximum.times do |index|
          scope["_#{index + 1}"] = 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)
      pattern = pattern.body while pattern.is_a?(Prism::ParenthesesNode)

      # 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::CallTargetNode, Prism::IndexTargetNode
        evaluated_receivers&.[](node.receiver) || evaluate_multi_write_receiver(node, scope, nil)
      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.name.to_s, value, scope
      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::CallTargetNode, Prism::CallNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        receiver
      when Prism::IndexTargetNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        if node.arguments
          node.arguments.arguments&.each do |arg|
            if arg.is_a? Prism::SplatNode
              evaluate arg.expression, scope if arg.expression
            else
              evaluate arg, scope
            end
          end
        end
        evaluate node.block.expression, scope if node.block&.expression
        receiver
      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 && method.block.type.respond_to?(:required_positionals)
          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

evaluate_nil_node(_node, _scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 207
    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_it_local_variable_read_node(_node, scope)
      # `it` is not a normal local variable. It can be overridden like `tap{p it; it=1}`.
      # Use the name `_1` instead of `it` to avoid conflict.
      scope['_1'] || Types::NIL
    end

    def evaluate_call_node(node, scope)
      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|
              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
              case node.block.parameters
              when Prism::NumberedParametersNode
                assign_numbered_parameters node.block.parameters.maximum, block_scope, block_args, {}
              when Prism::BlockParametersNode
                assign_parameters node.block.parameters.parameters, block_scope, block_args, {}
              when Prism::ItParametersNode
                scope['_1'] = block_args.first || Types::NIL
              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 node.attribute_write?
          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.binary_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.binary_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.binary_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.binary_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.name.to_s, value, scope
      value
    end

    def evaluate_lambda_node(node, scope)
      local_table = node.locals.to_h { [_1.to_s, Types::OBJECT] }

      # `it` is not added to local_table because it is not a normal local variable.
      # We need to explicitly add it to the scope.
      local_table['_1'] = Types::OBJECT if node.parameters.is_a?(Prism::ItParametersNode)

      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.is_a?(Prism::ParametersNode) && 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 node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.subsequent ? evaluate(node.subsequent, _1) : Types::NIL }
      )]
    end

    def evaluate_unless_node(node, scope)
      evaluate node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.else_clause ? evaluate(node.else_clause, _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]
          evaluate_write node.reference, error_type, s, nil
        end
        node.statements ? evaluate(node.statements, s) : Types::NIL
      end
      if node.subsequent # begin; rescue A; rescue B; end
        types = scope.run_branches(
          run_rescue,
          -> { evaluate node.subsequent, _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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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)
      # /(?<a>)(?<b>)/ =~ 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)
      name = node.name.to_s
      case node
      when Prism::ConstantPathNode
        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
        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
        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
      # Prism::ImplicitRestNode (tap{|a,|}) does not have a name
      if node.rest.is_a?(Prism::RestParameterNode) && node.rest.name
        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(maximum, scope, args, _kwargs)
      if maximum == 1
        scope['_1'] = args.first || Types::NIL
      else
        args = sized_splat(args.first, :to_ary, maximum) if args.size == 1
        maximum.times do |index|
          scope["_#{index + 1}"] = 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)
      pattern = pattern.body while pattern.is_a?(Prism::ParenthesesNode)

      # 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::CallTargetNode, Prism::IndexTargetNode
        evaluated_receivers&.[](node.receiver) || evaluate_multi_write_receiver(node, scope, nil)
      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.name.to_s, value, scope
      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::CallTargetNode, Prism::CallNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        receiver
      when Prism::IndexTargetNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        if node.arguments
          node.arguments.arguments&.each do |arg|
            if arg.is_a? Prism::SplatNode
              evaluate arg.expression, scope if arg.expression
            else
              evaluate arg, scope
            end
          end
        end
        evaluate node.block.expression, scope if node.block&.expression
        receiver
      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 && method.block.type.respond_to?(:required_positionals)
          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.

evaluate_numbered_reference_read_node(_node, _scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 215
    def evaluate_numbered_reference_read_node(_node, _scope)
      Types::UnionType[Types::STRING, Types::NIL]
    end

evaluate_or_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 308
    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.binary_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.binary_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.binary_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.binary_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.name.to_s, value, scope
      value
    end

    def evaluate_lambda_node(node, scope)
      local_table = node.locals.to_h { [_1.to_s, Types::OBJECT] }

      # `it` is not added to local_table because it is not a normal local variable.
      # We need to explicitly add it to the scope.
      local_table['_1'] = Types::OBJECT if node.parameters.is_a?(Prism::ItParametersNode)

      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.is_a?(Prism::ParametersNode) && 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 node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.subsequent ? evaluate(node.subsequent, _1) : Types::NIL }
      )]
    end

    def evaluate_unless_node(node, scope)
      evaluate node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.else_clause ? evaluate(node.else_clause, _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]
          evaluate_write node.reference, error_type, s, nil
        end
        node.statements ? evaluate(node.statements, s) : Types::NIL
      end
      if node.subsequent # begin; rescue A; rescue B; end
        types = scope.run_branches(
          run_rescue,
          -> { evaluate node.subsequent, _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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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)
      # /(?<a>)(?<b>)/ =~ 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)
      name = node.name.to_s
      case node
      when Prism::ConstantPathNode
        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
        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
        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
      # Prism::ImplicitRestNode (tap{|a,|}) does not have a name
      if node.rest.is_a?(Prism::RestParameterNode) && node.rest.name
        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(maximum, scope, args, _kwargs)
      if maximum == 1
        scope['_1'] = args.first || Types::NIL
      else
        args = sized_splat(args.first, :to_ary, maximum) if args.size == 1
        maximum.times do |index|
          scope["_#{index + 1}"] = 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)
      pattern = pattern.body while pattern.is_a?(Prism::ParenthesesNode)

      # 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::CallTargetNode, Prism::IndexTargetNode
        evaluated_receivers&.[](node.receiver) || evaluate_multi_write_receiver(node, scope, nil)
      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.name.to_s, value, scope
      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::CallTargetNode, Prism::CallNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        receiver
      when Prism::IndexTargetNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        if node.arguments
          node.arguments.arguments&.each do |arg|
            if arg.is_a? Prism::SplatNode
              evaluate arg.expression, scope if arg.expression
            else
              evaluate arg, scope
            end
          end
        end
        evaluate node.block.expression, scope if node.block&.expression
        receiver
      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 && method.block.type.respond_to?(:required_positionals)
          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)

evaluate_parentheses_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 192
    def evaluate_parentheses_node(node, scope)
      node.body ? evaluate(node.body, scope) : Types::NIL
    end

evaluate_post_execution_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 808
    def evaluate_post_execution_node(node, scope)
      node.statements && @dig_targets.dig?(node.statements) ? evaluate(node.statements, scope) : Types::NIL
    end

evaluate_pre_execution_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 804
    def evaluate_pre_execution_node(node, scope)
      node.statements ? evaluate(node.statements, scope) : Types::NIL
    end

evaluate_program_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 53
    def evaluate_program_node(node, scope)
      evaluate node.statements, scope
    end

evaluate_range_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 753
    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

evaluate_rational_node(_node, _scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 108
    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_it_local_variable_read_node(_node, scope)
      # `it` is not a normal local variable. It can be overridden like `tap{p it; it=1}`.
      # Use the name `_1` instead of `it` to avoid conflict.
      scope['_1'] || Types::NIL
    end

    def evaluate_call_node(node, scope)
      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|
              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
              case node.block.parameters
              when Prism::NumberedParametersNode
                assign_numbered_parameters node.block.parameters.maximum, block_scope, block_args, {}
              when Prism::BlockParametersNode
                assign_parameters node.block.parameters.parameters, block_scope, block_args, {}
              when Prism::ItParametersNode
                scope['_1'] = block_args.first || Types::NIL
              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 node.attribute_write?
          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.binary_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.binary_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.binary_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.binary_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.name.to_s, value, scope
      value
    end

    def evaluate_lambda_node(node, scope)
      local_table = node.locals.to_h { [_1.to_s, Types::OBJECT] }

      # `it` is not added to local_table because it is not a normal local variable.
      # We need to explicitly add it to the scope.
      local_table['_1'] = Types::OBJECT if node.parameters.is_a?(Prism::ItParametersNode)

      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.is_a?(Prism::ParametersNode) && 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 node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.subsequent ? evaluate(node.subsequent, _1) : Types::NIL }
      )]
    end

    def evaluate_unless_node(node, scope)
      evaluate node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.else_clause ? evaluate(node.else_clause, _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]
          evaluate_write node.reference, error_type, s, nil
        end
        node.statements ? evaluate(node.statements, s) : Types::NIL
      end
      if node.subsequent # begin; rescue A; rescue B; end
        types = scope.run_branches(
          run_rescue,
          -> { evaluate node.subsequent, _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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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)
      # /(?<a>)(?<b>)/ =~ 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)
      name = node.name.to_s
      case node
      when Prism::ConstantPathNode
        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
        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
        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
      # Prism::ImplicitRestNode (tap{|a,|}) does not have a name
      if node.rest.is_a?(Prism::RestParameterNode) && node.rest.name
        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(maximum, scope, args, _kwargs)
      if maximum == 1
        scope['_1'] = args.first || Types::NIL
      else
        args = sized_splat(args.first, :to_ary, maximum) if args.size == 1
        maximum.times do |index|
          scope["_#{index + 1}"] = 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)
      pattern = pattern.body while pattern.is_a?(Prism::ParenthesesNode)

      # 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::CallTargetNode, Prism::IndexTargetNode
        evaluated_receivers&.[](node.receiver) || evaluate_multi_write_receiver(node, scope, nil)
      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.name.to_s, value, scope
      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::CallTargetNode, Prism::CallNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        receiver
      when Prism::IndexTargetNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        if node.arguments
          node.arguments.arguments&.each do |arg|
            if arg.is_a? Prism::SplatNode
              evaluate arg.expression, scope if arg.expression
            else
              evaluate arg, scope
            end
          end
        end
        evaluate node.block.expression, scope if node.block&.expression
        receiver
      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 && method.block.type.respond_to?(:required_positionals)
          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

evaluate_redo_node(_node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 550
    def evaluate_redo_node(_node, scope)
      scope.terminate
      Types::NIL
    end

evaluate_reference_read(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 223
    def evaluate_reference_read(node, scope)
      scope[node.name.to_s] || Types::NIL
    end

evaluate_reference_write(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 450
    def evaluate_reference_write(node, scope)
      scope[node.name.to_s] = evaluate node.value, scope
    end

evaluate_regular_expression_node(_node, _scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 120
    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_it_local_variable_read_node(_node, scope)
      # `it` is not a normal local variable. It can be overridden like `tap{p it; it=1}`.
      # Use the name `_1` instead of `it` to avoid conflict.
      scope['_1'] || Types::NIL
    end

    def evaluate_call_node(node, scope)
      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|
              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
              case node.block.parameters
              when Prism::NumberedParametersNode
                assign_numbered_parameters node.block.parameters.maximum, block_scope, block_args, {}
              when Prism::BlockParametersNode
                assign_parameters node.block.parameters.parameters, block_scope, block_args, {}
              when Prism::ItParametersNode
                scope['_1'] = block_args.first || Types::NIL
              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 node.attribute_write?
          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.binary_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.binary_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.binary_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.binary_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.name.to_s, value, scope
      value
    end

    def evaluate_lambda_node(node, scope)
      local_table = node.locals.to_h { [_1.to_s, Types::OBJECT] }

      # `it` is not added to local_table because it is not a normal local variable.
      # We need to explicitly add it to the scope.
      local_table['_1'] = Types::OBJECT if node.parameters.is_a?(Prism::ItParametersNode)

      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.is_a?(Prism::ParametersNode) && 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 node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.subsequent ? evaluate(node.subsequent, _1) : Types::NIL }
      )]
    end

    def evaluate_unless_node(node, scope)
      evaluate node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.else_clause ? evaluate(node.else_clause, _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]
          evaluate_write node.reference, error_type, s, nil
        end
        node.statements ? evaluate(node.statements, s) : Types::NIL
      end
      if node.subsequent # begin; rescue A; rescue B; end
        types = scope.run_branches(
          run_rescue,
          -> { evaluate node.subsequent, _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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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)
      # /(?<a>)(?<b>)/ =~ 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)
      name = node.name.to_s
      case node
      when Prism::ConstantPathNode
        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
        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
        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
      # Prism::ImplicitRestNode (tap{|a,|}) does not have a name
      if node.rest.is_a?(Prism::RestParameterNode) && node.rest.name
        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(maximum, scope, args, _kwargs)
      if maximum == 1
        scope['_1'] = args.first || Types::NIL
      else
        args = sized_splat(args.first, :to_ary, maximum) if args.size == 1
        maximum.times do |index|
          scope["_#{index + 1}"] = 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)
      pattern = pattern.body while pattern.is_a?(Prism::ParenthesesNode)

      # 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::CallTargetNode, Prism::IndexTargetNode
        evaluated_receivers&.[](node.receiver) || evaluate_multi_write_receiver(node, scope, nil)
      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.name.to_s, value, scope
      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::CallTargetNode, Prism::CallNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        receiver
      when Prism::IndexTargetNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        if node.arguments
          node.arguments.arguments&.each do |arg|
            if arg.is_a? Prism::SplatNode
              evaluate arg.expression, scope if arg.expression
            else
              evaluate arg, scope
            end
          end
        end
        evaluate node.block.expression, scope if node.block&.expression
        receiver
      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 && method.block.type.respond_to?(:required_positionals)
          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 =

evaluate_rescue_modifier_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 614
    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

evaluate_rescue_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 590
    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]
          evaluate_write node.reference, error_type, s, nil
        end
        node.statements ? evaluate(node.statements, s) : Types::NIL
      end
      if node.subsequent # begin; rescue A; rescue B; end
        types = scope.run_branches(
          run_rescue,
          -> { evaluate node.subsequent, _1 }
        )
        Types::UnionType[*types]
      else
        run_rescue.call scope
      end
    end

evaluate_retry_node(_node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 555
    def evaluate_retry_node(_node, scope)
      scope.terminate
      Types::NIL
    end

evaluate_return_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 519
    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]
          evaluate_write node.reference, error_type, s, nil
        end
        node.statements ? evaluate(node.statements, s) : Types::NIL
      end
      if node.subsequent # begin; rescue A; rescue B; end
        types = scope.run_branches(
          run_rescue,
          -> { evaluate node.subsequent, _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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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)
      # /(?<a>)(?<b>)/ =~ 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)
      name = node.name.to_s
      case node
      when Prism::ConstantPathNode
        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
        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
        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
      # Prism::ImplicitRestNode (tap{|a,|}) does not have a name
      if node.rest.is_a?(Prism::RestParameterNode) && node.rest.name
        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(maximum, scope, args, _kwargs)
      if maximum == 1
        scope['_1'] = args.first || Types::NIL
      else
        args = sized_splat(args.first, :to_ary, maximum) if args.size == 1
        maximum.times do |index|
          scope["_#{index + 1}"] = 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)
      pattern = pattern.body while pattern.is_a?(Prism::ParenthesesNode)

      # 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::CallTargetNode, Prism::IndexTargetNode
        evaluated_receivers&.[](node.receiver) || evaluate_multi_write_receiver(node, scope, nil)
      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.name.to_s, value, scope
      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::CallTargetNode, Prism::CallNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        receiver
      when Prism::IndexTargetNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        if node.arguments
          node.arguments.arguments&.each do |arg|
            if arg.is_a? Prism::SplatNode
              evaluate arg.expression, scope if arg.expression
            else
              evaluate arg, scope
            end
          end
        end
        evaluate node.block.expression, scope if node.block&.expression
        receiver
      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 && method.block.type.respond_to?(:required_positionals)
          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

evaluate_self_node(_node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 201
    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_it_local_variable_read_node(_node, scope)
      # `it` is not a normal local variable. It can be overridden like `tap{p it; it=1}`.
      # Use the name `_1` instead of `it` to avoid conflict.
      scope['_1'] || Types::NIL
    end

    def evaluate_call_node(node, scope)
      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|
              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
              case node.block.parameters
              when Prism::NumberedParametersNode
                assign_numbered_parameters node.block.parameters.maximum, block_scope, block_args, {}
              when Prism::BlockParametersNode
                assign_parameters node.block.parameters.parameters, block_scope, block_args, {}
              when Prism::ItParametersNode
                scope['_1'] = block_args.first || Types::NIL
              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 node.attribute_write?
          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.binary_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.binary_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.binary_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.binary_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.name.to_s, value, scope
      value
    end

    def evaluate_lambda_node(node, scope)
      local_table = node.locals.to_h { [_1.to_s, Types::OBJECT] }

      # `it` is not added to local_table because it is not a normal local variable.
      # We need to explicitly add it to the scope.
      local_table['_1'] = Types::OBJECT if node.parameters.is_a?(Prism::ItParametersNode)

      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.is_a?(Prism::ParametersNode) && 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 node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.subsequent ? evaluate(node.subsequent, _1) : Types::NIL }
      )]
    end

    def evaluate_unless_node(node, scope)
      evaluate node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.else_clause ? evaluate(node.else_clause, _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]
          evaluate_write node.reference, error_type, s, nil
        end
        node.statements ? evaluate(node.statements, s) : Types::NIL
      end
      if node.subsequent # begin; rescue A; rescue B; end
        types = scope.run_branches(
          run_rescue,
          -> { evaluate node.subsequent, _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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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)
      # /(?<a>)(?<b>)/ =~ 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)
      name = node.name.to_s
      case node
      when Prism::ConstantPathNode
        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
        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
        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
      # Prism::ImplicitRestNode (tap{|a,|}) does not have a name
      if node.rest.is_a?(Prism::RestParameterNode) && node.rest.name
        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(maximum, scope, args, _kwargs)
      if maximum == 1
        scope['_1'] = args.first || Types::NIL
      else
        args = sized_splat(args.first, :to_ary, maximum) if args.size == 1
        maximum.times do |index|
          scope["_#{index + 1}"] = 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)
      pattern = pattern.body while pattern.is_a?(Prism::ParenthesesNode)

      # 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::CallTargetNode, Prism::IndexTargetNode
        evaluated_receivers&.[](node.receiver) || evaluate_multi_write_receiver(node, scope, nil)
      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.name.to_s, value, scope
      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::CallTargetNode, Prism::CallNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        receiver
      when Prism::IndexTargetNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        if node.arguments
          node.arguments.arguments&.each do |arg|
            if arg.is_a? Prism::SplatNode
              evaluate arg.expression, scope if arg.expression
            else
              evaluate arg, scope
            end
          end
        end
        evaluate node.block.expression, scope if node.block&.expression
        receiver
      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 && method.block.type.respond_to?(:required_positionals)
          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

evaluate_singleton_class_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 620
    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

evaluate_source_encoding_node(_node, _scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 213
    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_it_local_variable_read_node(_node, scope)
      # `it` is not a normal local variable. It can be overridden like `tap{p it; it=1}`.
      # Use the name `_1` instead of `it` to avoid conflict.
      scope['_1'] || Types::NIL
    end

    def evaluate_call_node(node, scope)
      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|
              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
              case node.block.parameters
              when Prism::NumberedParametersNode
                assign_numbered_parameters node.block.parameters.maximum, block_scope, block_args, {}
              when Prism::BlockParametersNode
                assign_parameters node.block.parameters.parameters, block_scope, block_args, {}
              when Prism::ItParametersNode
                scope['_1'] = block_args.first || Types::NIL
              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 node.attribute_write?
          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.binary_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.binary_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.binary_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.binary_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.name.to_s, value, scope
      value
    end

    def evaluate_lambda_node(node, scope)
      local_table = node.locals.to_h { [_1.to_s, Types::OBJECT] }

      # `it` is not added to local_table because it is not a normal local variable.
      # We need to explicitly add it to the scope.
      local_table['_1'] = Types::OBJECT if node.parameters.is_a?(Prism::ItParametersNode)

      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.is_a?(Prism::ParametersNode) && 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 node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.subsequent ? evaluate(node.subsequent, _1) : Types::NIL }
      )]
    end

    def evaluate_unless_node(node, scope)
      evaluate node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.else_clause ? evaluate(node.else_clause, _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]
          evaluate_write node.reference, error_type, s, nil
        end
        node.statements ? evaluate(node.statements, s) : Types::NIL
      end
      if node.subsequent # begin; rescue A; rescue B; end
        types = scope.run_branches(
          run_rescue,
          -> { evaluate node.subsequent, _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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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)
      # /(?<a>)(?<b>)/ =~ 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)
      name = node.name.to_s
      case node
      when Prism::ConstantPathNode
        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
        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
        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
      # Prism::ImplicitRestNode (tap{|a,|}) does not have a name
      if node.rest.is_a?(Prism::RestParameterNode) && node.rest.name
        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(maximum, scope, args, _kwargs)
      if maximum == 1
        scope['_1'] = args.first || Types::NIL
      else
        args = sized_splat(args.first, :to_ary, maximum) if args.size == 1
        maximum.times do |index|
          scope["_#{index + 1}"] = 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)
      pattern = pattern.body while pattern.is_a?(Prism::ParenthesesNode)

      # 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::CallTargetNode, Prism::IndexTargetNode
        evaluated_receivers&.[](node.receiver) || evaluate_multi_write_receiver(node, scope, nil)
      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.name.to_s, value, scope
      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::CallTargetNode, Prism::CallNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        receiver
      when Prism::IndexTargetNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        if node.arguments
          node.arguments.arguments&.each do |arg|
            if arg.is_a? Prism::SplatNode
              evaluate arg.expression, scope if arg.expression
            else
              evaluate arg, scope
            end
          end
        end
        evaluate node.block.expression, scope if node.block&.expression
        receiver
      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 && method.block.type.respond_to?(:required_positionals)
          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

evaluate_source_file_node(_node, _scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 209
    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_it_local_variable_read_node(_node, scope)
      # `it` is not a normal local variable. It can be overridden like `tap{p it; it=1}`.
      # Use the name `_1` instead of `it` to avoid conflict.
      scope['_1'] || Types::NIL
    end

    def evaluate_call_node(node, scope)
      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|
              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
              case node.block.parameters
              when Prism::NumberedParametersNode
                assign_numbered_parameters node.block.parameters.maximum, block_scope, block_args, {}
              when Prism::BlockParametersNode
                assign_parameters node.block.parameters.parameters, block_scope, block_args, {}
              when Prism::ItParametersNode
                scope['_1'] = block_args.first || Types::NIL
              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 node.attribute_write?
          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.binary_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.binary_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.binary_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.binary_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.name.to_s, value, scope
      value
    end

    def evaluate_lambda_node(node, scope)
      local_table = node.locals.to_h { [_1.to_s, Types::OBJECT] }

      # `it` is not added to local_table because it is not a normal local variable.
      # We need to explicitly add it to the scope.
      local_table['_1'] = Types::OBJECT if node.parameters.is_a?(Prism::ItParametersNode)

      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.is_a?(Prism::ParametersNode) && 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 node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.subsequent ? evaluate(node.subsequent, _1) : Types::NIL }
      )]
    end

    def evaluate_unless_node(node, scope)
      evaluate node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.else_clause ? evaluate(node.else_clause, _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]
          evaluate_write node.reference, error_type, s, nil
        end
        node.statements ? evaluate(node.statements, s) : Types::NIL
      end
      if node.subsequent # begin; rescue A; rescue B; end
        types = scope.run_branches(
          run_rescue,
          -> { evaluate node.subsequent, _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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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)
      # /(?<a>)(?<b>)/ =~ 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)
      name = node.name.to_s
      case node
      when Prism::ConstantPathNode
        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
        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
        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
      # Prism::ImplicitRestNode (tap{|a,|}) does not have a name
      if node.rest.is_a?(Prism::RestParameterNode) && node.rest.name
        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(maximum, scope, args, _kwargs)
      if maximum == 1
        scope['_1'] = args.first || Types::NIL
      else
        args = sized_splat(args.first, :to_ary, maximum) if args.size == 1
        maximum.times do |index|
          scope["_#{index + 1}"] = 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)
      pattern = pattern.body while pattern.is_a?(Prism::ParenthesesNode)

      # 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::CallTargetNode, Prism::IndexTargetNode
        evaluated_receivers&.[](node.receiver) || evaluate_multi_write_receiver(node, scope, nil)
      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.name.to_s, value, scope
      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::CallTargetNode, Prism::CallNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        receiver
      when Prism::IndexTargetNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        if node.arguments
          node.arguments.arguments&.each do |arg|
            if arg.is_a? Prism::SplatNode
              evaluate arg.expression, scope if arg.expression
            else
              evaluate arg, scope
            end
          end
        end
        evaluate node.block.expression, scope if node.block&.expression
        receiver
      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 && method.block.type.respond_to?(:required_positionals)
          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(

evaluate_source_line_node(_node, _scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 211
    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_it_local_variable_read_node(_node, scope)
      # `it` is not a normal local variable. It can be overridden like `tap{p it; it=1}`.
      # Use the name `_1` instead of `it` to avoid conflict.
      scope['_1'] || Types::NIL
    end

    def evaluate_call_node(node, scope)
      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|
              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
              case node.block.parameters
              when Prism::NumberedParametersNode
                assign_numbered_parameters node.block.parameters.maximum, block_scope, block_args, {}
              when Prism::BlockParametersNode
                assign_parameters node.block.parameters.parameters, block_scope, block_args, {}
              when Prism::ItParametersNode
                scope['_1'] = block_args.first || Types::NIL
              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 node.attribute_write?
          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.binary_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.binary_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.binary_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.binary_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.name.to_s, value, scope
      value
    end

    def evaluate_lambda_node(node, scope)
      local_table = node.locals.to_h { [_1.to_s, Types::OBJECT] }

      # `it` is not added to local_table because it is not a normal local variable.
      # We need to explicitly add it to the scope.
      local_table['_1'] = Types::OBJECT if node.parameters.is_a?(Prism::ItParametersNode)

      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.is_a?(Prism::ParametersNode) && 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 node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.subsequent ? evaluate(node.subsequent, _1) : Types::NIL }
      )]
    end

    def evaluate_unless_node(node, scope)
      evaluate node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.else_clause ? evaluate(node.else_clause, _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]
          evaluate_write node.reference, error_type, s, nil
        end
        node.statements ? evaluate(node.statements, s) : Types::NIL
      end
      if node.subsequent # begin; rescue A; rescue B; end
        types = scope.run_branches(
          run_rescue,
          -> { evaluate node.subsequent, _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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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)
      # /(?<a>)(?<b>)/ =~ 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)
      name = node.name.to_s
      case node
      when Prism::ConstantPathNode
        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
        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
        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
      # Prism::ImplicitRestNode (tap{|a,|}) does not have a name
      if node.rest.is_a?(Prism::RestParameterNode) && node.rest.name
        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(maximum, scope, args, _kwargs)
      if maximum == 1
        scope['_1'] = args.first || Types::NIL
      else
        args = sized_splat(args.first, :to_ary, maximum) if args.size == 1
        maximum.times do |index|
          scope["_#{index + 1}"] = 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)
      pattern = pattern.body while pattern.is_a?(Prism::ParenthesesNode)

      # 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::CallTargetNode, Prism::IndexTargetNode
        evaluated_receivers&.[](node.receiver) || evaluate_multi_write_receiver(node, scope, nil)
      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.name.to_s, value, scope
      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::CallTargetNode, Prism::CallNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        receiver
      when Prism::IndexTargetNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        if node.arguments
          node.arguments.arguments&.each do |arg|
            if arg.is_a? Prism::SplatNode
              evaluate arg.expression, scope if arg.expression
            else
              evaluate arg, scope
            end
          end
        end
        evaluate node.block.expression, scope if node.block&.expression
        receiver
      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 && method.block.type.respond_to?(:required_positionals)
          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,

evaluate_splat_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 777
    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

evaluate_statements_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 57
    def evaluate_statements_node(node, scope)
      if node.body.empty?
        Types::NIL
      else
        node.body.map { evaluate _1, scope }.last
      end
    end

evaluate_string_concat_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 122
    def evaluate_string_concat_node(node, scope)
      evaluate node.left, scope
      evaluate node.right, scope
      Types::STRING
    end

evaluate_string_node(_node, _scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 112
    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_it_local_variable_read_node(_node, scope)
      # `it` is not a normal local variable. It can be overridden like `tap{p it; it=1}`.
      # Use the name `_1` instead of `it` to avoid conflict.
      scope['_1'] || Types::NIL
    end

    def evaluate_call_node(node, scope)
      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|
              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
              case node.block.parameters
              when Prism::NumberedParametersNode
                assign_numbered_parameters node.block.parameters.maximum, block_scope, block_args, {}
              when Prism::BlockParametersNode
                assign_parameters node.block.parameters.parameters, block_scope, block_args, {}
              when Prism::ItParametersNode
                scope['_1'] = block_args.first || Types::NIL
              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 node.attribute_write?
          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.binary_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.binary_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.binary_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.binary_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.name.to_s, value, scope
      value
    end

    def evaluate_lambda_node(node, scope)
      local_table = node.locals.to_h { [_1.to_s, Types::OBJECT] }

      # `it` is not added to local_table because it is not a normal local variable.
      # We need to explicitly add it to the scope.
      local_table['_1'] = Types::OBJECT if node.parameters.is_a?(Prism::ItParametersNode)

      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.is_a?(Prism::ParametersNode) && 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 node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.subsequent ? evaluate(node.subsequent, _1) : Types::NIL }
      )]
    end

    def evaluate_unless_node(node, scope)
      evaluate node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.else_clause ? evaluate(node.else_clause, _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]
          evaluate_write node.reference, error_type, s, nil
        end
        node.statements ? evaluate(node.statements, s) : Types::NIL
      end
      if node.subsequent # begin; rescue A; rescue B; end
        types = scope.run_branches(
          run_rescue,
          -> { evaluate node.subsequent, _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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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)
      # /(?<a>)(?<b>)/ =~ 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)
      name = node.name.to_s
      case node
      when Prism::ConstantPathNode
        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
        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
        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
      # Prism::ImplicitRestNode (tap{|a,|}) does not have a name
      if node.rest.is_a?(Prism::RestParameterNode) && node.rest.name
        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(maximum, scope, args, _kwargs)
      if maximum == 1
        scope['_1'] = args.first || Types::NIL
      else
        args = sized_splat(args.first, :to_ary, maximum) if args.size == 1
        maximum.times do |index|
          scope["_#{index + 1}"] = 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)
      pattern = pattern.body while pattern.is_a?(Prism::ParenthesesNode)

      # 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::CallTargetNode, Prism::IndexTargetNode
        evaluated_receivers&.[](node.receiver) || evaluate_multi_write_receiver(node, scope, nil)
      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.name.to_s, value, scope
      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::CallTargetNode, Prism::CallNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        receiver
      when Prism::IndexTargetNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        if node.arguments
          node.arguments.arguments&.each do |arg|
            if arg.is_a? Prism::SplatNode
              evaluate arg.expression, scope if arg.expression
            else
              evaluate arg, scope
            end
          end
        end
        evaluate node.block.expression, scope if node.block&.expression
        receiver
      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 && method.block.type.respond_to?(:required_positionals)
          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

evaluate_super_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 562
    def evaluate_super_node(node, scope)
      evaluate_list_splat_items node.arguments.arguments, scope if node.arguments
      Types::OBJECT
    end

evaluate_symbol_node(_node, _scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 118
    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_it_local_variable_read_node(_node, scope)
      # `it` is not a normal local variable. It can be overridden like `tap{p it; it=1}`.
      # Use the name `_1` instead of `it` to avoid conflict.
      scope['_1'] || Types::NIL
    end

    def evaluate_call_node(node, scope)
      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|
              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
              case node.block.parameters
              when Prism::NumberedParametersNode
                assign_numbered_parameters node.block.parameters.maximum, block_scope, block_args, {}
              when Prism::BlockParametersNode
                assign_parameters node.block.parameters.parameters, block_scope, block_args, {}
              when Prism::ItParametersNode
                scope['_1'] = block_args.first || Types::NIL
              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 node.attribute_write?
          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.binary_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.binary_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.binary_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.binary_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.name.to_s, value, scope
      value
    end

    def evaluate_lambda_node(node, scope)
      local_table = node.locals.to_h { [_1.to_s, Types::OBJECT] }

      # `it` is not added to local_table because it is not a normal local variable.
      # We need to explicitly add it to the scope.
      local_table['_1'] = Types::OBJECT if node.parameters.is_a?(Prism::ItParametersNode)

      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.is_a?(Prism::ParametersNode) && 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 node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.subsequent ? evaluate(node.subsequent, _1) : Types::NIL }
      )]
    end

    def evaluate_unless_node(node, scope)
      evaluate node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.else_clause ? evaluate(node.else_clause, _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]
          evaluate_write node.reference, error_type, s, nil
        end
        node.statements ? evaluate(node.statements, s) : Types::NIL
      end
      if node.subsequent # begin; rescue A; rescue B; end
        types = scope.run_branches(
          run_rescue,
          -> { evaluate node.subsequent, _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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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)
      # /(?<a>)(?<b>)/ =~ 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)
      name = node.name.to_s
      case node
      when Prism::ConstantPathNode
        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
        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
        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
      # Prism::ImplicitRestNode (tap{|a,|}) does not have a name
      if node.rest.is_a?(Prism::RestParameterNode) && node.rest.name
        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(maximum, scope, args, _kwargs)
      if maximum == 1
        scope['_1'] = args.first || Types::NIL
      else
        args = sized_splat(args.first, :to_ary, maximum) if args.size == 1
        maximum.times do |index|
          scope["_#{index + 1}"] = 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)
      pattern = pattern.body while pattern.is_a?(Prism::ParenthesesNode)

      # 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::CallTargetNode, Prism::IndexTargetNode
        evaluated_receivers&.[](node.receiver) || evaluate_multi_write_receiver(node, scope, nil)
      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.name.to_s, value, scope
      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::CallTargetNode, Prism::CallNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        receiver
      when Prism::IndexTargetNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        if node.arguments
          node.arguments.arguments&.each do |arg|
            if arg.is_a? Prism::SplatNode
              evaluate arg.expression, scope if arg.expression
            else
              evaluate arg, scope
            end
          end
        end
        evaluate node.block.expression, scope if node.block&.expression
        receiver
      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 && method.block.type.respond_to?(:required_positionals)
          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

evaluate_true_node(_node, _scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 203
    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_it_local_variable_read_node(_node, scope)
      # `it` is not a normal local variable. It can be overridden like `tap{p it; it=1}`.
      # Use the name `_1` instead of `it` to avoid conflict.
      scope['_1'] || Types::NIL
    end

    def evaluate_call_node(node, scope)
      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|
              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
              case node.block.parameters
              when Prism::NumberedParametersNode
                assign_numbered_parameters node.block.parameters.maximum, block_scope, block_args, {}
              when Prism::BlockParametersNode
                assign_parameters node.block.parameters.parameters, block_scope, block_args, {}
              when Prism::ItParametersNode
                scope['_1'] = block_args.first || Types::NIL
              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 node.attribute_write?
          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.binary_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.binary_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.binary_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.binary_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.name.to_s, value, scope
      value
    end

    def evaluate_lambda_node(node, scope)
      local_table = node.locals.to_h { [_1.to_s, Types::OBJECT] }

      # `it` is not added to local_table because it is not a normal local variable.
      # We need to explicitly add it to the scope.
      local_table['_1'] = Types::OBJECT if node.parameters.is_a?(Prism::ItParametersNode)

      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.is_a?(Prism::ParametersNode) && 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 node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.subsequent ? evaluate(node.subsequent, _1) : Types::NIL }
      )]
    end

    def evaluate_unless_node(node, scope)
      evaluate node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.else_clause ? evaluate(node.else_clause, _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]
          evaluate_write node.reference, error_type, s, nil
        end
        node.statements ? evaluate(node.statements, s) : Types::NIL
      end
      if node.subsequent # begin; rescue A; rescue B; end
        types = scope.run_branches(
          run_rescue,
          -> { evaluate node.subsequent, _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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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.else_clause
        branches << ->(s) { evaluate node.else_clause, 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)
      # /(?<a>)(?<b>)/ =~ 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)
      name = node.name.to_s
      case node
      when Prism::ConstantPathNode
        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
        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
        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
      # Prism::ImplicitRestNode (tap{|a,|}) does not have a name
      if node.rest.is_a?(Prism::RestParameterNode) && node.rest.name
        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(maximum, scope, args, _kwargs)
      if maximum == 1
        scope['_1'] = args.first || Types::NIL
      else
        args = sized_splat(args.first, :to_ary, maximum) if args.size == 1
        maximum.times do |index|
          scope["_#{index + 1}"] = 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)
      pattern = pattern.body while pattern.is_a?(Prism::ParenthesesNode)

      # 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::CallTargetNode, Prism::IndexTargetNode
        evaluated_receivers&.[](node.receiver) || evaluate_multi_write_receiver(node, scope, nil)
      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.name.to_s, value, scope
      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::CallTargetNode, Prism::CallNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        receiver
      when Prism::IndexTargetNode
        receiver = evaluate(node.receiver, scope)
        evaluated_receivers[node.receiver] = receiver if evaluated_receivers
        if node.arguments
          node.arguments.arguments&.each do |arg|
            if arg.is_a? Prism::SplatNode
              evaluate arg.expression, scope if arg.expression
            else
              evaluate arg, scope
            end
          end
        end
        evaluate node.block.expression, scope if node.block&.expression
        receiver
      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 && method.block.type.respond_to?(:required_positionals)
          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

evaluate_undef_node(_node, _scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 814
        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)
          name = node.name.to_s
          case node
          when Prism::ConstantPathNode
            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
            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
            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
          # Prism::ImplicitRestNode (tap{|a,|}) does not have a name
          if node.rest.is_a?(Prism::RestParameterNode) && node.rest.name
            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(maximum, scope, args, _kwargs)
          if maximum == 1
            scope['_1'] = args.first || Types::NIL
          else
            args = sized_splat(args.first, :to_ary, maximum) if args.size == 1
            maximum.times do |index|
              scope["_#{index + 1}"] = 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)
          pattern = pattern.body while pattern.is_a?(Prism::ParenthesesNode)

          # 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::CallTargetNode, Prism::IndexTargetNode
            evaluated_receivers&.[](node.receiver) || evaluate_multi_write_receiver(node, scope, nil)
          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.name.to_s, value, scope
          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::CallTargetNode, Prism::CallNode
            receiver = evaluate(node.receiver, scope)
            evaluated_receivers[node.receiver] = receiver if evaluated_receivers
            receiver
          when Prism::IndexTargetNode
            receiver = evaluate(node.receiver, scope)
            evaluated_receivers[node.receiver] = receiver if evaluated_receivers
            if node.arguments
              node.arguments.arguments&.each do |arg|
                if arg.is_a? Prism::SplatNode
                  evaluate arg.expression, scope if arg.expression
                else
                  evaluate arg, scope
                end
              end
            end
            evaluate node.block.expression, scope if node.block&.expression
            receiver
          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 && method.block.type.respond_to?(:required_positionals)
              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

evaluate_unless_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 489
    def evaluate_unless_node(node, scope)
      evaluate node.predicate, scope
      Types::UnionType[*scope.run_branches(
        -> { node.statements ? evaluate(node.statements, _1) : Types::NIL },
        -> { node.else_clause ? evaluate(node.else_clause, _1) : Types::NIL }
      )]
    end

evaluate_variable_and_write(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 366
    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

evaluate_variable_operator_write(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 356
    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.binary_operator, [right], nil, nil, scope, name_match: false
    end

evaluate_variable_or_write(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 375
    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

evaluate_while_until(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 501
    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

evaluate_write(node, value, scope, evaluated_receivers) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 1045
    def evaluate_write(node, value, scope, evaluated_receivers)
      case node
      when Prism::MultiTargetNode
        evaluate_multi_write node, value, scope, evaluated_receivers
      when Prism::CallTargetNode, Prism::IndexTargetNode
        evaluated_receivers&.[](node.receiver) || evaluate_multi_write_receiver(node, scope, nil)
      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.name.to_s, value, scope
      end
    end

evaluate_x_string_node(_node, _scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 114
    def evaluate_x_string_node(_node, _scope)
      Types::UnionType[Types::STRING, Types::NIL]
    end

evaluate_yield_node(node, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 545
    def evaluate_yield_node(node, scope)
      evaluate_list_splat_items node.arguments.arguments, scope if node.arguments
      Types::OBJECT
    end

method_call(receiver, method_name, args, kwargs, block, scope, name_match: true) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 1141
    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 && method.block.type.respond_to?(:required_positionals)
          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

partition_to_array(value, method) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 1125
    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

resolve(type, scope) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 19
    def resolve(type, scope)
      @block.call type, scope
    end

sized_splat(value, method, size) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 1118
    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

target?(node) click to toggle source

    # File repl_type_completor-0.1.9/lib/repl_type_completor/type_analyzer.rb, line 18
      def target?(node) = @target_id == node.__id__
      def resolve(type, scope)
        @block.call type, scope
      end
    end