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

.def_with_visibility(mod, name, visibility, method = nil, &block) ⇒ Object



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/types/private/class_utils.rb', line 72

def self.def_with_visibility(mod, name, visibility, method=nil, &block)
  mod.module_exec do
    # Start a visibility (public/protected/private) region, so that
    # all of the method redefinitions happen with the right visibility
    # from the beginning. This ensures that any other code that is
    # triggered by `method_added`, sees the redefined method with the
    # right visibility.
    send(visibility)

    if method
      define_method(name, method)
    else
      define_method(name, &block)
    end

    if block && block.arity < 0 && respond_to?(:ruby2_keywords, true)
      ruby2_keywords(name)
    end
  end
end

.replace_method(mod, name, original_only = false, &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). If ‘original_only` is false, 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).

If ‘original_only` is true, return the `UnboundMethod` representing the original method.



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/types/private/class_utils.rb', line 100

def self.replace_method(mod, name, original_only=false, &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."
    end
  end

  overwritten = original_owner == mod
  T::Configuration.without_ruby_warnings do
    T::Private::DeclState.current.without_on_method_added do
      def_with_visibility(mod, name, original_visibility, &blk)
    end
  end

  if original_only
    original_method
  else
    new_method = mod.instance_method(name)
    ReplacedMethod.new(mod, original_method, new_method, overwritten, original_visibility)
  end
end