class Dry::Core::Memoizable::Memoizer

@api private

def declaration(definition, lookup)

Other tags:
    Api: - private
def declaration(definition, lookup)
  params = []
  binds = []
  defined = {}
  definition.each do |type, name|
    mapped_type = map_bind_type(type, name, lookup, defined) do
      raise ::NotImplementedError, "type: #{type}, name: #{name}"
    end
    if mapped_type
      defined[mapped_type] = true
      bind = name_from_param(name) || make_bind_name(binds.size)
      binds << bind
      params << param(bind, mapped_type)
    end
  end
  [params, binds]
end

def define_memoizable(method:)

Other tags:
    Api: - private
def define_memoizable(method:)
  parameters = method.parameters
  mod = self
  kernel = KERNEL
  if parameters.empty?
    key = "#{object_id}:#{method.name}".hash.abs
    define_method(method.name) do
      value = super()
      if kernel[:frozen].bind_call(self)
        # It's not possible to modify singleton classes
        # of frozen objects
        mod.remove_method(method.name)
        mod.module_eval(<<~RUBY, __FILE__, __LINE__ + 1)
          def #{method.name}                          # def slow_calc
            cached = @__memoized__[#{key}]            #   cached = @__memoized__[12345678]
                                                      #
            if cached || @__memoized__.key?(#{key})   #   if cached || @__memoized__.key?(12345678)
              cached                                  #     cached
            else                                      #   else
              @__memoized__[#{key}] = super           #     @__memoized__[12345678] = super
            end                                       #   end
          end                                         # end
        RUBY
      else
        # We make an attr_reader for computed value.
        # Readers are "special-cased" in ruby so such
        # access will be the fastest way, faster than you'd
        # expect :)
        attr_name = :"__memozed_#{key}__"
        ivar_name = :"@#{attr_name}"
        kernel[:ivar_set].bind_call(self, ivar_name, value)
        eigenclass = kernel[:singleton].bind_call(self)
        eigenclass.attr_reader(attr_name)
        eigenclass.alias_method(method.name, attr_name)
        eigenclass.remove_method(attr_name)
      end
      value
    end
  else
    mapping = parameters.to_h { |k, v = nil| [k, v] }
    params, binds = declaration(parameters, mapping)
    last_param = parameters.last
    if last_param[0].eql?(:block) && !last_param[1].eql?(:&)
      Deprecations.warn(<<~WARN)
        Memoization for block-accepting methods isn't safe.
        Every call creates a new block instance bloating cached results.
        In the future, blocks will still be allowed but won't participate in
        cache key calculation.
      WARN
    end
    m = module_eval(<<~RUBY, __FILE__, __LINE__ + 1)
      def #{method.name}(#{params.join(", ")})                 # def slow_calc(arg1, arg2, arg3)
        key = [:"#{method.name}", #{binds.join(", ")}].hash    #   key = [:slow_calc, arg1, arg2, arg3].hash
                                                               #
        if @__memoized__.key?(key)                             #   if @__memoized__.key?(key)
          @__memoized__[key]                                   #     @__memoized__[key]
        else                                                   #   else
          @__memoized__[key] = super                           #     @__memoized__[key] = super
        end                                                    #   end
      end                                                      # end
    RUBY
    if respond_to?(:ruby2_keywords, true) && mapping.key?(:reyrest)
      ruby2_keywords(method.name)
    end
    m
  end
end

def initialize(klass, names)

Other tags:
    Api: - private
def initialize(klass, names)
  super()
  names.each do |name|
    define_memoizable(
      method: klass.instance_method(name)
    )
  end
end

def make_bind_name(idx)

Other tags:
    Api: - private
def make_bind_name(idx)
  :"__lv_#{idx}__"
end

def map_bind_type(type, name, original_params, defined_types) # rubocop:disable Metrics/PerceivedComplexity

Other tags:
    Api: - private
def map_bind_type(type, name, original_params, defined_types) # rubocop:disable Metrics/PerceivedComplexity
  case type
  when :req
    :reqular
  when :rest, :keyreq, :keyrest
    type
  when :block
    if name.eql?(:&)
      # most likely this is a case of delegation
      # rather than actual block
      nil
    else
      type
    end
  when :opt
    if original_params.key?(:rest) || defined_types[:rest]
      nil
    else
      :rest
    end
  when :key
    if original_params.key?(:keyrest) || defined_types[:keyrest]
      nil
    else
      :keyrest
    end
  else
    yield
  end
end

def name_from_param(name)

Other tags:
    Api: - private
def name_from_param(name)
  if PARAM_PLACEHOLDERS.include?(name)
    nil
  else
    name
  end
end

def param(name, type)

Other tags:
    Api: - private
def param(name, type)
  case type
  when :reqular
    name
  when :rest
    "*#{name}"
  when :keyreq
    "#{name}:"
  when :keyrest
    "**#{name}"
  when :block
    "&#{name}"
  end
end