sorbet-runtime 0.0.1.pre.prealpha → 0.4.4253

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 (69) hide show
  1. checksums.yaml +5 -5
  2. data/lib/sorbet-runtime.rb +100 -0
  3. data/lib/types/_types.rb +245 -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 +37 -0
  7. data/lib/types/configuration.rb +368 -0
  8. data/lib/types/generic.rb +23 -0
  9. data/lib/types/helpers.rb +31 -0
  10. data/lib/types/interface_wrapper.rb +158 -0
  11. data/lib/types/private/abstract/data.rb +36 -0
  12. data/lib/types/private/abstract/declare.rb +39 -0
  13. data/lib/types/private/abstract/hooks.rb +43 -0
  14. data/lib/types/private/abstract/validate.rb +128 -0
  15. data/lib/types/private/casts.rb +22 -0
  16. data/lib/types/private/class_utils.rb +102 -0
  17. data/lib/types/private/decl_state.rb +18 -0
  18. data/lib/types/private/error_handler.rb +37 -0
  19. data/lib/types/private/methods/_methods.rb +344 -0
  20. data/lib/types/private/methods/call_validation.rb +1177 -0
  21. data/lib/types/private/methods/decl_builder.rb +275 -0
  22. data/lib/types/private/methods/modes.rb +18 -0
  23. data/lib/types/private/methods/signature.rb +196 -0
  24. data/lib/types/private/methods/signature_validation.rb +232 -0
  25. data/lib/types/private/mixins/mixins.rb +27 -0
  26. data/lib/types/private/runtime_levels.rb +41 -0
  27. data/lib/types/private/types/not_typed.rb +23 -0
  28. data/lib/types/private/types/string_holder.rb +26 -0
  29. data/lib/types/private/types/void.rb +33 -0
  30. data/lib/types/profile.rb +27 -0
  31. data/lib/types/props/_props.rb +165 -0
  32. data/lib/types/props/constructor.rb +20 -0
  33. data/lib/types/props/custom_type.rb +84 -0
  34. data/lib/types/props/decorator.rb +826 -0
  35. data/lib/types/props/errors.rb +8 -0
  36. data/lib/types/props/optional.rb +73 -0
  37. data/lib/types/props/plugin.rb +15 -0
  38. data/lib/types/props/pretty_printable.rb +106 -0
  39. data/lib/types/props/serializable.rb +376 -0
  40. data/lib/types/props/type_validation.rb +98 -0
  41. data/lib/types/props/utils.rb +49 -0
  42. data/lib/types/props/weak_constructor.rb +30 -0
  43. data/lib/types/runtime_profiled.rb +36 -0
  44. data/lib/types/sig.rb +28 -0
  45. data/lib/types/struct.rb +8 -0
  46. data/lib/types/types/base.rb +141 -0
  47. data/lib/types/types/class_of.rb +38 -0
  48. data/lib/types/types/enum.rb +42 -0
  49. data/lib/types/types/fixed_array.rb +60 -0
  50. data/lib/types/types/fixed_hash.rb +59 -0
  51. data/lib/types/types/intersection.rb +36 -0
  52. data/lib/types/types/noreturn.rb +25 -0
  53. data/lib/types/types/proc.rb +51 -0
  54. data/lib/types/types/self_type.rb +31 -0
  55. data/lib/types/types/simple.rb +33 -0
  56. data/lib/types/types/type_member.rb +7 -0
  57. data/lib/types/types/type_parameter.rb +23 -0
  58. data/lib/types/types/type_template.rb +7 -0
  59. data/lib/types/types/type_variable.rb +31 -0
  60. data/lib/types/types/typed_array.rb +20 -0
  61. data/lib/types/types/typed_enumerable.rb +141 -0
  62. data/lib/types/types/typed_enumerator.rb +22 -0
  63. data/lib/types/types/typed_hash.rb +29 -0
  64. data/lib/types/types/typed_range.rb +22 -0
  65. data/lib/types/types/typed_set.rb +22 -0
  66. data/lib/types/types/union.rb +59 -0
  67. data/lib/types/types/untyped.rb +25 -0
  68. data/lib/types/utils.rb +223 -0
  69. metadata +122 -15
