lib/memo_wise.rb in memo_wise-1.3.0 vs lib/memo_wise.rb in memo_wise-1.4.0

- old
+ new

@@ -167,50 +167,87 @@ HEREDOC end raise ArgumentError, "#{method_name.inspect} must be a Symbol" unless method_name.is_a?(Symbol) - api = MemoWise::InternalAPI.new(klass) - visibility = api.method_visibility(method_name) + visibility = MemoWise::InternalAPI.method_visibility(klass, method_name) original_memo_wised_name = MemoWise::InternalAPI.original_memo_wised_name(method_name) method = klass.instance_method(method_name) klass.send(:alias_method, original_memo_wised_name, method_name) klass.send(:private, original_memo_wised_name) method_arguments = MemoWise::InternalAPI.method_arguments(method) - index = MemoWise::InternalAPI.next_index!(klass, method_name) case method_arguments when MemoWise::InternalAPI::NONE # Zero-arg methods can use simpler/more performant logic because the # hash key is just the method name. - klass.module_eval <<~HEREDOC, __FILE__, __LINE__ + 1 - def #{method_name} - if @_memo_wise_sentinels[#{index}] - @_memo_wise[#{index}] - else - ret = @_memo_wise[#{index}] = #{original_memo_wised_name} - @_memo_wise_sentinels[#{index}] = true - ret + klass.send(:define_method, method_name) do # Ruby 2.4's `define_method` is private in some cases + index = MemoWise::InternalAPI.index(self, method_name) + klass.module_eval <<~HEREDOC, __FILE__, __LINE__ + 1 + def #{method_name} + if @_memo_wise_sentinels[#{index}] + @_memo_wise[#{index}] + else + ret = @_memo_wise[#{index}] = #{original_memo_wised_name} + @_memo_wise_sentinels[#{index}] = true + ret + end end - end - HEREDOC + HEREDOC + + klass.send(visibility, method_name) + send(method_name) + end when MemoWise::InternalAPI::ONE_REQUIRED_POSITIONAL, MemoWise::InternalAPI::ONE_REQUIRED_KEYWORD key = method.parameters.first.last + # NOTE: Ruby 2.6 and below, and TruffleRuby 3.0, break when we use + # `define_method(...) do |*args, **kwargs|`. Instead we must use the + # simpler `|*args|` pattern. We can't just do this always though + # because Ruby 2.7 and above require `|*args, **kwargs|` to work + # correctly. + # See: https://blog.saeloun.com/2019/10/07/ruby-2-7-keyword-arguments-redesign.html#ruby-26 + # :nocov: + if RUBY_VERSION < "2.7" || RUBY_ENGINE == "truffleruby" + klass.send(:define_method, method_name) do |*args| # Ruby 2.4's `define_method` is private in some cases + index = MemoWise::InternalAPI.index(self, method_name) + klass.module_eval <<~HEREDOC, __FILE__, __LINE__ + 1 + def #{method_name}(#{MemoWise::InternalAPI.args_str(method)}) + _memo_wise_hash = (@_memo_wise[#{index}] ||= {}) + _memo_wise_output = _memo_wise_hash[#{key}] + if _memo_wise_output || _memo_wise_hash.key?(#{key}) + _memo_wise_output + else + _memo_wise_hash[#{key}] = #{original_memo_wised_name}(#{MemoWise::InternalAPI.call_str(method)}) + end + end + HEREDOC - klass.module_eval <<~HEREDOC, __FILE__, __LINE__ + 1 - def #{method_name}(#{MemoWise::InternalAPI.args_str(method)}) - _memo_wise_hash = (@_memo_wise[#{index}] ||= {}) - _memo_wise_output = _memo_wise_hash[#{key}] - if _memo_wise_output || _memo_wise_hash.key?(#{key}) - _memo_wise_output - else - _memo_wise_hash[#{key}] = #{original_memo_wised_name}(#{MemoWise::InternalAPI.call_str(method)}) - end + klass.send(visibility, method_name) + send(method_name, *args) end - HEREDOC + # :nocov: + else + klass.define_method(method_name) do |*args, **kwargs| + index = MemoWise::InternalAPI.index(self, method_name) + klass.module_eval <<~HEREDOC, __FILE__, __LINE__ + 1 + def #{method_name}(#{MemoWise::InternalAPI.args_str(method)}) + _memo_wise_hash = (@_memo_wise[#{index}] ||= {}) + _memo_wise_output = _memo_wise_hash[#{key}] + if _memo_wise_output || _memo_wise_hash.key?(#{key}) + _memo_wise_output + else + _memo_wise_hash[#{key}] = #{original_memo_wised_name}(#{MemoWise::InternalAPI.call_str(method)}) + end + end + HEREDOC + + klass.send(visibility, method_name) + send(method_name, *args, **kwargs) + end + end # MemoWise::InternalAPI::MULTIPLE_REQUIRED, MemoWise::InternalAPI::SPLAT, # MemoWise::InternalAPI::DOUBLE_SPLAT, MemoWise::InternalAPI::SPLAT_AND_DOUBLE_SPLAT else # NOTE: When benchmarking this implementation against something like: # @@ -222,22 +259,58 @@ # is because this case uses a more complex hash key (see # `MemoWise::InternalAPI.key_str`), and hashing that key has less # consistent performance. In general, this should still be faster for # truthy results because `Hash#[]` generally performs hash lookups # faster than `Hash#fetch`. - klass.module_eval <<~HEREDOC, __FILE__, __LINE__ + 1 - def #{method_name}(#{MemoWise::InternalAPI.args_str(method)}) - _memo_wise_hash = (@_memo_wise[#{index}] ||= {}) - _memo_wise_key = #{MemoWise::InternalAPI.key_str(method)} - _memo_wise_output = _memo_wise_hash[_memo_wise_key] - if _memo_wise_output || _memo_wise_hash.key?(_memo_wise_key) - _memo_wise_output - else - _memo_wise_hash[_memo_wise_key] = #{original_memo_wised_name}(#{MemoWise::InternalAPI.call_str(method)}) - end + # + # NOTE: Ruby 2.6 and below, and TruffleRuby 3.0, break when we use + # `define_method(...) do |*args, **kwargs|`. Instead we must use the + # simpler `|*args|` pattern. We can't just do this always though + # because Ruby 2.7 and above require `|*args, **kwargs|` to work + # correctly. + # See: https://blog.saeloun.com/2019/10/07/ruby-2-7-keyword-arguments-redesign.html#ruby-26 + # :nocov: + if RUBY_VERSION < "2.7" || RUBY_ENGINE == "truffleruby" + klass.send(:define_method, method_name) do |*args| # Ruby 2.4's `define_method` is private in some cases + index = MemoWise::InternalAPI.index(self, method_name) + klass.module_eval <<~HEREDOC, __FILE__, __LINE__ + 1 + def #{method_name}(#{MemoWise::InternalAPI.args_str(method)}) + _memo_wise_hash = (@_memo_wise[#{index}] ||= {}) + _memo_wise_key = #{MemoWise::InternalAPI.key_str(method)} + _memo_wise_output = _memo_wise_hash[_memo_wise_key] + if _memo_wise_output || _memo_wise_hash.key?(_memo_wise_key) + _memo_wise_output + else + _memo_wise_hash[_memo_wise_key] = #{original_memo_wised_name}(#{MemoWise::InternalAPI.call_str(method)}) + end + end + HEREDOC + + klass.send(visibility, method_name) + send(method_name, *args) end - HEREDOC + # :nocov: + else # Ruby 2.7 and above break with (*args) + klass.define_method(method_name) do |*args, **kwargs| + index = MemoWise::InternalAPI.index(self, method_name) + klass.module_eval <<~HEREDOC, __FILE__, __LINE__ + 1 + def #{method_name}(#{MemoWise::InternalAPI.args_str(method)}) + _memo_wise_hash = (@_memo_wise[#{index}] ||= {}) + _memo_wise_key = #{MemoWise::InternalAPI.key_str(method)} + _memo_wise_output = _memo_wise_hash[_memo_wise_key] + if _memo_wise_output || _memo_wise_hash.key?(_memo_wise_key) + _memo_wise_output + else + _memo_wise_hash[_memo_wise_key] = #{original_memo_wised_name}(#{MemoWise::InternalAPI.call_str(method)}) + end + end + HEREDOC + + klass.send(visibility, method_name) + send(method_name, *args, **kwargs) + end + end end klass.send(visibility, method_name) end end @@ -438,16 +511,15 @@ # ex.method_called_times #=> nil # def preset_memo_wise(method_name, *args, **kwargs) raise ArgumentError, "Pass a block as the value to preset for #{method_name}, #{args}" unless block_given? - api = MemoWise::InternalAPI.new(self) - api.validate_memo_wised!(method_name) + MemoWise::InternalAPI.validate_memo_wised!(self, method_name) - method = method(method_name) + method = method(MemoWise::InternalAPI.original_memo_wised_name(method_name)) method_arguments = MemoWise::InternalAPI.method_arguments(method) - index = api.index(method_name) + index = MemoWise::InternalAPI.index(self, method_name) if method_arguments == MemoWise::InternalAPI::NONE @_memo_wise_sentinels[index] = true @_memo_wise[index] = yield return @@ -546,15 +618,14 @@ end raise ArgumentError, "#{method_name.inspect} must be a Symbol" unless method_name.is_a?(Symbol) raise ArgumentError, "#{method_name} is not a defined method" unless respond_to?(method_name, true) - api = MemoWise::InternalAPI.new(self) - api.validate_memo_wised!(method_name) + MemoWise::InternalAPI.validate_memo_wised!(self, method_name) - method = method(method_name) + method = method(MemoWise::InternalAPI.original_memo_wised_name(method_name)) method_arguments = MemoWise::InternalAPI.method_arguments(method) - index = api.index(method_name) + index = MemoWise::InternalAPI.index(self, method_name) case method_arguments when MemoWise::InternalAPI::NONE @_memo_wise_sentinels[index] = nil @_memo_wise[index] = nil