Class: T::InterfaceWrapper

Inherits:
Object
  • Object
show all
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

Instance Method Summary collapse

Methods included from Sig

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.

Examples:

if (impl = T::InterfaceWrapper.dynamic_cast(iface, MyImplementation))
  impl.do_things
end

Parameters:

  • obj (Object)

    object to cast

  • mod (Module)

    type to cast ‘obj` to



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_methodsObject



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.

Parameters:

  • obj (Object)

    object to cast

  • mod (Module)

    type to cast ‘obj` to



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_USEObject

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_USEObject

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

Returns:



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

Returns:



86
87
88
# File 'lib/types/interface_wrapper.rb', line 86

def kind_of?(other) # rubocop:disable PrisonGuard/BanBuiltinMethodOverride
  is_a?(other)
end