@@ -0,0 +1,368 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module T::Configuration
5
+ # Set a handler to handle `TypeError`s raised by any in-line type assertions,
6
+ # including `T.must`, `T.let`, `T.cast`, and `T.assert_type!`.
7
+ #
8
+ # By default, any `TypeError`s detected by this gem will be raised. Setting
9
+ # inline_type_error_handler to an object that implements :call (e.g. proc or
10
+ # lambda) allows users to customize the behavior when a `TypeError` is
11
+ # raised on any inline type assertion.
12
+ #
13
+ # @param [Lambda, Proc, Object, nil] value Proc that handles the error (pass
14
+ # nil to reset to default behavior)
15
+ #
16
+ # Parameters passed to value.call:
17
+ #
18
+ # @param [TypeError] error TypeError that was raised
19
+ #
20
+ # @example
21
+ # T::Configuration.inline_type_error_handler = lambda do |error|
22
+ # puts error.message
23
+ # end
24
+ def self.inline_type_error_handler=(value)
25
+ validate_lambda_given!(value)
26
+ @inline_type_error_handler = value
27
+ end
28
+
29
+ private_class_method def self.inline_type_error_handler_default(error)
30
+ raise error
31
+ end
32
+
33
+ def self.inline_type_error_handler(error)
34
+ if @inline_type_error_handler
35
+ @inline_type_error_handler.call(error)
36
+ else
37
+ inline_type_error_handler_default(error)
38
+ end
39
+ end
40
+
41
+ # Set a handler to handle errors that occur when the builder methods in the
42
+ # body of a sig are executed. The sig builder methods are inside a proc so
43
+ # that they can be lazily evaluated the first time the method being sig'd is
44
+ # called.
45
+ #
46
+ # By default, improper use of the builder methods within the body of a sig
47
+ # cause an ArgumentError to be raised. Setting sig_builder_error_handler to an
48
+ # object that implements :call (e.g. proc or lambda) allows users to
49
+ # customize the behavior when a sig can't be built for some reason.
50
+ #
51
+ # @param [Lambda, Proc, Object, nil] value Proc that handles the error (pass
52
+ # nil to reset to default behavior)
53
+ #
54
+ # Parameters passed to value.call:
55
+ #
56
+ # @param [StandardError] error The error that was raised
57
+ # @param [Thread::Backtrace::Location] location Location of the error
58
+ #
59
+ # @example
60
+ # T::Configuration.sig_builder_error_handler = lambda do |error, location|
61
+ # puts error.message
62
+ # end
63
+ def self.sig_builder_error_handler=(value)
64
+ validate_lambda_given!(value)
65
+ @sig_builder_error_handler = value
66
+ end
67
+
68
+ private_class_method def self.sig_builder_error_handler_default(error, location)
69
+ T::Private::Methods.sig_error(location, error.message)
70
+ end
71
+
72
+ def self.sig_builder_error_handler(error, location)
73
+ if @sig_builder_error_handler
74
+ @sig_builder_error_handler.call(error, location)
75
+ else
76
+ sig_builder_error_handler_default(error, location)
77
+ end
78
+ end
79
+
80
+ # Set a handler to handle sig validation errors.
81
+ #
82
+ # Sig validation errors include things like abstract checks, override checks,
83
+ # and type compatibility of arguments. They happen after a sig has been
84
+ # successfully built, but the built sig is incompatible with other sigs in
85
+ # some way.
86
+ #
87
+ # By default, sig validation errors cause an exception to be raised. One
88
+ # exception is for `generated` sigs, for which a message will be logged
89
+ # instead of raising. Setting sig_validation_error_handler to an object that
90
+ # implements :call (e.g. proc or lambda) allows users to customize the
91
+ # behavior when a method signature's build fails.
92
+ #
93
+ # @param [Lambda, Proc, Object, nil] value Proc that handles the error (pass
94
+ # nil to reset to default behavior)
95
+ #
96
+ # Parameters passed to value.call:
97
+ #
98
+ # @param [StandardError] error The error that was raised
99
+ # @param [Hash] opts A hash containing contextual information on the error:
100
+ # @option opts [Method, UnboundMethod] :method Method on which the signature build failed
101
+ # @option opts [T::Private::Methods::Declaration] :declaration Method
102
+ # signature declaration struct
103
+ # @option opts [T::Private::Methods::Signature, nil] :signature Signature
104
+ # that failed (nil if sig build failed before Signature initialization)
105
+ # @option opts [T::Private::Methods::Signature, nil] :super_signature Super
106
+ # method's signature (nil if method is not an override or super method
107
+ # does not have a method signature)
108
+ #
109
+ # @example
110
+ # T::Configuration.sig_validation_error_handler = lambda do |error, opts|
111
+ # puts error.message
112
+ # end
113
+ def self.sig_validation_error_handler=(value)
114
+ validate_lambda_given!(value)
115
+ @sig_validation_error_handler = value
116
+ end
117
+
118
+ private_class_method def self.sig_validation_error_handler_default(error, opts)
119
+ # if this method overrides a generated signature, report that one instead
120
+ bad_method = opts[:method]
121
+ if !opts[:declaration].generated
122
+ super_signature = opts[:super_signature]
123
+ raise error if !super_signature&.generated
124
+ bad_method = super_signature.method
125
+ end
126
+
127
+ method_file, method_line = bad_method.source_location
128
+ T::Configuration.log_info_handler(
129
+ "SIG-DECLARE-FAILED",
130
+ {
131
+ definition_file: method_file,
132
+ definition_line: method_line,
133
+ kind: "Delete",
134
+ message: error.message,
135
+ },
136
+ )
137
+ end
138
+
139
+ def self.sig_validation_error_handler(error, opts)
140
+ if @sig_validation_error_handler
141
+ @sig_validation_error_handler.call(error, opts)
142
+ else
143
+ sig_validation_error_handler_default(error, opts)
144
+ end
145
+ end
146
+
147
+ # Set a handler for type errors that result from calling a method.
148
+ #
149
+ # By default, errors from calling a method cause an exception to be raised.
150
+ # One exception is for `generated` sigs, for which a message will be logged
151
+ # instead of raising. Setting call_validation_error_handler to an object that
152
+ # implements :call (e.g. proc or lambda) allows users to customize the
153
+ # behavior when a method is called with invalid parameters, or returns an
154
+ # invalid value.
155
+ #
156
+ # @param [Lambda, Proc, Object, nil] value Proc that handles the error
157
+ # report (pass nil to reset to default behavior)
158
+ #
159
+ # Parameters passed to value.call:
160
+ #
161
+ # @param [T::Private::Methods::Signature] signature Signature that failed
162
+ # @param [Hash] opts A hash containing contextual information on the error:
163
+ # @option opts [String] :message Error message
164
+ # @option opts [String] :kind One of:
165
+ # ['Parameter', 'Block parameter', 'Return value']
166
+ # @option opts [Symbol] :name Param or block param name (nil for return
167
+ # value)
168
+ # @option opts [Object] :type Expected param/return value type
169
+ # @option opts [Object] :value Actual param/return value
170
+ # @option opts [Thread::Backtrace::Location] :location Location of the
171
+ # caller
172
+ #
173
+ # @example
174
+ # T::Configuration.call_validation_error_handler = lambda do |signature, opts|
175
+ # puts opts[:message]
176
+ # end
177
+ def self.call_validation_error_handler=(value)
178
+ validate_lambda_given!(value)
179
+ @call_validation_error_handler = value
180
+ end
181
+
182
+ private_class_method def self.call_validation_error_handler_default(signature, opts)
183
+ method_file, method_line = signature.method.source_location
184
+ location = opts[:location]
185
+ suffix = "Caller: #{location.path}:#{location.lineno}\n" \
186
+ "Definition: #{method_file}:#{method_line}"
187
+
188
+ error_message = "#{opts[:kind]}#{opts[:name] ? " '#{opts[:name]}'" : ''}: #{opts[:message]}\n#{suffix}"
189
+
190
+ if signature.generated
191
+ got = opts[:value].class
192
+ got = T.unsafe(T::Enumerable[T.untyped]).describe_obj(opts[:value]) if got < Enumerable
193
+ T::Configuration.log_info_handler(
194
+ "SIG-CHECK-FAILED",
195
+ {
196
+ caller_file: location.path,
197
+ caller_line: location.lineno,
198
+ definition_file: method_file,
199
+ definition_line: method_line,
200
+ kind: opts[:kind],
201
+ name: opts[:name],
202
+ expected: opts[:type].name,
203
+ got: got,
204
+ },
205
+ )
206
+ elsif signature.soft_notify
207
+ T::Configuration.soft_assert_handler(
208
+ "TypeError: #{error_message}",
209
+ {notify: signature.soft_notify}
210
+ )
211
+ else
212
+ begin
213
+ raise TypeError.new(error_message)
214
+ rescue TypeError => e # raise into rescue to ensure e.backtrace is populated
215
+ T::Private::ErrorHandler.handle_inline_type_error(e)
216
+ end
217
+ end
218
+ end
219
+
220
+ def self.call_validation_error_handler(signature, opts)
221
+ if @call_validation_error_handler
222
+ @call_validation_error_handler.call(signature, opts)
223
+ else
224
+ call_validation_error_handler_default(signature, opts)
225
+ end
226
+ end
227
+
228
+ # Set a handler for logging
229
+ #
230
+ # @param [Lambda, Proc, Object, nil] value Proc that handles the error
231
+ # report (pass nil to reset to default behavior)
232
+ #
233
+ # Parameters passed to value.call:
234
+ #
235
+ # @param [String] str Message to be logged
236
+ # @param [Hash] extra A hash containing additional parameters to be passed along to the logger.
237
+ #
238
+ # @example
239
+ # T::Configuration.log_info_handler = lambda do |str, extra|
240
+ # puts "#{str}, context: #{extra}"
241
+ # end
242
+ def self.log_info_handler=(value)
243
+ validate_lambda_given!(value)
244
+ @log_info_handler = value
245
+ end
246
+
247
+ private_class_method def self.log_info_handler_default(str, extra)
248
+ puts "#{str}, extra: #{extra}" # rubocop:disable PrisonGuard/NoBarePuts
249
+ end
250
+
251
+ def self.log_info_handler(str, extra)
252
+ if @log_info_handler
253
+ @log_info_handler.call(str, extra)
254
+ else
255
+ log_info_handler_default(str, extra)
256
+ end
257
+ end
258
+
259
+ # Set a handler for soft assertions
260
+ #
261
+ # These generally shouldn't stop execution of the program, but rather inform
262
+ # some party of the assertion to action on later.
263
+ #
264
+ # @param [Lambda, Proc, Object, nil] value Proc that handles the error
265
+ # report (pass nil to reset to default behavior)
266
+ #
267
+ # Parameters passed to value.call:
268
+ #
269
+ # @param [String] str Assertion message
270
+ # @param [Hash] extra A hash containing additional parameters to be passed along to the handler.
271
+ #
272
+ # @example
273
+ # T::Configuration.soft_assert_handler = lambda do |str, extra|
274
+ # puts "#{str}, context: #{extra}"
275
+ # end
276
+ def self.soft_assert_handler=(value)
277
+ validate_lambda_given!(value)
278
+ @soft_assert_handler = value
279
+ end
280
+
281
+ private_class_method def self.soft_assert_handler_default(str, extra)
282
+ puts "#{str}, extra: #{extra}" # rubocop:disable PrisonGuard/NoBarePuts
283
+ end
284
+
285
+ def self.soft_assert_handler(str, extra)
286
+ if @soft_assert_handler
287
+ @soft_assert_handler.call(str, extra)
288
+ else
289
+ soft_assert_handler_default(str, extra)
290
+ end
291
+ end
292
+
293
+ # Set a handler for hard assertions
294
+ #
295
+ # These generally should stop execution of the program, and optionally inform
296
+ # some party of the assertion.
297
+ #
298
+ # @param [Lambda, Proc, Object, nil] value Proc that handles the error
299
+ # report (pass nil to reset to default behavior)
300
+ #
301
+ # Parameters passed to value.call:
302
+ #
303
+ # @param [String] str Assertion message
304
+ # @param [Hash] extra A hash containing additional parameters to be passed along to the handler.
305
+ #
306
+ # @example
307
+ # T::Configuration.hard_assert_handler = lambda do |str, extra|
308
+ # raise "#{str}, context: #{extra}"
309
+ # end
310
+ def self.hard_assert_handler=(value)
311
+ validate_lambda_given!(value)
312
+ @hard_assert_handler = value
313
+ end
314
+
315
+ private_class_method def self.hard_assert_handler_default(str, _)
316
+ raise str
317
+ end
318
+
319
+ def self.hard_assert_handler(str, extra)
320
+ if @hard_assert_handler
321
+ @hard_assert_handler.call(str, extra)
322
+ else
323
+ hard_assert_handler_default(str, extra)
324
+ end
325
+ end
326
+
327
+ # Set a list of class strings that are to be considered scalar.
328
+ # (pass nil to reset to default behavior)
329
+ #
330
+ # @param [String] value Class name.
331
+ #
332
+ # @example
333
+ # T::Configuration.scalar_types = ["NilClass", "TrueClass", "FalseClass", ...]
334
+ def self.scalar_types=(values)
335
+ if values.nil?
336
+ @scalar_tyeps = values
337
+ else
338
+ bad_values = values.select {|v| v.class != String}
339
+ unless bad_values.empty?
340
+ raise ArgumentError.new("Provided values must all be class name strings.")
341
+ end
342
+
343
+ @scalar_types = Set.new(values).freeze
344
+ end
345
+ end
346
+
347
+ @default_scalar_types = Set.new(%w{
348
+ NilClass
349
+ TrueClass
350
+ FalseClass
351
+ Integer
352
+ Float
353
+ String
354
+ Symbol
355
+ Time
356
+ }).freeze
357
+
358
+ def self.scalar_types
359
+ @scalar_types || @default_scalar_types
360
+ end
361
+
362
+
363
+ private_class_method def self.validate_lambda_given!(value)
364
+ if !value.nil? && !value.respond_to?(:call)
365
+ raise ArgumentError.new("Provided value must respond to :call")
366
+ end
367
+ end
368
+ 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)
17
+ T::Types::TypeMember.new(variance) # rubocop:disable PrisonGuard/UseOpusTypesShortcut
18
+ end
19
+
20
+ def type_template(variance=:invariant, fixed: nil)
21
+ T::Types::TypeTemplate.new(variance) # rubocop:disable PrisonGuard/UseOpusTypesShortcut
22
+ end
23
+ end
@@ -0,0 +1,31 @@
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
+ # Causes a mixin to also mix in class methods from the named module.
20
+ #
21
+ # Nearly equivalent to
22
+ #
23
+ # def self.included(other)
24
+ # other.extend(mod)
25
+ # end
26
+ #
27
+ # Except that it is statically analyzed by sorbet.
28
+ def mixes_in_class_methods(mod)
29
+ Private::Mixins.declare_mixes_in_class_methods(self, mod)
30
+ end
31
+ 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