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
- #kind_of?(other) ⇒ Boolean
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 85 86 87 88 |
# 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 this bug at https://github.com/sorbet/sorbet/issues" end if !target_obj.is_a?(interface_mod) # wrapped_dynamic_cast should guarantee this never happens. raise "Unexpected: `is_a?` failed. Please report this bug at https://github.com/sorbet/sorbet/issues" end if target_obj.class == interface_mod # wrapped_dynamic_cast should guarantee this never happens. raise "Unexpected: exact class match. Please report this bug at https://github.com/sorbet/sorbet/issues" 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 singleton_class.respond_to?(:ruby2_keywords, true) singleton_class.send(:ruby2_keywords, method_name) 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 this bug at https://github.com/sorbet/sorbet/issues" 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.
127 128 129 130 131 132 133 134 135 136 |
# File 'lib/types/interface_wrapper.rb', line 127 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
159 160 161 |
# File 'lib/types/interface_wrapper.rb', line 159 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.
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
# File 'lib/types/interface_wrapper.rb', line 142 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.
112 113 114 |
# File 'lib/types/interface_wrapper.rb', line 112 def __interface_mod_DO_NOT_USE # rubocop:disable Naming/MethodName @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.
106 107 108 |
# File 'lib/types/interface_wrapper.rb', line 106 def __target_obj_DO_NOT_USE # rubocop:disable Naming/MethodName @target_obj end |
#is_a?(other) ⇒ Boolean
94 95 96 97 98 99 100 101 102 |
# File 'lib/types/interface_wrapper.rb', line 94 def is_a?(other) 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
90 91 92 |
# File 'lib/types/interface_wrapper.rb', line 90 def kind_of?(other) is_a?(other) end |