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,8 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ module T::Props
5
+ class Error < StandardError; end
6
+ class InvalidValueError < Error; end
7
+ class ImmutableProp < Error; end
8
+ end
@@ -0,0 +1,268 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+ module T::Props
5
+ # Helper to validate generated code, to mitigate security concerns around
6
+ # `class_eval`. Not called by default; the expectation is this will be used
7
+ # in a test iterating over all T::Props::Serializable subclasses.
8
+ #
9
+ # We validate the exact expected structure of the generated methods as far
10
+ # as we can, and then where cloning produces an arbitrarily nested structure,
11
+ # we just validate a lack of side effects.
12
+ module GeneratedCodeValidation
13
+ extend Private::Parse
14
+
15
+ class ValidationError < RuntimeError; end
16
+
17
+ def self.validate_deserialize(source)
18
+ parsed = parse(source)
19
+
20
+ # def %<name>(hash)
21
+ # ...
22
+ # end
23
+ assert_equal(:def, parsed.type)
24
+ name, args, body = parsed.children
25
+ assert_equal(:__t_props_generated_deserialize, name)
26
+ assert_equal(s(:args, s(:arg, :hash)), args)
27
+
28
+ assert_equal(:begin, body.type)
29
+ init, *prop_clauses, ret = body.children
30
+
31
+ # found = %<prop_count>
32
+ # ...
33
+ # found
34
+ assert_equal(:lvasgn, init.type)
35
+ init_name, init_val = init.children
36
+ assert_equal(:found, init_name)
37
+ assert_equal(:int, init_val.type)
38
+ assert_equal(s(:lvar, :found), ret)
39
+
40
+ prop_clauses.each_with_index do |clause, i|
41
+ if i % 2 == 0
42
+ validate_deserialize_hash_read(clause)
43
+ else
44
+ validate_deserialize_ivar_set(clause)
45
+ end
46
+ end
47
+ end
48
+
49
+ def self.validate_serialize(source)
50
+ parsed = parse(source)
51
+
52
+ # def %<name>(strict)
53
+ # ...
54
+ # end
55
+ assert_equal(:def, parsed.type)
56
+ name, args, body = parsed.children
57
+ assert_equal(:__t_props_generated_serialize, name)
58
+ assert_equal(s(:args, s(:arg, :strict)), args)
59
+
60
+ assert_equal(:begin, body.type)
61
+ init, *prop_clauses, ret = body.children
62
+
63
+ # h = {}
64
+ # ...
65
+ # h
66
+ assert_equal(s(:lvasgn, :h, s(:hash)), init)
67
+ assert_equal(s(:lvar, :h), ret)
68
+
69
+ prop_clauses.each do |clause|
70
+ validate_serialize_clause(clause)
71
+ end
72
+ end
73
+
74
+ private_class_method def self.validate_serialize_clause(clause)
75
+ assert_equal(:if, clause.type)
76
+ condition, if_body, else_body = clause.children
77
+
78
+ # if @%<accessor_key>.nil?
79
+ assert_equal(:send, condition.type)
80
+ receiver, method = condition.children
81
+ assert_equal(:ivar, receiver.type)
82
+ assert_equal(:nil?, method)
83
+
84
+ unless if_body.nil?
85
+ # required_prop_missing_from_serialize(%<prop>) if strict
86
+ assert_equal(:if, if_body.type)
87
+ if_strict_condition, if_strict_body, if_strict_else = if_body.children
88
+ assert_equal(s(:lvar, :strict), if_strict_condition)
89
+ assert_equal(:send, if_strict_body.type)
90
+ on_strict_receiver, on_strict_method, on_strict_arg = if_strict_body.children
91
+ assert_equal(nil, on_strict_receiver)
92
+ assert_equal(:required_prop_missing_from_serialize, on_strict_method)
93
+ assert_equal(:sym, on_strict_arg.type)
94
+ assert_equal(nil, if_strict_else)
95
+ end
96
+
97
+ # h[%<serialized_form>] = ...
98
+ assert_equal(:send, else_body.type)
99
+ receiver, method, h_key, h_val = else_body.children
100
+ assert_equal(s(:lvar, :h), receiver)
101
+ assert_equal(:[]=, method)
102
+ assert_equal(:str, h_key.type)
103
+
104
+ validate_lack_of_side_effects(h_val, whitelisted_methods_for_serialize)
105
+ end
106
+
107
+ private_class_method def self.validate_deserialize_hash_read(clause)
108
+ # val = hash[%<serialized_form>s]
109
+
110
+ assert_equal(:lvasgn, clause.type)
111
+ name, val = clause.children
112
+ assert_equal(:val, name)
113
+ assert_equal(:send, val.type)
114
+ receiver, method, arg = val.children
115
+ assert_equal(s(:lvar, :hash), receiver)
116
+ assert_equal(:[], method)
117
+ assert_equal(:str, arg.type)
118
+ end
119
+
120
+ private_class_method def self.validate_deserialize_ivar_set(clause)
121
+ # %<accessor_key>s = if val.nil?
122
+ # found -= 1 unless hash.key?(%<serialized_form>s)
123
+ # %<nil_handler>s
124
+ # else
125
+ # %<serialized_val>s
126
+ # end
127
+
128
+ assert_equal(:ivasgn, clause.type)
129
+ ivar_name, deser_val = clause.children
130
+ unless ivar_name.is_a?(Symbol)
131
+ raise ValidationError.new("Unexpected ivar: #{ivar_name}")
132
+ end
133
+
134
+ assert_equal(:if, deser_val.type)
135
+ condition, if_body, else_body = deser_val.children
136
+ assert_equal(s(:send, s(:lvar, :val), :nil?), condition)
137
+
138
+ assert_equal(:begin, if_body.type)
139
+ update_found, handle_nil = if_body.children
140
+ assert_equal(:if, update_found.type)
141
+ found_condition, found_if_body, found_else_body = update_found.children
142
+ assert_equal(:send, found_condition.type)
143
+ receiver, method, arg = found_condition.children
144
+ assert_equal(s(:lvar, :hash), receiver)
145
+ assert_equal(:key?, method)
146
+ assert_equal(:str, arg.type)
147
+ assert_equal(nil, found_if_body)
148
+ assert_equal(s(:op_asgn, s(:lvasgn, :found), :-, s(:int, 1)), found_else_body)
149
+
150
+ validate_deserialize_handle_nil(handle_nil)
151
+
152
+ if else_body.type == :kwbegin
153
+ rescue_expression, = else_body.children
154
+ assert_equal(:rescue, rescue_expression.type)
155
+
156
+ try, rescue_body = rescue_expression.children
157
+ validate_lack_of_side_effects(try, whitelisted_methods_for_deserialize)
158
+
159
+ assert_equal(:resbody, rescue_body.type)
160
+ exceptions, assignment, handler = rescue_body.children
161
+ assert_equal(:array, exceptions.type)
162
+ exceptions.children.each {|c| assert_equal(:const, c.type)}
163
+ assert_equal(:lvasgn, assignment.type)
164
+ assert_equal([:e], assignment.children)
165
+ validate_lack_of_side_effects(handler, whitelisted_methods_for_deserialize)
166
+ else
167
+ validate_lack_of_side_effects(else_body, whitelisted_methods_for_deserialize)
168
+ end
169
+ end
170
+
171
+ private_class_method def self.validate_deserialize_handle_nil(node)
172
+ case node.type
173
+ when :hash, :array, :str, :sym, :int, :float, :true, :false, :nil, :const
174
+ # Primitives and constants are safe
175
+ when :send
176
+ receiver, method, arg = node.children
177
+ if receiver.nil?
178
+ # required_prop_missing_from_deserialize(%<prop>)
179
+ assert_equal(:required_prop_missing_from_deserialize, method)
180
+ assert_equal(:sym, arg.type)
181
+ elsif receiver == self_class_decorator
182
+ # self.class.decorator.raise_nil_deserialize_error(%<serialized_form>)
183
+ assert_equal(:raise_nil_deserialize_error, method)
184
+ assert_equal(:str, arg.type)
185
+ elsif method == :default
186
+ # self.class.decorator.props_with_defaults.fetch(%<prop>).default
187
+ assert_equal(:send, receiver.type)
188
+ inner_receiver, inner_method, inner_arg = receiver.children
189
+ assert_equal(
190
+ s(:send, self_class_decorator, :props_with_defaults),
191
+ inner_receiver,
192
+ )
193
+ assert_equal(:fetch, inner_method)
194
+ assert_equal(:sym, inner_arg.type)
195
+ else
196
+ raise ValidationError.new("Unexpected receiver in nil handler: #{node.inspect}")
197
+ end
198
+ else
199
+ raise ValidationError.new("Unexpected nil handler: #{node.inspect}")
200
+ end
201
+ end
202
+
203
+ private_class_method def self.self_class_decorator
204
+ @self_class_decorator ||= s(:send, s(:send, s(:self), :class), :decorator).freeze
205
+ end
206
+
207
+ private_class_method def self.validate_lack_of_side_effects(node, whitelisted_methods_by_receiver_type)
208
+ case node.type
209
+ when :const
210
+ # This is ok, because we'll have validated what method has been called
211
+ # if applicable
212
+ when :hash, :array, :str, :sym, :int, :float, :true, :false, :nil, :self
213
+ # Primitives & self are ok
214
+ when :lvar, :arg, :ivar
215
+ # Reading local & instance variables & arguments is ok
216
+ unless node.children.all? {|c| c.is_a?(Symbol)}
217
+ raise ValidationError.new("Unexpected child for #{node.type}: #{node.inspect}")
218
+ end
219
+ when :args, :mlhs, :block, :begin, :if
220
+ # Blocks etc are read-only if their contents are read-only
221
+ node.children.each {|c| validate_lack_of_side_effects(c, whitelisted_methods_by_receiver_type) if c}
222
+ when :send
223
+ # Sends are riskier so check a whitelist
224
+ receiver, method, *args = node.children
225
+ if receiver
226
+ if receiver.type == :send
227
+ key = receiver
228
+ else
229
+ key = receiver.type
230
+ validate_lack_of_side_effects(receiver, whitelisted_methods_by_receiver_type)
231
+ end
232
+
233
+ if !whitelisted_methods_by_receiver_type[key]&.include?(method)
234
+ raise ValidationError.new("Unexpected method #{method} called on #{receiver.inspect}")
235
+ end
236
+ end
237
+ args.each do |arg|
238
+ validate_lack_of_side_effects(arg, whitelisted_methods_by_receiver_type)
239
+ end
240
+ else
241
+ raise ValidationError.new("Unexpected node type #{node.type}: #{node.inspect}")
242
+ end
243
+ end
244
+
245
+ private_class_method def self.assert_equal(expected, actual)
246
+ if expected != actual
247
+ raise ValidationError.new("Expected #{expected}, got #{actual}")
248
+ end
249
+ end
250
+
251
+ # Method calls generated by SerdeTransform
252
+ private_class_method def self.whitelisted_methods_for_serialize
253
+ @whitelisted_methods_for_serialize ||= {
254
+ :lvar => %i{dup map transform_values transform_keys each_with_object nil? []= serialize},
255
+ :ivar => %i{dup map transform_values transform_keys each_with_object serialize},
256
+ :const => %i{checked_serialize deep_clone_object},
257
+ }
258
+ end
259
+
260
+ # Method calls generated by SerdeTransform
261
+ private_class_method def self.whitelisted_methods_for_deserialize
262
+ @whitelisted_methods_for_deserialize ||= {
263
+ :lvar => %i{dup map transform_values transform_keys each_with_object nil? []= to_f},
264
+ :const => %i{deserialize from_hash deep_clone_object soft_assert_handler},
265
+ }
266
+ end
267
+ end
268
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+ # typed: false
3
+
4
+ module T::Props
5
+
6
+ # Helper for generating methods that replace themselves with a specialized
7
+ # version on first use. The main use case is when we want to generate a
8
+ # method using the full set of props on a class; we can't do that during
9
+ # prop definition because we have no way of knowing whether we are defining
10
+ # the last prop.
11
+ #
12
+ # See go/M8yrvzX2 (Stripe-internal) for discussion of security considerations.
13
+ # In outline, while `class_eval` is a bit scary, we believe that as long as
14
+ # all inputs are defined in version control (and this is enforced by calling
15
+ # `disable_lazy_evaluation!` appropriately), risk isn't significantly higher
16
+ # than with build-time codegen.
17
+ module HasLazilySpecializedMethods
18
+ extend T::Sig
19
+
20
+ class SourceEvaluationDisabled < RuntimeError
21
+ def initialize
22
+ super("Evaluation of lazily-defined methods is disabled")
23
+ end
24
+ end
25
+
26
+ # Disable any future evaluation of lazily-defined methods.
27
+ #
28
+ # This is intended to be called after startup but before interacting with
29
+ # the outside world, to limit attack surface for our `class_eval` use.
30
+ #
31
+ # Note it does _not_ prevent explicit calls to `eagerly_define_lazy_methods!`
32
+ # from working.
33
+ sig {void}
34
+ def self.disable_lazy_evaluation!
35
+ @lazy_evaluation_disabled ||= true
36
+ end
37
+
38
+ sig {returns(T::Boolean)}
39
+ def self.lazy_evaluation_enabled?
40
+ !@lazy_evaluation_disabled
41
+ end
42
+
43
+ module DecoratorMethods
44
+ extend T::Sig
45
+
46
+ sig {returns(T::Hash[Symbol, T.proc.returns(String)]).checked(:never)}
47
+ private def lazily_defined_methods
48
+ @lazily_defined_methods ||= {}
49
+ end
50
+
51
+ sig {params(name: Symbol).void}
52
+ private def eval_lazily_defined_method!(name)
53
+ if !HasLazilySpecializedMethods.lazy_evaluation_enabled?
54
+ raise SourceEvaluationDisabled.new
55
+ end
56
+
57
+ source = lazily_defined_methods.fetch(name).call
58
+
59
+ cls = decorated_class
60
+ cls.class_eval(source.to_s)
61
+ cls.send(:private, name)
62
+ end
63
+
64
+ sig {params(name: Symbol, blk: T.proc.returns(String)).void}
65
+ private def enqueue_lazy_method_definition!(name, &blk)
66
+ lazily_defined_methods[name] = blk
67
+
68
+ cls = decorated_class
69
+ cls.send(:define_method, name) do |*args|
70
+ self.class.decorator.send(:eval_lazily_defined_method!, name)
71
+ send(name, *args)
72
+ end
73
+ if cls.respond_to?(:ruby2_keywords, true)
74
+ cls.send(:ruby2_keywords, name)
75
+ end
76
+ cls.send(:private, name)
77
+ end
78
+
79
+ sig {void}
80
+ def eagerly_define_lazy_methods!
81
+ return if lazily_defined_methods.empty?
82
+
83
+ source = lazily_defined_methods.values.map(&:call).map(&:to_s).join("\n\n")
84
+
85
+ cls = decorated_class
86
+ cls.class_eval(source)
87
+ lazily_defined_methods.keys.each {|name| cls.send(:private, name)}
88
+ lazily_defined_methods.clear
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+ # typed: false
3
+
4
+ module T::Props::Optional
5
+ include T::Props::Plugin
6
+ end
7
+
8
+
9
+ ##############################################
10
+
11
+
12
+ # NB: This must stay in the same file where T::Props::Optional is defined due to
13
+ # T::Props::Decorator#apply_plugin; see https://git.corp.stripe.com/stripe-internal/pay-server/blob/fc7f15593b49875f2d0499ffecfd19798bac05b3/chalk/odm/lib/chalk-odm/document_decorator.rb#L716-L717
14
+ module T::Props::Optional::DecoratorMethods
15
+ extend T::Sig
16
+
17
+ VALID_RULE_KEYS = {
18
+ default: true,
19
+ factory: true,
20
+ }.freeze
21
+ private_constant :VALID_RULE_KEYS
22
+
23
+ DEFAULT_SETTER_RULE_KEY = :_t_props_private_apply_default
24
+ private_constant :DEFAULT_SETTER_RULE_KEY
25
+
26
+ def valid_rule_key?(key)
27
+ super || VALID_RULE_KEYS[key]
28
+ end
29
+
30
+ def prop_optional?(prop); prop_rules(prop)[:fully_optional]; end
31
+
32
+ def compute_derived_rules(rules)
33
+ rules[:fully_optional] = !T::Props::Utils.need_nil_write_check?(rules)
34
+ rules[:need_nil_read_check] = T::Props::Utils.need_nil_read_check?(rules)
35
+ end
36
+
37
+ # checked(:never) - O(runtime object construction)
38
+ sig {returns(T::Hash[Symbol, T::Props::Private::ApplyDefault]).checked(:never)}
39
+ attr_reader :props_with_defaults
40
+
41
+ # checked(:never) - O(runtime object construction)
42
+ sig {returns(T::Hash[Symbol, T::Props::Private::SetterFactory::SetterProc]).checked(:never)}
43
+ attr_reader :props_without_defaults
44
+
45
+ def add_prop_definition(prop, rules)
46
+ compute_derived_rules(rules)
47
+
48
+ default_setter = T::Props::Private::ApplyDefault.for(decorated_class, rules)
49
+ if default_setter
50
+ @props_with_defaults ||= {}
51
+ @props_with_defaults[prop] = default_setter
52
+ @props_without_defaults&.delete(prop) # Handle potential override
53
+
54
+ rules[DEFAULT_SETTER_RULE_KEY] = default_setter
55
+ else
56
+ @props_without_defaults ||= {}
57
+ @props_without_defaults[prop] = rules.fetch(:setter_proc)
58
+ @props_with_defaults&.delete(prop) # Handle potential override
59
+ end
60
+
61
+ super
62
+ end
63
+
64
+ def prop_validate_definition!(name, cls, rules, type)
65
+ result = super
66
+
67
+ if rules.key?(:default) && rules.key?(:factory)
68
+ raise ArgumentError.new("Setting both :default and :factory is invalid. See: go/chalk-docs")
69
+ end
70
+
71
+ result
72
+ end
73
+
74
+ def has_default?(rules)
75
+ rules.include?(DEFAULT_SETTER_RULE_KEY)
76
+ end
77
+
78
+ def get_default(rules, instance_class)
79
+ rules[DEFAULT_SETTER_RULE_KEY]&.default
80
+ end
81
+ end