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