class T::InterfaceWrapper
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.
Public Class Methods
“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.
@param obj [Object] object to cast @param mod [Module] type to cast `obj` to
@example
if (impl = T::InterfaceWrapper.dynamic_cast(iface, MyImplementation)) impl.do_things end
# 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
# 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
# File lib/types/interface_wrapper.rb, line 155 def self.self_methods @self_methods ||= self.instance_methods(false).to_set end
# 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
# 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
Like dynamic_cast
, but puts the result in its own wrapper if necessary.
@param obj [Object] object to cast @param mod [Module] type to cast `obj` to
# 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
Public Instance Methods
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.
# File lib/types/interface_wrapper.rb, line 108 def __interface_mod_DO_NOT_USE # rubocop:disable Naming/MethodName @interface_mod end
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.
# File lib/types/interface_wrapper.rb, line 102 def __target_obj_DO_NOT_USE # rubocop:disable Naming/MethodName @target_obj end
# File lib/types/interface_wrapper.rb, line 90 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
# File lib/types/interface_wrapper.rb, line 86 def kind_of?(other) is_a?(other) end