sorbet-runtime 0.5.5841

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. checksums.yaml +7 -0
  2. data/lib/sorbet-runtime.rb +116 -0
  3. data/lib/types/_types.rb +285 -0
  4. data/lib/types/abstract_utils.rb +50 -0
  5. data/lib/types/boolean.rb +8 -0
  6. data/lib/types/compatibility_patches.rb +95 -0
  7. data/lib/types/configuration.rb +428 -0
  8. data/lib/types/enum.rb +349 -0
  9. data/lib/types/generic.rb +23 -0
  10. data/lib/types/helpers.rb +39 -0
  11. data/lib/types/interface_wrapper.rb +158 -0
  12. data/lib/types/non_forcing_constants.rb +51 -0
  13. data/lib/types/private/abstract/data.rb +36 -0
  14. data/lib/types/private/abstract/declare.rb +48 -0
  15. data/lib/types/private/abstract/hooks.rb +43 -0
  16. data/lib/types/private/abstract/validate.rb +128 -0
  17. data/lib/types/private/casts.rb +22 -0
  18. data/lib/types/private/class_utils.rb +111 -0
  19. data/lib/types/private/decl_state.rb +30 -0
  20. data/lib/types/private/final.rb +51 -0
  21. data/lib/types/private/methods/_methods.rb +460 -0
  22. data/lib/types/private/methods/call_validation.rb +1149 -0
  23. data/lib/types/private/methods/decl_builder.rb +228 -0
  24. data/lib/types/private/methods/modes.rb +16 -0
  25. data/lib/types/private/methods/signature.rb +196 -0
  26. data/lib/types/private/methods/signature_validation.rb +229 -0
  27. data/lib/types/private/mixins/mixins.rb +27 -0
  28. data/lib/types/private/retry.rb +10 -0
  29. data/lib/types/private/runtime_levels.rb +56 -0
  30. data/lib/types/private/sealed.rb +65 -0
  31. data/lib/types/private/types/not_typed.rb +23 -0
  32. data/lib/types/private/types/string_holder.rb +26 -0
  33. data/lib/types/private/types/type_alias.rb +26 -0
  34. data/lib/types/private/types/void.rb +34 -0
  35. data/lib/types/profile.rb +31 -0
  36. data/lib/types/props/_props.rb +161 -0
  37. data/lib/types/props/constructor.rb +40 -0
  38. data/lib/types/props/custom_type.rb +108 -0
  39. data/lib/types/props/decorator.rb +672 -0
  40. data/lib/types/props/errors.rb +8 -0
  41. data/lib/types/props/generated_code_validation.rb +268 -0
  42. data/lib/types/props/has_lazily_specialized_methods.rb +92 -0
  43. data/lib/types/props/optional.rb +81 -0
  44. data/lib/types/props/plugin.rb +37 -0
  45. data/lib/types/props/pretty_printable.rb +107 -0
  46. data/lib/types/props/private/apply_default.rb +170 -0
  47. data/lib/types/props/private/deserializer_generator.rb +165 -0
  48. data/lib/types/props/private/parser.rb +32 -0
  49. data/lib/types/props/private/serde_transform.rb +192 -0
  50. data/lib/types/props/private/serializer_generator.rb +77 -0
  51. data/lib/types/props/private/setter_factory.rb +134 -0
  52. data/lib/types/props/serializable.rb +330 -0
  53. data/lib/types/props/type_validation.rb +111 -0
  54. data/lib/types/props/utils.rb +59 -0
  55. data/lib/types/props/weak_constructor.rb +67 -0
  56. data/lib/types/runtime_profiled.rb +24 -0
  57. data/lib/types/sig.rb +30 -0
  58. data/lib/types/struct.rb +18 -0
  59. data/lib/types/types/attached_class.rb +37 -0
  60. data/lib/types/types/base.rb +151 -0
  61. data/lib/types/types/class_of.rb +38 -0
  62. data/lib/types/types/enum.rb +42 -0
  63. data/lib/types/types/fixed_array.rb +60 -0
  64. data/lib/types/types/fixed_hash.rb +59 -0
  65. data/lib/types/types/intersection.rb +37 -0
  66. data/lib/types/types/noreturn.rb +29 -0
  67. data/lib/types/types/proc.rb +51 -0
  68. data/lib/types/types/self_type.rb +35 -0
  69. data/lib/types/types/simple.rb +33 -0
  70. data/lib/types/types/t_enum.rb +38 -0
  71. data/lib/types/types/type_member.rb +7 -0
  72. data/lib/types/types/type_parameter.rb +23 -0
  73. data/lib/types/types/type_template.rb +7 -0
  74. data/lib/types/types/type_variable.rb +31 -0
  75. data/lib/types/types/typed_array.rb +34 -0
  76. data/lib/types/types/typed_enumerable.rb +161 -0
  77. data/lib/types/types/typed_enumerator.rb +36 -0
  78. data/lib/types/types/typed_hash.rb +43 -0
  79. data/lib/types/types/typed_range.rb +26 -0
  80. data/lib/types/types/typed_set.rb +36 -0
  81. data/lib/types/types/union.rb +56 -0
  82. data/lib/types/types/untyped.rb +29 -0
  83. data/lib/types/utils.rb +217 -0
  84. metadata +223 -0
