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
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.

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



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_methodsObject



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.

Parameters:

  • obj (Object)

    object to cast

  • mod (Module)

    type to cast ‘obj` to



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_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.



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_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.



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

Returns:



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

Returns:



90
91
92
# File 'lib/types/interface_wrapper.rb', line 90

def kind_of?(other)
  is_a?(other)
end