Class: T::InterfaceWrapper
- Inherits:
-
Object
- Object
- T::InterfaceWrapper
- Extended by:
- Sig
- Defined in:
- lib/types/interface_wrapper.rb
Overview
Wraps an object, exposing only the methods defined on a given class/module. The idea is that, in the absence of a static type checker that would prevent you from calling non-Bar methods on a variable of type Bar, we can use these wrappers as a way of enforcing it at runtime.
Once we ship static type checking, we should get rid of this entirely.
Defined Under Namespace
Modules: Helpers
Class Method Summary collapse
-
.dynamic_cast(obj, mod) ⇒ Object
“Cast” an object to another type.
- .self_methods ⇒ Object
- .wrap_instance(obj, interface_mod) ⇒ Object
- .wrap_instances(arr, interface_mod) ⇒ Object
-
.wrapped_dynamic_cast(obj, mod) ⇒ Object
Like dynamic_cast, but puts the result in its own wrapper if necessary.
Instance Method Summary collapse
-
#__interface_mod_DO_NOT_USE ⇒ Object
Prefixed because we’re polluting the namespace of the interface we’re wrapping, and we don’t want anyone else (besides wrapped_dynamic_cast) calling it.
-
#__target_obj_DO_NOT_USE ⇒ Object
Prefixed because we’re polluting the namespace of the interface we’re wrapping, and we don’t want anyone else (besides dynamic_cast) calling it.
-
#initialize(target_obj, interface_mod) ⇒ InterfaceWrapper
constructor
A new instance of InterfaceWrapper.
-
#is_a?(other) ⇒ Boolean
rubocop:disable PrisonGuard/BanBuiltinMethodOverride.
-
#kind_of?(other) ⇒ Boolean
rubocop:disable PrisonGuard/BanBuiltinMethodOverride.
Methods included from Sig
Constructor Details
#initialize(target_obj, interface_mod) ⇒ InterfaceWrapper
Returns a new instance of InterfaceWrapper.
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'lib/types/interface_wrapper.rb', line 43 def initialize(target_obj, interface_mod) if target_obj.is_a?(T::InterfaceWrapper) # wrapped_dynamic_cast should guarantee this never happens. raise "Unexpected: wrapping a wrapper. Please report to #dev-productivity." end if !target_obj.is_a?(interface_mod) # wrapped_dynamic_cast should guarantee this never happens. raise "Unexpected: `is_a?` failed. Please report to #dev-productivity." end if target_obj.class == interface_mod # wrapped_dynamic_cast should guarantee this never happens. raise "Unexpected: exact class match. Please report to #dev-productivity." end @target_obj = target_obj @interface_mod = interface_mod self_methods = self.class.self_methods # If perf becomes an issue, we can define these on an anonymous subclass, and keep a cache # so we only need to do it once per unique `interface_mod` T::Utils.methods_excluding_object(interface_mod).each do |method_name| if self_methods.include?(method_name) raise "interface_mod has a method that conflicts with #{self.class}: #{method_name}" end define_singleton_method(method_name) do |*args, &blk| target_obj.send(method_name, *args, &blk) end if target_obj.singleton_class.public_method_defined?(method_name) # no-op, it's already public elsif target_obj.singleton_class.protected_method_defined?(method_name) singleton_class.send(:protected, method_name) elsif target_obj.singleton_class.private_method_defined?(method_name) singleton_class.send(:private, method_name) else raise "This should never happen. Report to #dev-productivity" end end end |
Class Method Details
.dynamic_cast(obj, mod) ⇒ Object
“Cast” an object to another type. If ‘obj` is an InterfaceWrapper, returns the the wrapped object if that matches `type`. Otherwise, returns `obj` if it matches `type`. Otherwise, returns nil.
123 124 125 126 127 128 129 130 131 132 |
# File 'lib/types/interface_wrapper.rb', line 123 def self.dynamic_cast(obj, mod) if obj.is_a?(T::InterfaceWrapper) target_obj = obj.__target_obj_DO_NOT_USE target_obj.is_a?(mod) ? target_obj : nil elsif obj.is_a?(mod) obj else nil end end |
.self_methods ⇒ Object
155 156 157 |
# File 'lib/types/interface_wrapper.rb', line 155 def self.self_methods @self_methods ||= self.instance_methods(false).to_set end |
.wrap_instance(obj, interface_mod) ⇒ Object
24 25 26 27 28 29 30 |
# File 'lib/types/interface_wrapper.rb', line 24 def self.wrap_instance(obj, interface_mod) wrapper = wrapped_dynamic_cast(obj, interface_mod) if wrapper.nil? raise "#{obj.class} cannot be cast to #{interface_mod}" end wrapper end |
.wrap_instances(arr, interface_mod) ⇒ Object
39 40 41 |
# File 'lib/types/interface_wrapper.rb', line 39 def self.wrap_instances(arr, interface_mod) arr.map {|instance| self.wrap_instance(instance, interface_mod)} end |
.wrapped_dynamic_cast(obj, mod) ⇒ Object
Like dynamic_cast, but puts the result in its own wrapper if necessary.
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
# File 'lib/types/interface_wrapper.rb', line 138 def self.wrapped_dynamic_cast(obj, mod) # Avoid unwrapping and creating an equivalent wrapper. if obj.is_a?(T::InterfaceWrapper) && obj.__interface_mod_DO_NOT_USE == mod return obj end cast_obj = dynamic_cast(obj, mod) if cast_obj.nil? nil elsif cast_obj.class == mod # Nothing to wrap, they want the full class cast_obj else new(cast_obj, mod) end end |
Instance Method Details
#__interface_mod_DO_NOT_USE ⇒ Object
Prefixed because we’re polluting the namespace of the interface we’re wrapping, and we don’t want anyone else (besides wrapped_dynamic_cast) calling it.
108 109 110 |
# File 'lib/types/interface_wrapper.rb', line 108 def __interface_mod_DO_NOT_USE @interface_mod end |
#__target_obj_DO_NOT_USE ⇒ Object
Prefixed because we’re polluting the namespace of the interface we’re wrapping, and we don’t want anyone else (besides dynamic_cast) calling it.
102 103 104 |
# File 'lib/types/interface_wrapper.rb', line 102 def __target_obj_DO_NOT_USE @target_obj end |
#is_a?(other) ⇒ Boolean
rubocop:disable PrisonGuard/BanBuiltinMethodOverride
90 91 92 93 94 95 96 97 98 |
# File 'lib/types/interface_wrapper.rb', line 90 def is_a?(other) # rubocop:disable PrisonGuard/BanBuiltinMethodOverride if !other.is_a?(Module) raise TypeError.new("class or module required") end # This makes is_a? return true for T::InterfaceWrapper (and its ancestors), # as well as for @interface_mod and its ancestors. self.class <= other || @interface_mod <= other end |
#kind_of?(other) ⇒ Boolean
rubocop:disable PrisonGuard/BanBuiltinMethodOverride
86 87 88 |
# File 'lib/types/interface_wrapper.rb', line 86 def kind_of?(other) # rubocop:disable PrisonGuard/BanBuiltinMethodOverride is_a?(other) end |