# frozen_string_literal: truemoduleDrymoduleCoremoduleMemoizableMEMOIZED_HASH={}.freezePARAM_PLACEHOLDERS=%i[* ** &].freezemoduleClassInterfacemoduleBasedefmemoize(*names)prepend(Memoizer.new(self,names))enddefinherited(base)supermemoizer=base.ancestors.find{_1.is_a?(Memoizer)}base.prepend(memoizer.dup)ifmemoizerendendmoduleBasicObjectincludeBasedefnew(*,**)obj=superobj.instance_eval{@__memoized__=MEMOIZED_HASH.dup}objendendmoduleObjectincludeBasedefnew(*,**)obj=superobj.instance_variable_set(:@__memoized__,MEMOIZED_HASH.dup)objendendenddefself.included(klass)superifklass<=Objectklass.extend(ClassInterface::Object)elseklass.extend(ClassInterface::BasicObject)endend# @api privateclassMemoizer<::ModuleKERNEL={singleton: ::Kernel.instance_method(:singleton_class),ivar_set: ::Kernel.instance_method(:instance_variable_set),frozen: ::Kernel.instance_method(:frozen?)}.freeze# @api privatedefinitialize(klass,names)super()names.eachdo|name|define_memoizable(method: klass.instance_method(name))endendprivate# @api private# rubocop:disable Metrics/AbcSizedefdefine_memoizable(method:)parameters=method.parametersmod=selfkernel=KERNELifparameters.empty?key="#{__id__}:#{method.name}".hash.absdefine_method(method.name)dovalue=super()ifkernel[:frozen].bind_call(self)# It's not possible to modify singleton classes# of frozen objectsmod.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
RUBYelse# 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)endvalueendelsemapping=parameters.to_h{|k,v=nil|[k,v]}params,binds=declaration(parameters,mapping)last_param=parameters.lastiflast_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.
WARNendmodule_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
RUBYendend# rubocop:enable Metrics/AbcSize# @api privatedefdeclaration(definition,lookup)params=[]binds=[]defined={}definition.eachdo|type,name|mapped_type=map_bind_type(type,name,lookup,defined)doraise::NotImplementedError,"type: #{type}, name: #{name}"endifmapped_typedefined[mapped_type]=truebind=name_from_param(name)||make_bind_name(binds.size)binds<<bindparams<<param(bind,mapped_type)endend[params,binds]end# @api privatedefname_from_param(name)ifPARAM_PLACEHOLDERS.include?(name)nilelsenameendend# @api privatedefmake_bind_name(idx):"__lv_#{idx}__"end# @api privatedefmap_bind_type(type,name,original_params,defined_types)# rubocop:disable Metrics/PerceivedComplexitycasetypewhen:req:reqularwhen:rest,:keyreq,:keyresttypewhen:blockifname.eql?(:&)# most likely this is a case of delegation# rather than actual blocknilelsetypeendwhen:optiforiginal_params.key?(:rest)||defined_types[:rest]nilelse:restendwhen:keyiforiginal_params.key?(:keyrest)||defined_types[:keyrest]nilelse:keyrestendelseyieldendend# @api privatedefparam(name,type)casetypewhen:reqularnamewhen:rest"*#{name}"when:keyreq"#{name}:"when:keyrest"**#{name}"when:block"&#{name}"endendendendendend