@@ -0,0 +1,349 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ # Enumerations allow for type-safe declarations of a fixed set of values.
5
+ #
6
+ # Every value is a singleton instance of the class (i.e. `Suit::SPADE.is_a?(Suit) == true`).
7
+ #
8
+ # Each value has a corresponding serialized value. By default this is the constant's name converted
9
+ # to lowercase (e.g. `Suit::Club.serialize == 'club'`); however a custom value may be passed to the
10
+ # constructor. Enum will `freeze` the serialized value.
11
+ #
12
+ # @example Declaring an Enum:
13
+ # class Suit < T::Enum
14
+ # enums do
15
+ # CLUB = new
16
+ # SPADE = new
17
+ # DIAMOND = new
18
+ # HEART = new
19
+ # end
20
+ # end
21
+ #
22
+ # @example Custom serialization value:
23
+ # class Status < T::Enum
24
+ # enums do
25
+ # READY = new('rdy')
26
+ # ...
27
+ # end
28
+ # end
29
+ #
30
+ # @example Accessing values:
31
+ # Suit::SPADE
32
+ #
33
+ # @example Converting from serialized value to enum instance:
34
+ # Suit.deserialize('club') == Suit::CLUB
35
+ #
36
+ # @example Using enums in type signatures:
37
+ # sig {params(suit: Suit).returns(Boolean)}
38
+ # def is_red?(suit); ...; end
39
+ #
40
+ # WARNING: Enum instances are singletons that are shared among all their users. Their internals
41
+ # should be kept immutable to avoid unpredictable action at a distance.
42
+ class T::Enum
43
+ extend T::Sig
44
+ extend T::Props::CustomType
45
+
46
+ # TODO(jez) Might want to restrict this, or make subclasses provide this type
47
+ SerializedVal = T.type_alias {T.untyped}
48
+ private_constant :SerializedVal
49
+
50
+ ## Enum class methods ##
51
+ sig {returns(T::Array[T.attached_class])}
52
+ def self.values
53
+ if @values.nil?
54
+ raise "Attempting to access values of #{self.class} before it has been initialized." \
55
+ " Enums are not initialized until the 'enums do' block they are defined in has finished running."
56
+ end
57
+ @values
58
+ end
59
+
60
+ # Convert from serialized value to enum instance
61
+ #
62
+ # Note: It would have been nice to make this method final before people started overriding it.
63
+ # Note: Failed CriticalMethodsNoRuntimeTypingTest
64
+ sig {params(serialized_val: SerializedVal).returns(T.nilable(T.attached_class)).checked(:never)}
65
+ def self.try_deserialize(serialized_val)
66
+ if @mapping.nil?
67
+ raise "Attempting to access serialization map of #{self.class} before it has been initialized." \
68
+ " Enums are not initialized until the 'enums do' block they are defined in has finished running."
69
+ end
70
+ @mapping[serialized_val]
71
+ end
72
+
73
+ # Convert from serialized value to enum instance.
74
+ #
75
+ # Note: It would have been nice to make this method final before people started overriding it.
76
+ # Note: Failed CriticalMethodsNoRuntimeTypingTest
77
+ #
78
+ # @return [self]
79
+ # @raise [KeyError] if serialized value does not match any instance.
80
+ sig {overridable.params(serialized_val: SerializedVal).returns(T.attached_class).checked(:never)}
81
+ def self.from_serialized(serialized_val)
82
+ res = try_deserialize(serialized_val)
83
+ if res.nil?
84
+ raise KeyError.new("Enum #{self} key not found: #{serialized_val.inspect}")
85
+ end
86
+ res
87
+ end
88
+
89
+ # Note: It would have been nice to make this method final before people started overriding it.
90
+ # @return [Boolean] Does the given serialized value correspond with any of this enum's values.
91
+ sig {overridable.params(serialized_val: SerializedVal).returns(T::Boolean)}
92
+ def self.has_serialized?(serialized_val)
93
+ if @mapping.nil?
94
+ raise "Attempting to access serialization map of #{self.class} before it has been initialized." \
95
+ " Enums are not initialized until the 'enums do' block they are defined in has finished running."
96
+ end
97
+ @mapping.include?(serialized_val)
98
+ end
99
+
100
+ # Note: Failed CriticalMethodsNoRuntimeTypingTest
101
+ sig {override.params(instance: T.nilable(T::Enum)).returns(SerializedVal).checked(:never)}
102
+ def self.serialize(instance)
103
+ # This is needed otherwise if a Chalk::ODM::Document with a property of the shape
104
+ # T::Hash[T.nilable(MyEnum), Integer] and a value that looks like {nil => 0} is
105
+ # serialized, we throw the error on L102.
106
+ return nil if instance.nil?
107
+
108
+ if self == T::Enum
109
+ raise "Cannot call T::Enum.serialize directly. You must call on a specific child class."
110
+ end
111
+ if instance.class != self
112
+ raise "Cannot call #serialize on a value that is not an instance of #{self}."
113
+ end
114
+ instance.serialize
115
+ end
116
+
117
+ # Note: Failed CriticalMethodsNoRuntimeTypingTest
118
+ sig {override.params(mongo_value: SerializedVal).returns(T.attached_class).checked(:never)}
119
+ def self.deserialize(mongo_value)
120
+ if self == T::Enum
121
+ raise "Cannot call T::Enum.deserialize directly. You must call on a specific child class."
122
+ end
123
+ self.from_serialized(mongo_value)
124
+ end
125
+
126
+
127
+ ## Enum instance methods ##
128
+
129
+
130
+ sig {returns(T.self_type)}
131
+ def dup
132
+ self
133
+ end
134
+
135
+ sig {returns(T.self_type).checked(:tests)}
136
+ def clone
137
+ self
138
+ end
139
+
140
+ # Note: Failed CriticalMethodsNoRuntimeTypingTest
141
+ sig {returns(SerializedVal).checked(:never)}
142
+ def serialize
143
+ assert_bound!
144
+ @serialized_val
145
+ end
146
+
147
+ sig {params(args: T.untyped).returns(T.untyped)}
148
+ def to_json(*args)
149
+ serialize.to_json(*args)
150
+ end
151
+
152
+ sig {returns(String)}
153
+ def to_s
154
+ inspect
155
+ end
156
+
157
+ sig {returns(String)}
158
+ def inspect
159
+ "#<#{self.class.name}::#{@const_name || '__UNINITIALIZED__'}>"
160
+ end
161
+
162
+ sig {params(other: BasicObject).returns(T.nilable(Integer))}
163
+ def <=>(other)
164
+ case other
165
+ when self.class
166
+ self.serialize <=> other.serialize
167
+ else
168
+ nil
169
+ end
170
+ end
171
+
172
+
173
+ # NB: Do not call this method. This exists to allow for a safe migration path in places where enum
174
+ # values are compared directly against string values.
175
+ #
176
+ # Ruby's string has a weird quirk where `'my_string' == obj` calls obj.==('my_string') if obj
177
+ # responds to the `to_str` method. It does not actually call `to_str` however.
178
+ #
179
+ # See https://ruby-doc.org/core-2.4.0/String.html#method-i-3D-3D
180
+ sig {returns(String)}
181
+ def to_str
182
+ msg = 'Implicit conversion of Enum instances to strings is not allowed. Call #serialize instead.'
183
+ if T::Configuration.legacy_t_enum_migration_mode?
184
+ T::Configuration.soft_assert_handler(
185
+ msg,
186
+ storytime: {class: self.class.name},
187
+ )
188
+ serialize.to_s
189
+ else
190
+ raise NoMethodError.new(msg)
191
+ end
192
+ end
193
+
194
+ sig {params(other: BasicObject).returns(T::Boolean).checked(:never)}
195
+ def ==(other)
196
+ case other
197
+ when String
198
+ if T::Configuration.legacy_t_enum_migration_mode?
199
+ comparison_assertion_failed(:==, other)
200
+ self.serialize == other
201
+ else
202
+ false
203
+ end
204
+ else
205
+ super(other)
206
+ end
207
+ end
208
+
209
+ sig {params(other: BasicObject).returns(T::Boolean).checked(:never)}
210
+ def ===(other)
211
+ case other
212
+ when String
213
+ if T::Configuration.legacy_t_enum_migration_mode?
214
+ comparison_assertion_failed(:===, other)
215
+ self.serialize == other
216
+ else
217
+ false
218
+ end
219
+ else
220
+ super(other)
221
+ end
222
+ end
223
+
224
+ sig {params(method: Symbol, other: T.untyped).void}
225
+ private def comparison_assertion_failed(method, other)
226
+ T::Configuration.soft_assert_handler(
227
+ 'Enum to string comparison not allowed. Compare to the Enum instance directly instead. See go/enum-migration',
228
+ storytime: {
229
+ class: self.class.name,
230
+ self: self.inspect,
231
+ other: other,
232
+ other_class: other.class.name,
233
+ method: method,
234
+ }
235
+ )
236
+ end
237
+
238
+
239
+ ## Private implementation ##
240
+
241
+
242
+ sig {params(serialized_val: SerializedVal).void}
243
+ private def initialize(serialized_val=nil)
244
+ raise 'T::Enum is abstract' if self.class == T::Enum
245
+ if !self.class.started_initializing?
246
+ raise "Must instantiate all enum values of #{self.class} inside 'enums do'."
247
+ end
248
+ if self.class.fully_initialized?
249
+ raise "Cannot instantiate a new enum value of #{self.class} after it has been initialized."
250
+ end
251
+
252
+ serialized_val = serialized_val.frozen? ? serialized_val : serialized_val.dup.freeze
253
+ @serialized_val = T.let(serialized_val, T.nilable(SerializedVal))
254
+ @const_name = T.let(nil, T.nilable(Symbol))
255
+ self.class._register_instance(self)
256
+ end
257
+
258
+ sig {returns(NilClass).checked(:never)}
259
+ private def assert_bound!
260
+ if @const_name.nil?
261
+ raise "Attempting to access Enum value on #{self.class} before it has been initialized." \
262
+ " Enums are not initialized until the 'enums do' block they are defined in has finished running."
263
+ end
264
+ end
265
+
266
+ sig {params(const_name: Symbol).void}
267
+ def _bind_name(const_name)
268
+ @const_name = const_name
269
+ @serialized_val = const_to_serialized_val(const_name) if @serialized_val.nil?
270
+ freeze
271
+ end
272
+
273
+ sig {params(const_name: Symbol).returns(String)}
274
+ private def const_to_serialized_val(const_name)
275
+ # Historical note: We convert to lowercase names because the majority of existing calls to
276
+ # `make_accessible` were arrays of lowercase strings. Doing this conversion allowed for the
277
+ # least amount of repetition in migrated declarations.
278
+ const_name.to_s.downcase.freeze
279
+ end
280
+
281
+ sig {returns(T::Boolean)}
282
+ def self.started_initializing?
283
+ @started_initializing = T.let(@started_initializing, T.nilable(T::Boolean))
284
+ @started_initializing ||= false
285
+ end
286
+
287
+ sig {returns(T::Boolean)}
288
+ def self.fully_initialized?
289
+ @fully_initialized = T.let(@fully_initialized, T.nilable(T::Boolean))
290
+ @fully_initialized ||= false
291
+ end
292
+
293
+ # Maintains the order in which values are defined
294
+ sig {params(instance: T.untyped).void}
295
+ def self._register_instance(instance)
296
+ @values ||= []
297
+ @values << T.cast(instance, T.attached_class)
298
+ end
299
+
300
+ # Entrypoint for allowing people to register new enum values.
301
+ # All enum values must be defined within this block.
302
+ sig {params(blk: T.proc.void).void}
303
+ def self.enums(&blk)
304
+ raise "enums cannot be defined for T::Enum" if self == T::Enum
305
+ raise "Enum #{self} was already initialized" if @fully_initialized
306
+ raise "Enum #{self} is still initializing" if @started_initializing
307
+
308
+ @started_initializing = true
309
+
310
+ @values = T.let(nil, T.nilable(T::Array[T.attached_class]))
311
+
312
+ yield
313
+
314
+ @mapping = T.let(nil, T.nilable(T::Hash[SerializedVal, T.attached_class]))
315
+ @mapping = {}
316
+
317
+ # Freeze the Enum class and bind the constant names into each of the instances.
318
+ self.constants(false).each do |const_name|
319
+ instance = self.const_get(const_name, false)
320
+ if !instance.is_a?(self)
321
+ raise "Invalid constant #{self}::#{const_name} on enum. " \
322
+ "All constants defined for an enum must be instances itself (e.g. `Foo = new`)."
323
+ end
324
+
325
+ instance._bind_name(const_name)
326
+ serialized = instance.serialize
327
+ if @mapping.include?(serialized)
328
+ raise "Enum values must have unique serializations. Value '#{serialized}' is repeated on #{self}."
329
+ end
330
+ @mapping[serialized] = instance
331
+ end
332
+ @values.freeze
333
+ @mapping.freeze
334
+
335
+ orphaned_instances = T.must(@values) - @mapping.values
336
+ if !orphaned_instances.empty?
337
+ raise "Enum values must be assigned to constants: #{orphaned_instances.map {|v| v.instance_variable_get('@serialized_val')}}"
338
+ end
339
+
340
+ @fully_initialized = true
341
+ end
342
+
343
+ sig {params(child_class: Module).void}
344
+ def self.inherited(child_class)
345
+ super
346
+
347
+ raise "Inheriting from children of T::Enum is prohibited" if self != T::Enum
348
+ end
349
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+ # Use as a mixin with extend (`extend T::Generic`).
5
+ # Docs at https://hackpad.corp.stripe.com/Type-Validation-in-pay-server-1JaoTHir5Mo.
6
+ module T::Generic
7
+ include T::Helpers
8
+ include Kernel
9
+
10
+ ### Class/Module Helpers ###
11
+
12
+ def [](*types)
13
+ self
14
+ end
15
+
16
+ def type_member(variance=:invariant, fixed: nil, lower: T.untyped, upper: BasicObject)
17
+ T::Types::TypeMember.new(variance) # rubocop:disable PrisonGuard/UseOpusTypesShortcut
18
+ end
19
+
20
+ def type_template(variance=:invariant, fixed: nil, lower: T.untyped, upper: BasicObject)
21
+ T::Types::TypeTemplate.new(variance) # rubocop:disable PrisonGuard/UseOpusTypesShortcut
22
+ end
23
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+ # Use as a mixin with extend (`extend T::Helpers`).
5
+ # Docs at https://confluence.corp.stripe.com/display/PRODINFRA/Ruby+Types
6
+ module T::Helpers
7
+ Private = T::Private
8
+
9
+ ### Class/Module Helpers ###
10
+
11
+ def abstract!
12
+ Private::Abstract::Declare.declare_abstract(self, type: :abstract)
13
+ end
14
+
15
+ def interface!
16
+ Private::Abstract::Declare.declare_abstract(self, type: :interface)
17
+ end
18
+
19
+ def final!
20
+ Private::Final.declare(self)
21
+ end
22
+
23
+ def sealed!
24
+ Private::Sealed.declare(self, Kernel.caller(1..1)&.first&.split(':')&.first)
25
+ end
26
+
27
+ # Causes a mixin to also mix in class methods from the named module.
28
+ #
29
+ # Nearly equivalent to
30
+ #
31
+ # def self.included(other)
32
+ # other.extend(mod)
33
+ # end
34
+ #
35
+ # Except that it is statically analyzed by sorbet.
36
+ def mixes_in_class_methods(mod)
37
+ Private::Mixins.declare_mixes_in_class_methods(self, mod)
38
+ end
39
+ end
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+ # typed: false
3
+
4
+ # Wraps an object, exposing only the methods defined on a given class/module. The idea is that, in
5
+ # the absence of a static type checker that would prevent you from calling non-Bar methods on a
6
+ # variable of type Bar, we can use these wrappers as a way of enforcing it at runtime.
7
+ #
8
+ # Once we ship static type checking, we should get rid of this entirely.
9
+ class T::InterfaceWrapper
10
+ extend T::Sig
11
+
12
+ module Helpers
13
+ def wrap_instance(obj)
14
+ T::InterfaceWrapper.wrap_instance(obj, self)
15
+ end
16
+
17
+ def wrap_instances(arr)
18
+ T::InterfaceWrapper.wrap_instances(arr, self)
19
+ end
20
+ end
21
+
22
+ private_class_method :new # use `wrap_instance`
23
+
24
+ def self.wrap_instance(obj, interface_mod)
25
+ wrapper = wrapped_dynamic_cast(obj, interface_mod)
26
+ if wrapper.nil?
27
+ raise "#{obj.class} cannot be cast to #{interface_mod}"
28
+ end
29
+ wrapper
30
+ end
31
+
32
+ sig do
33
+ params(
34
+ arr: Array,
35
+ interface_mod: T.untyped
36
+ )
37
+ .returns(Array)
38
+ end
39
+ def self.wrap_instances(arr, interface_mod)
40
+ arr.map {|instance| self.wrap_instance(instance, interface_mod)}
41
+ end
42
+
43
+ def initialize(target_obj, interface_mod)
44
+ if target_obj.is_a?(T::InterfaceWrapper)
45
+ # wrapped_dynamic_cast should guarantee this never happens.
46
+ raise "Unexpected: wrapping a wrapper. Please report to #dev-productivity."
47
+ end
48
+
49
+ if !target_obj.is_a?(interface_mod)
50
+ # wrapped_dynamic_cast should guarantee this never happens.
51
+ raise "Unexpected: `is_a?` failed. Please report to #dev-productivity."
52
+ end
53
+
54
+ if target_obj.class == interface_mod
55
+ # wrapped_dynamic_cast should guarantee this never happens.
56
+ raise "Unexpected: exact class match. Please report to #dev-productivity."
57
+ end
58
+
59
+ @target_obj = target_obj
60
+ @interface_mod = interface_mod
61
+ self_methods = self.class.self_methods
62
+
63
+ # If perf becomes an issue, we can define these on an anonymous subclass, and keep a cache
64
+ # so we only need to do it once per unique `interface_mod`
65
+ T::Utils.methods_excluding_object(interface_mod).each do |method_name|
66
+ if self_methods.include?(method_name)
67
+ raise "interface_mod has a method that conflicts with #{self.class}: #{method_name}"
68
+ end
69
+
70
+ define_singleton_method(method_name) do |*args, &blk|
71
+ target_obj.send(method_name, *args, &blk)
72
+ end
73
+
74
+ if target_obj.singleton_class.public_method_defined?(method_name)
75
+ # no-op, it's already public
76
+ elsif target_obj.singleton_class.protected_method_defined?(method_name)
77
+ singleton_class.send(:protected, method_name)
78
+ elsif target_obj.singleton_class.private_method_defined?(method_name)
79
+ singleton_class.send(:private, method_name)
80
+ else
81
+ raise "This should never happen. Report to #dev-productivity"
82
+ end
83
+ end
84
+ end
85
+
86
+ def kind_of?(other) # rubocop:disable PrisonGuard/BanBuiltinMethodOverride
87
+ is_a?(other)
88
+ end
89
+
90
+ def is_a?(other) # rubocop:disable PrisonGuard/BanBuiltinMethodOverride
91
+ if !other.is_a?(Module)
92
+ raise TypeError.new("class or module required")
93
+ end
94
+
95
+ # This makes is_a? return true for T::InterfaceWrapper (and its ancestors),
96
+ # as well as for @interface_mod and its ancestors.
97
+ self.class <= other || @interface_mod <= other
98
+ end
99
+
100
+ # Prefixed because we're polluting the namespace of the interface we're wrapping, and we don't
101
+ # want anyone else (besides dynamic_cast) calling it.
102
+ def __target_obj_DO_NOT_USE
103
+ @target_obj
104
+ end
105
+
106
+ # Prefixed because we're polluting the namespace of the interface we're wrapping, and we don't
107
+ # want anyone else (besides wrapped_dynamic_cast) calling it.
108
+ def __interface_mod_DO_NOT_USE
109
+ @interface_mod
110
+ end
111
+
112
+ # "Cast" an object to another type. If `obj` is an InterfaceWrapper, returns the the wrapped
113
+ # object if that matches `type`. Otherwise, returns `obj` if it matches `type`. Otherwise,
114
+ # returns nil.
115
+ #
116
+ # @param obj [Object] object to cast
117
+ # @param mod [Module] type to cast `obj` to
118
+ #
119
+ # @example
120
+ # if (impl = T::InterfaceWrapper.dynamic_cast(iface, MyImplementation))
121
+ # impl.do_things
122
+ # end
123
+ def self.dynamic_cast(obj, mod)
124
+ if obj.is_a?(T::InterfaceWrapper)
125
+ target_obj = obj.__target_obj_DO_NOT_USE
126
+ target_obj.is_a?(mod) ? target_obj : nil
127
+ elsif obj.is_a?(mod)
128
+ obj
129
+ else
130
+ nil
131
+ end
132
+ end
133
+
134
+ # Like dynamic_cast, but puts the result in its own wrapper if necessary.
135
+ #
136
+ # @param obj [Object] object to cast
137
+ # @param mod [Module] type to cast `obj` to
138
+ def self.wrapped_dynamic_cast(obj, mod)
139
+ # Avoid unwrapping and creating an equivalent wrapper.
140
+ if obj.is_a?(T::InterfaceWrapper) && obj.__interface_mod_DO_NOT_USE == mod
141
+ return obj
142
+ end
143
+
144
+ cast_obj = dynamic_cast(obj, mod)
145
+ if cast_obj.nil?
146
+ nil
147
+ elsif cast_obj.class == mod
148
+ # Nothing to wrap, they want the full class
149
+ cast_obj
150
+ else
151
+ new(cast_obj, mod)
152
+ end
153
+ end
154
+
155
+ def self.self_methods
156
+ @self_methods ||= self.instance_methods(false).to_set
157
+ end
158
+ end