lib/dry/behaviour/black_tie.rb in dry-behaviour-0.8.0 vs lib/dry/behaviour/black_tie.rb in dry-behaviour-0.9.0

- old
+ new

@@ -60,39 +60,25 @@ NORMALIZE_KEYS.(self).include? method end end def defmethod(name, *params) - BlackTie.protocols[self][name] = params - end - - DELEGATE_METHOD = lambda do |klazz, (source, target)| - klazz.class_eval do - define_method(source, &Dry::DEFINE_METHOD.curry[target]) + if params.size.zero? || params.first.is_a?(Array) && params.first.last != :req + BlackTie.Logger.warn(IMPLICIT_RECEIVER_DECLARATION % [Dry::BlackTie.proto_caller, self.inspect, name]) + params.unshift(:this) end - end - - POSTPONE_EXTEND = lambda do |target, protocol| - TracePoint.new(:end) do |tp| - if tp.self == protocol - target.extend protocol - tp.disable + params = + params.map do |p, type| + if type && !PARAM_TYPES.include?(type) + BlackTie.Logger.warn(UNKNOWN_TYPE_DECLARATION % [Dry::BlackTie.proto_caller, type, self.inspect, name]) + type = nil + end + [type || PARAM_TYPES.include?(p) ? p : :req, p] end - end.enable + BlackTie.protocols[self][name] = params end - NORMALIZE_KEYS = lambda do |protocol| - BlackTie.protocols[protocol].keys.reject { |k| k.to_s =~ /\A__.*__\z/ } - end - - IMPLICIT_DELEGATE_DEPRECATION = - "\n⚠️ DEPRECATED → Implicit delegation to the target class will be removed in 1.0\n" \ - "  ⮩ due to the lack of the explicit implementation of %s#%s for %s\n" \ - "  ⮩ it will be delegated to the target class itself.\n" \ - "  ⮩ Consider using explicit `delegate:' declaration in `defimpl' or\n" \ - "  ⮩ use `implicit_inheritance: true' parameter in protocol definition.".freeze - def defimpl(protocol = nil, target: nil, delegate: [], map: {}, &λ) raise if target.nil? || !block_given? && delegate.empty? && map.empty? mds = normalize_map_delegates(delegate, map) @@ -104,30 +90,95 @@ mod.methods(false).tap do |meths| (NORMALIZE_KEYS.(protocol) - meths).each_with_object(meths) do |m, acc| if BlackTie.protocols[protocol][:__implicit_inheritance__] mod.singleton_class.class_eval do - define_method m do |*args, &λ| - super(*args, &λ) + define_method m do |this, *♿_args, &♿_λ| + super(this, *♿_args, &♿_λ) end end else BlackTie.Logger.warn( - IMPLICIT_DELEGATE_DEPRECATION % [protocol.inspect, m, target] + IMPLICIT_DELEGATE_DEPRECATION % [Dry::BlackTie.proto_caller, protocol.inspect, m, target] ) DELEGATE_METHOD.(mod.singleton_class, [m] * 2) end acc << m end end.each do |m| target = [target] unless target.is_a?(Array) target.each do |tgt| + ok = + [ + BlackTie.protocols[protocol][m], + mod.method(m).parameters.reject { |_, v| v.to_s[/\A♿_/] } + ].map(&:first).reduce(:==) + + # TODO[1.0] raise NotImplemented(:arity) + BlackTie.Logger.warn( + WRONG_PARAMETER_DECLARATION % [Dry::BlackTie.proto_caller, protocol.inspect, m, target, BlackTie.protocols[protocol][m].map(&:first)] + ) unless ok + BlackTie.implementations[protocol][tgt][m] = mod.method(m).to_proc end end end end module_function :defimpl + + PARAM_TYPES = %i[req opt rest keyrest block] + + DELEGATE_METHOD = lambda do |klazz, (source, target)| + klazz.class_eval do + define_method(source, &Dry::DEFINE_METHOD.curry[target]) + end + end + + POSTPONE_EXTEND = lambda do |target, protocol| + TracePoint.new(:end) do |tp| + if tp.self == protocol + target.extend protocol + tp.disable + end + end.enable + end + + NORMALIZE_KEYS = lambda do |protocol| + BlackTie.protocols[protocol].keys.reject { |k| k.to_s =~ /\A__.*__\z/ } + end + + def self.proto_caller + caller.drop_while do |line| + line =~ %r[dry-behaviour/lib/dry/behaviour] + end.first + end + + IMPLICIT_DELEGATE_DEPRECATION = + "\n🚨️ DEPRECATED → %s\n" \ + "  ⮩ Implicit delegation to the target class will be removed in 1.0\n" \ + "  ⮩ due to the lack of the explicit implementation of %s#%s for %s\n" \ + "  ⮩ it will be delegated to the target class itself.\n" \ + "  ⮩ Consider using explicit `delegate:' declaration in `defimpl' or\n" \ + "  ⮩ use `implicit_inheritance: true' parameter in protocol definition.".freeze + + IMPLICIT_RECEIVER_DECLARATION = + "\n⚠️ TOO IMPLICIT → %s\n" \ + "  ⮩ Implicit declaration of `this' parameter in `defmethod'.\n" \ + "  ⮩ Whilst it’s allowed, we strongly encourage to explicitly declare it\n" \ + "  ⮩ in call to %s#defmethod(%s).".freeze + + UNKNOWN_TYPE_DECLARATION = + "\n⚠️ UNKNOWN TYPE → %s\n" \ + "  ⮩ Unknown parameter type [%s] in call to %s#defmethod(%s).\n" \ + "  ⮩ Is it a typo? Omit the type for `:req' or pass one of allowed types:\n" \ + "  ⮩ #{PARAM_TYPES.inspect}".freeze + + WRONG_PARAMETER_DECLARATION = + "\n🚨️ DEPRECATED → %s\n" \ + "  ⮩ Wrong parameters declaration will be removed in 1.0\n" \ + "  ⮩ %s#%s was implemented for %s with unexpected parameters.\n" \ + "  ⮩ Consider implementing interfaces exactly as they were declared.\n" \ + "  ⮩ Expected: %s".freeze def self.Logger @logger ||= if Kernel.const_defined?('::Rails') Rails.logger else