sorbet-runtime 0.4.4667 → 0.5.6189
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/lib/sorbet-runtime.rb +15 -3
- data/lib/types/_types.rb +26 -17
- data/lib/types/boolean.rb +1 -1
- data/lib/types/compatibility_patches.rb +65 -10
- data/lib/types/configuration.rb +93 -7
- data/lib/types/enum.rb +371 -0
- data/lib/types/generic.rb +2 -2
- data/lib/types/interface_wrapper.rb +4 -4
- data/lib/types/non_forcing_constants.rb +61 -0
- data/lib/types/private/abstract/data.rb +2 -2
- data/lib/types/private/abstract/declare.rb +3 -0
- data/lib/types/private/abstract/validate.rb +7 -7
- data/lib/types/private/casts.rb +27 -0
- data/lib/types/private/class_utils.rb +8 -5
- data/lib/types/private/methods/_methods.rb +80 -28
- data/lib/types/private/methods/call_validation.rb +5 -47
- data/lib/types/private/methods/decl_builder.rb +14 -56
- data/lib/types/private/methods/modes.rb +5 -7
- data/lib/types/private/methods/signature.rb +32 -18
- data/lib/types/private/methods/signature_validation.rb +29 -35
- data/lib/types/private/retry.rb +10 -0
- data/lib/types/private/sealed.rb +21 -1
- data/lib/types/private/types/type_alias.rb +31 -0
- data/lib/types/private/types/void.rb +4 -3
- data/lib/types/profile.rb +5 -1
- data/lib/types/props/_props.rb +3 -7
- data/lib/types/props/constructor.rb +29 -9
- data/lib/types/props/custom_type.rb +51 -27
- data/lib/types/props/decorator.rb +248 -405
- data/lib/types/props/generated_code_validation.rb +268 -0
- data/lib/types/props/has_lazily_specialized_methods.rb +92 -0
- data/lib/types/props/optional.rb +37 -41
- data/lib/types/props/plugin.rb +23 -1
- data/lib/types/props/pretty_printable.rb +3 -3
- data/lib/types/props/private/apply_default.rb +170 -0
- data/lib/types/props/private/deserializer_generator.rb +165 -0
- data/lib/types/props/private/parser.rb +32 -0
- data/lib/types/props/private/serde_transform.rb +186 -0
- data/lib/types/props/private/serializer_generator.rb +77 -0
- data/lib/types/props/private/setter_factory.rb +139 -0
- data/lib/types/props/serializable.rb +137 -192
- data/lib/types/props/type_validation.rb +19 -6
- data/lib/types/props/utils.rb +3 -7
- data/lib/types/props/weak_constructor.rb +51 -14
- data/lib/types/sig.rb +6 -6
- data/lib/types/types/attached_class.rb +37 -0
- data/lib/types/types/base.rb +26 -2
- data/lib/types/types/fixed_array.rb +28 -2
- data/lib/types/types/fixed_hash.rb +11 -10
- data/lib/types/types/intersection.rb +6 -0
- data/lib/types/types/noreturn.rb +4 -0
- data/lib/types/types/self_type.rb +4 -0
- data/lib/types/types/simple.rb +22 -1
- data/lib/types/types/t_enum.rb +38 -0
- data/lib/types/types/type_parameter.rb +1 -1
- data/lib/types/types/type_variable.rb +1 -1
- data/lib/types/types/typed_array.rb +7 -2
- data/lib/types/types/typed_enumerable.rb +28 -17
- data/lib/types/types/typed_enumerator.rb +7 -2
- data/lib/types/types/typed_hash.rb +8 -3
- data/lib/types/types/typed_range.rb +7 -2
- data/lib/types/types/typed_set.rb +7 -2
- data/lib/types/types/union.rb +37 -5
- data/lib/types/types/untyped.rb +4 -0
- data/lib/types/utils.rb +43 -11
- metadata +103 -11
- data/lib/types/private/error_handler.rb +0 -0
- data/lib/types/runtime_profiled.rb +0 -24
- data/lib/types/types/opus_enum.rb +0 -33
@@ -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.even?
|
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 # rubocop:disable Lint/BooleanSymbol
|
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 # rubocop:disable Lint/BooleanSymbol
|
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.each_key {|name| cls.send(:private, name)}
|
88
|
+
lazily_defined_methods.clear
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
data/lib/types/props/optional.rb
CHANGED
@@ -12,51 +12,58 @@ end
|
|
12
12
|
# NB: This must stay in the same file where T::Props::Optional is defined due to
|
13
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
14
|
module T::Props::Optional::DecoratorMethods
|
15
|
-
|
16
|
-
# applicable (e.g., :on_load only applies when using T::Serializable).
|
17
|
-
VALID_OPTIONAL_RULES = Set[
|
18
|
-
:existing, # deprecated
|
19
|
-
:on_load,
|
20
|
-
false,
|
21
|
-
true,
|
22
|
-
].freeze
|
23
|
-
|
24
|
-
def valid_props
|
25
|
-
super + [
|
26
|
-
:default,
|
27
|
-
:factory,
|
28
|
-
:optional,
|
29
|
-
]
|
30
|
-
end
|
15
|
+
extend T::Sig
|
31
16
|
|
32
|
-
|
17
|
+
VALID_RULE_KEYS = {
|
18
|
+
default: true,
|
19
|
+
factory: true,
|
20
|
+
}.freeze
|
21
|
+
private_constant :VALID_RULE_KEYS
|
33
22
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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]
|
39
28
|
end
|
40
29
|
|
30
|
+
def prop_optional?(prop); prop_rules(prop)[:fully_optional]; end
|
31
|
+
|
41
32
|
def compute_derived_rules(rules)
|
42
33
|
rules[:fully_optional] = !T::Props::Utils.need_nil_write_check?(rules)
|
43
34
|
rules[:need_nil_read_check] = T::Props::Utils.need_nil_read_check?(rules)
|
44
35
|
end
|
45
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
|
+
|
46
45
|
def add_prop_definition(prop, rules)
|
47
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
|
+
|
48
61
|
super
|
49
62
|
end
|
50
63
|
|
51
64
|
def prop_validate_definition!(name, cls, rules, type)
|
52
65
|
result = super
|
53
66
|
|
54
|
-
if (rules_optional = rules[:optional])
|
55
|
-
if !VALID_OPTIONAL_RULES.include?(rules_optional)
|
56
|
-
raise ArgumentError.new(":optional must be one of #{VALID_OPTIONAL_RULES.inspect}")
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
67
|
if rules.key?(:default) && rules.key?(:factory)
|
61
68
|
raise ArgumentError.new("Setting both :default and :factory is invalid. See: go/chalk-docs")
|
62
69
|
end
|
@@ -65,21 +72,10 @@ module T::Props::Optional::DecoratorMethods
|
|
65
72
|
end
|
66
73
|
|
67
74
|
def has_default?(rules)
|
68
|
-
rules.include?(
|
75
|
+
rules.include?(DEFAULT_SETTER_RULE_KEY)
|
69
76
|
end
|
70
77
|
|
71
78
|
def get_default(rules, instance_class)
|
72
|
-
|
73
|
-
default = rules[:default]
|
74
|
-
T::Props::Utils.deep_clone_object(default)
|
75
|
-
elsif rules.include?(:factory)
|
76
|
-
# Factory should never be nil if the key is specified, but
|
77
|
-
# we do this rather than 'elsif rules[:factory]' for
|
78
|
-
# consistency with :default.
|
79
|
-
factory = rules[:factory]
|
80
|
-
instance_class.class_exec(&factory)
|
81
|
-
else
|
82
|
-
nil
|
83
|
-
end
|
79
|
+
rules[DEFAULT_SETTER_RULE_KEY]&.default
|
84
80
|
end
|
85
81
|
end
|