Module: T::Private::ClassUtils

Defined in:
lib/types/private/class_utils.rb

Overview

Cut down version of Chalk::Tools::ClassUtils with only :replace_method functionality. Extracted to a separate namespace so the type system can be used standalone.

Defined Under Namespace

Classes: ReplacedMethod

Class Method Summary collapse

Class Method Details

.replace_method(mod, name, &blk) ⇒ Object

Replaces a method, either by overwriting it (if it is defined directly on ‘mod`) or by overriding it (if it is defined by one of mod’s ancestors). Returns a ReplacedMethod instance on which you can call ‘bind(…).call(…)` to call the original method, or `restore` to restore the original method (by overwriting or removing the override).



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/types/private/class_utils.rb', line 76

def self.replace_method(mod, name, &blk)
  original_method = mod.instance_method(name)
  original_visibility = visibility_method_name(mod, name)
  original_owner = original_method.owner

  mod.ancestors.each do |ancestor|
    break if ancestor == mod
    if ancestor == original_owner
      # If we get here, that means the method we're trying to replace exists on a *prepended*
      # mixin, which means in order to supersede it, we'd need to create a method on a new
      # module that we'd prepend before `ancestor`. The problem with that approach is there'd
      # be no way to remove that new module after prepending it, so we'd be left with these
      # empty anonymous modules in the ancestor chain after calling `restore`.
      #
      # That's not necessarily a deal breaker, but for now, we're keeping it as unsupported.
      raise "You're trying to replace `#{name}` on `#{mod}`, but that method exists in a " \
            "prepended module (#{ancestor}), which we don't currently support. Talk to " \
            "#dev-productivity for help."
    end
  end

  overwritten = original_owner == mod
  T::Configuration.without_ruby_warnings do
    T::Private::DeclState.current.skip_on_method_added = true
    mod.send(:define_method, name, &blk) # rubocop:disable PrisonGuard/UsePublicSend
    T::Private::DeclState.current.skip_on_method_added = false
  end
  mod.send(original_visibility, name) # rubocop:disable PrisonGuard/UsePublicSend
  new_method = mod.instance_method(name)

  ReplacedMethod.new(mod, original_method, new_method, overwritten, original_visibility)
end