sorbet-runtime 0.5.5255 → 0.5.5284
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.
- checksums.yaml +4 -4
- data/lib/sorbet-runtime.rb +1 -0
- data/lib/types/private/methods/signature.rb +7 -1
- data/lib/types/props/decorator.rb +65 -133
- data/lib/types/props/plugin.rb +23 -1
- data/lib/types/props/private/setter_factory.rb +92 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6570883296df2b8c2f0f18bd126c932daeac62effd65b35544f04c5b467a66a2
|
|
4
|
+
data.tar.gz: 43f95a4fdd8d83076f72d3094438995b05440d222d487a67c20b328745eecc13
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 26308242fd0d09ba1585594cf116150ed3a3112c503e4f11ee96a3301450a9cf7742729200c8a4652d28a6b166eca32dd1c6e766c6a16099b6c403d67006c45b
|
|
7
|
+
data.tar.gz: c5e8e54c724a32857f50642cd18400a0886e66a33cc5a84ddc68d75919ebc576e5d7bd52365919f69b5a653591dced8c5566223fa375071af8ae3cd75f02297f
|
data/lib/sorbet-runtime.rb
CHANGED
|
@@ -93,6 +93,7 @@ require_relative 'types/props/errors'
|
|
|
93
93
|
require_relative 'types/props/plugin'
|
|
94
94
|
require_relative 'types/props/utils'
|
|
95
95
|
# Props that run sigs statically so have to be after all the others :(
|
|
96
|
+
require_relative 'types/props/private/setter_factory'
|
|
96
97
|
require_relative 'types/props/optional'
|
|
97
98
|
require_relative 'types/props/weak_constructor'
|
|
98
99
|
require_relative 'types/props/constructor'
|
|
@@ -51,8 +51,14 @@ class T::Private::Methods::Signature
|
|
|
51
51
|
@on_failure = on_failure
|
|
52
52
|
@override_allow_incompatible = override_allow_incompatible
|
|
53
53
|
|
|
54
|
-
param_names = parameters.map {|_, name| name}
|
|
55
54
|
declared_param_names = raw_arg_types.keys
|
|
55
|
+
# If sig params are declared but there is a single parameter with a missing name
|
|
56
|
+
# **and** the method ends with a "=", assume it is a writer method generated
|
|
57
|
+
# by attr_writer or attr_accessor
|
|
58
|
+
writer_method = declared_param_names != [nil] && parameters == [[:req]] && method_name[-1] == "="
|
|
59
|
+
# For writer methods, map the single parameter to the method name without the "=" at the end
|
|
60
|
+
parameters = [[:req, method_name[0...-1].to_sym]] if writer_method
|
|
61
|
+
param_names = parameters.map {|_, name| name}
|
|
56
62
|
missing_names = param_names - declared_param_names
|
|
57
63
|
extra_names = declared_param_names - param_names
|
|
58
64
|
if !missing_names.empty?
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
# typed:
|
|
2
|
+
# typed: strict
|
|
3
3
|
|
|
4
4
|
# NB: This is not actually a decorator. It's just named that way for consistency
|
|
5
5
|
# with DocumentDecorator and ModelDecorator (which both seem to have been written
|
|
@@ -11,26 +11,27 @@ class T::Props::Decorator
|
|
|
11
11
|
extend T::Sig
|
|
12
12
|
|
|
13
13
|
Rules = T.type_alias {T::Hash[Symbol, T.untyped]}
|
|
14
|
-
|
|
15
|
-
DecoratedInstance = T.type_alias {T.untyped} # Would be T::Props, but that produces circular reference errors in some circumstances
|
|
14
|
+
DecoratedInstance = T.type_alias {Object} # Would be T::Props, but that produces circular reference errors in some circumstances
|
|
16
15
|
PropType = T.type_alias {T.any(T::Types::Base, T::Props::CustomType)}
|
|
17
16
|
PropTypeOrClass = T.type_alias {T.any(PropType, Module)}
|
|
18
17
|
|
|
19
18
|
class NoRulesError < StandardError; end
|
|
20
19
|
|
|
21
|
-
|
|
20
|
+
EMPTY_PROPS = T.let({}.freeze, T::Hash[Symbol, Rules])
|
|
21
|
+
private_constant :EMPTY_PROPS
|
|
22
|
+
|
|
23
|
+
sig {params(klass: T.untyped).void}
|
|
22
24
|
def initialize(klass)
|
|
23
|
-
@class = klass
|
|
24
|
-
|
|
25
|
-
Private.apply_decorator_methods(mod, self)
|
|
25
|
+
@class = T.let(klass, T.all(Module, T::Props::ClassMethods))
|
|
26
|
+
@class.plugins.each do |mod|
|
|
27
|
+
T::Props::Plugin::Private.apply_decorator_methods(mod, self)
|
|
26
28
|
end
|
|
29
|
+
@props = T.let(EMPTY_PROPS, T::Hash[Symbol, Rules])
|
|
27
30
|
end
|
|
28
31
|
|
|
29
32
|
# checked(:never) - O(prop accesses)
|
|
30
33
|
sig {returns(T::Hash[Symbol, Rules]).checked(:never)}
|
|
31
|
-
|
|
32
|
-
@props ||= {}.freeze
|
|
33
|
-
end
|
|
34
|
+
attr_reader :props
|
|
34
35
|
|
|
35
36
|
# Try to avoid using this; post-definition mutation of prop rules is
|
|
36
37
|
# surprising and hard to reason about.
|
|
@@ -62,7 +63,7 @@ class T::Props::Decorator
|
|
|
62
63
|
@props = @props.merge(prop => rules.freeze).freeze
|
|
63
64
|
end
|
|
64
65
|
|
|
65
|
-
VALID_RULE_KEYS = %i{
|
|
66
|
+
VALID_RULE_KEYS = T.let(%i{
|
|
66
67
|
enum
|
|
67
68
|
foreign
|
|
68
69
|
foreign_hint_only
|
|
@@ -76,16 +77,16 @@ class T::Props::Decorator
|
|
|
76
77
|
extra
|
|
77
78
|
optional
|
|
78
79
|
_tnilable
|
|
79
|
-
}.map {|k| [k, true]}.to_h.freeze
|
|
80
|
+
}.map {|k| [k, true]}.to_h.freeze, T::Hash[Symbol, T::Boolean])
|
|
80
81
|
private_constant :VALID_RULE_KEYS
|
|
81
82
|
|
|
82
83
|
sig {params(key: Symbol).returns(T::Boolean).checked(:never)}
|
|
83
84
|
def valid_rule_key?(key)
|
|
84
|
-
VALID_RULE_KEYS[key]
|
|
85
|
+
!!VALID_RULE_KEYS[key]
|
|
85
86
|
end
|
|
86
87
|
|
|
87
88
|
# checked(:never) - O(prop accesses)
|
|
88
|
-
sig {returns(
|
|
89
|
+
sig {returns(T.all(Module, T::Props::ClassMethods)).checked(:never)}
|
|
89
90
|
def decorated_class; @class; end
|
|
90
91
|
|
|
91
92
|
# Accessors
|
|
@@ -109,87 +110,15 @@ class T::Props::Decorator
|
|
|
109
110
|
instance.instance_variable_get(rules ? rules[:accessor_key] : '@' + prop.to_s) # rubocop:disable PrisonGuard/NoLurkyInstanceVariableAccess
|
|
110
111
|
end
|
|
111
112
|
|
|
112
|
-
# For performance, don't use named params here.
|
|
113
|
-
# Passing in rules here is purely a performance optimization.
|
|
114
|
-
#
|
|
115
|
-
# checked(:never) - O(prop accesses)
|
|
116
|
-
sig do
|
|
117
|
-
params(
|
|
118
|
-
instance: DecoratedInstance,
|
|
119
|
-
prop: Symbol,
|
|
120
|
-
value: T.untyped,
|
|
121
|
-
rules: T.nilable(Rules)
|
|
122
|
-
)
|
|
123
|
-
.void
|
|
124
|
-
.checked(:never)
|
|
125
|
-
end
|
|
126
|
-
def set(instance, prop, value, rules=props[prop.to_sym])
|
|
127
|
-
# For backwards compatibility, fall back to reconstructing the accessor key
|
|
128
|
-
# (though it would probably make more sense to raise in that case).
|
|
129
|
-
instance.instance_variable_set(rules ? rules[:accessor_key] : '@' + prop.to_s, value) # rubocop:disable PrisonGuard/NoLurkyInstanceVariableAccess
|
|
130
|
-
end
|
|
131
|
-
|
|
132
113
|
# Use this to validate that a value will validate for a given prop. Useful for knowing whether a value can be set on a model without setting it.
|
|
133
114
|
#
|
|
134
115
|
# checked(:never) - potentially O(prop accesses) depending on usage pattern
|
|
135
116
|
sig {params(prop: Symbol, val: T.untyped).void.checked(:never)}
|
|
136
117
|
def validate_prop_value(prop, val)
|
|
137
|
-
#
|
|
138
|
-
#
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
# Passing in rules here is purely a performance optimization.
|
|
143
|
-
#
|
|
144
|
-
# checked(:never) - O(prop accesses)
|
|
145
|
-
sig {params(prop: Symbol, val: T.untyped, rules: Rules).void.checked(:never)}
|
|
146
|
-
private def check_prop_type(prop, val, rules=prop_rules(prop))
|
|
147
|
-
type_object = rules.fetch(:type_object)
|
|
148
|
-
type = rules.fetch(:type)
|
|
149
|
-
|
|
150
|
-
# TODO: ideally we'd add `&& rules[:optional] != :existing` to this check
|
|
151
|
-
# (it makes sense to treat those props required in this context), but we'd need
|
|
152
|
-
# to be sure that doesn't break any existing code first.
|
|
153
|
-
if val.nil?
|
|
154
|
-
if !T::Props::Utils.need_nil_write_check?(rules) || (rules.key?(:default) && rules[:default].nil?)
|
|
155
|
-
return
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
if rules[:raise_on_nil_write]
|
|
159
|
-
raise T::Props::InvalidValueError.new("Can't set #{@class.name}.#{prop} to #{val.inspect} " \
|
|
160
|
-
"(instance of #{val.class}) - need a #{type}")
|
|
161
|
-
end
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
# T::Props::CustomType is not a real object based class so that we can not run real type check call.
|
|
165
|
-
# T::Props::CustomType.valid?() is only a helper function call.
|
|
166
|
-
valid =
|
|
167
|
-
if type.is_a?(T::Props::CustomType) && T::Props::Utils.optional_prop?(rules)
|
|
168
|
-
type.valid?(val)
|
|
169
|
-
else
|
|
170
|
-
type_object.valid?(val)
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
if !valid
|
|
174
|
-
raise T::Props::InvalidValueError.new("Can't set #{@class.name}.#{prop} to #{val.inspect} " \
|
|
175
|
-
"(instance of #{val.class}) - need a #{type_object}")
|
|
176
|
-
end
|
|
177
|
-
rescue T::Props::InvalidValueError => err
|
|
178
|
-
caller_loc = T.must(caller_locations(8, 1))[0]
|
|
179
|
-
|
|
180
|
-
pretty_message = "Parameter '#{prop}': #{err.message}\n" \
|
|
181
|
-
"Caller: #{caller_loc.path}:#{caller_loc.lineno}\n"
|
|
182
|
-
|
|
183
|
-
T::Configuration.call_validation_error_handler(
|
|
184
|
-
nil,
|
|
185
|
-
message: err.message,
|
|
186
|
-
pretty_message: pretty_message,
|
|
187
|
-
kind: 'Parameter',
|
|
188
|
-
name: prop,
|
|
189
|
-
type: type,
|
|
190
|
-
value: val,
|
|
191
|
-
location: caller_loc,
|
|
192
|
-
)
|
|
118
|
+
# We call `setter_proc` here without binding to an instance, so it'll run
|
|
119
|
+
# `instance_variable_set` if validation passes, but nothing will care.
|
|
120
|
+
# We only care about the validation.
|
|
121
|
+
prop_rules(prop).fetch(:setter_proc).call(val)
|
|
193
122
|
end
|
|
194
123
|
|
|
195
124
|
# For performance, don't use named params here.
|
|
@@ -198,21 +127,24 @@ class T::Props::Decorator
|
|
|
198
127
|
# the default, which raises if the prop doesn't exist (this maintains
|
|
199
128
|
# preexisting behavior).
|
|
200
129
|
#
|
|
130
|
+
# Note this path is NOT used by generated setters on instances,
|
|
131
|
+
# which are defined using `setter_proc` directly.
|
|
132
|
+
#
|
|
201
133
|
# checked(:never) - O(prop accesses)
|
|
202
134
|
sig do
|
|
203
135
|
params(
|
|
204
136
|
instance: DecoratedInstance,
|
|
205
137
|
prop: Symbol,
|
|
206
138
|
val: T.untyped,
|
|
207
|
-
rules:
|
|
139
|
+
rules: Rules
|
|
208
140
|
)
|
|
209
141
|
.void
|
|
210
142
|
.checked(:never)
|
|
211
143
|
end
|
|
212
144
|
def prop_set(instance, prop, val, rules=prop_rules(prop))
|
|
213
|
-
|
|
214
|
-
set(instance, prop, val, rules)
|
|
145
|
+
instance.instance_exec(val, &rules.fetch(:setter_proc))
|
|
215
146
|
end
|
|
147
|
+
alias_method :set, :prop_set
|
|
216
148
|
|
|
217
149
|
# For performance, don't use named params here.
|
|
218
150
|
# Passing in rules here is purely a performance optimization.
|
|
@@ -253,18 +185,18 @@ class T::Props::Decorator
|
|
|
253
185
|
prop: Symbol,
|
|
254
186
|
foreign_class: Module,
|
|
255
187
|
rules: Rules,
|
|
256
|
-
opts: Hash
|
|
188
|
+
opts: T::Hash[Symbol, T.untyped],
|
|
257
189
|
)
|
|
258
190
|
.returns(T.untyped)
|
|
259
191
|
.checked(:never)
|
|
260
192
|
end
|
|
261
193
|
def foreign_prop_get(instance, prop, foreign_class, rules=props[prop.to_sym], opts={})
|
|
262
194
|
return if !(value = prop_get(instance, prop, rules))
|
|
263
|
-
foreign_class.load(value, {}, opts)
|
|
195
|
+
T.unsafe(foreign_class).load(value, {}, opts)
|
|
264
196
|
end
|
|
265
197
|
|
|
266
198
|
# TODO: we should really be checking all the methods on `cls`, not just Object
|
|
267
|
-
BANNED_METHOD_NAMES = Object.instance_methods.to_set.freeze
|
|
199
|
+
BANNED_METHOD_NAMES = T.let(Object.instance_methods.to_set.freeze, T::Set[Symbol])
|
|
268
200
|
|
|
269
201
|
# checked(:never) - Rules hash is expensive to check
|
|
270
202
|
sig do
|
|
@@ -314,6 +246,7 @@ class T::Props::Decorator
|
|
|
314
246
|
nil
|
|
315
247
|
end
|
|
316
248
|
|
|
249
|
+
sig {params(name: Symbol).void}
|
|
317
250
|
private def validate_prop_name(name)
|
|
318
251
|
if name !~ /\A[A-Za-z_][A-Za-z0-9_-]*\z/
|
|
319
252
|
raise ArgumentError.new("Invalid prop name in #{@class.name}: #{name}")
|
|
@@ -440,7 +373,7 @@ class T::Props::Decorator
|
|
|
440
373
|
# are ultimately included does.
|
|
441
374
|
#
|
|
442
375
|
if defined?(Opus) && defined?(Opus::Sensitivity) && defined?(Opus::Sensitivity::PIIable)
|
|
443
|
-
if sensitivity_and_pii[:pii] && @class.is_a?(Class) &&
|
|
376
|
+
if sensitivity_and_pii[:pii] && @class.is_a?(Class) && !T.unsafe(@class).contains_pii?
|
|
444
377
|
raise ArgumentError.new(
|
|
445
378
|
'Cannot include a pii prop in a class that declares `contains_no_pii`'
|
|
446
379
|
)
|
|
@@ -499,7 +432,10 @@ class T::Props::Decorator
|
|
|
499
432
|
rules[:serializable_subtype] = hash_key_custom_type
|
|
500
433
|
end
|
|
501
434
|
|
|
435
|
+
rules[:setter_proc] = T::Props::Private::SetterFactory.build_setter_proc(@class, name, rules).freeze
|
|
436
|
+
|
|
502
437
|
add_prop_definition(name, rules)
|
|
438
|
+
|
|
503
439
|
# NB: using `without_accessors` doesn't make much sense unless you also define some other way to
|
|
504
440
|
# get at the property (e.g., Chalk::ODM::Document exposes `get` and `set`).
|
|
505
441
|
define_getter_and_setter(name, rules) unless rules[:without_accessors]
|
|
@@ -518,18 +454,23 @@ class T::Props::Decorator
|
|
|
518
454
|
private def define_getter_and_setter(name, rules)
|
|
519
455
|
T::Configuration.without_ruby_warnings do
|
|
520
456
|
if !rules[:immutable]
|
|
521
|
-
|
|
522
|
-
|
|
457
|
+
if method(:prop_set).owner != T::Props::Decorator
|
|
458
|
+
@class.send(:define_method, "#{name}=") do |val|
|
|
459
|
+
T.unsafe(self.class).decorator.prop_set(self, name, val, rules)
|
|
460
|
+
end
|
|
461
|
+
else
|
|
462
|
+
# Fast path (~4x faster as of Ruby 2.6)
|
|
463
|
+
@class.send(:define_method, "#{name}=", &rules.fetch(:setter_proc))
|
|
523
464
|
end
|
|
524
465
|
end
|
|
525
466
|
|
|
526
467
|
if method(:prop_get).owner != T::Props::Decorator || rules.key?(:ifunset)
|
|
527
468
|
@class.send(:define_method, name) do
|
|
528
|
-
self.class.decorator.prop_get(self, name, rules)
|
|
469
|
+
T.unsafe(self.class).decorator.prop_get(self, name, rules)
|
|
529
470
|
end
|
|
530
471
|
else
|
|
531
472
|
# Fast path (~30x faster as of Ruby 2.6)
|
|
532
|
-
@class.attr_reader
|
|
473
|
+
@class.send(:attr_reader, name) # send is used because `attr_reader` is private in 2.4
|
|
533
474
|
end
|
|
534
475
|
end
|
|
535
476
|
end
|
|
@@ -597,10 +538,10 @@ class T::Props::Decorator
|
|
|
597
538
|
end
|
|
598
539
|
|
|
599
540
|
# From T::Props::Utils.deep_clone_object, plus String
|
|
600
|
-
TYPES_NOT_NEEDING_CLONE = [TrueClass, FalseClass, NilClass, Symbol, String, Numeric]
|
|
541
|
+
TYPES_NOT_NEEDING_CLONE = T.let([TrueClass, FalseClass, NilClass, Symbol, String, Numeric], T::Array[Module])
|
|
601
542
|
|
|
602
543
|
# checked(:never) - Typechecks internally
|
|
603
|
-
sig {params(type: PropType).returns(T::Boolean).checked(:never)}
|
|
544
|
+
sig {params(type: PropType).returns(T.nilable(T::Boolean)).checked(:never)}
|
|
604
545
|
private def shallow_clone_ok(type)
|
|
605
546
|
inner_type =
|
|
606
547
|
if type.is_a?(T::Types::TypedArray)
|
|
@@ -642,7 +583,7 @@ class T::Props::Decorator
|
|
|
642
583
|
end
|
|
643
584
|
|
|
644
585
|
# checked(:never) - Rules hash is expensive to check
|
|
645
|
-
sig {params(prop_name: Symbol, rules:
|
|
586
|
+
sig {params(prop_name: Symbol, rules: Rules).void.checked(:never)}
|
|
646
587
|
private def validate_not_missing_sensitivity(prop_name, rules)
|
|
647
588
|
if rules[:sensitivity].nil?
|
|
648
589
|
if rules[:redaction]
|
|
@@ -747,6 +688,7 @@ class T::Props::Decorator
|
|
|
747
688
|
# *haven't* allowed additional options in the past and want to
|
|
748
689
|
# default to keeping this interface narrow.
|
|
749
690
|
@class.send(:define_method, fk_method) do |allow_direct_mutation: nil|
|
|
691
|
+
foreign = T.let(foreign, T.untyped)
|
|
750
692
|
if foreign.is_a?(Proc)
|
|
751
693
|
resolved_foreign = foreign.call
|
|
752
694
|
if !resolved_foreign.respond_to?(:load)
|
|
@@ -765,7 +707,7 @@ class T::Props::Decorator
|
|
|
765
707
|
opts = {allow_direct_mutation: allow_direct_mutation}
|
|
766
708
|
end
|
|
767
709
|
|
|
768
|
-
self.class.decorator.foreign_prop_get(self, prop_name, foreign, rules, opts)
|
|
710
|
+
T.unsafe(self.class).decorator.foreign_prop_get(self, prop_name, foreign, rules, opts)
|
|
769
711
|
end
|
|
770
712
|
|
|
771
713
|
force_fk_method = "#{fk_method}!"
|
|
@@ -777,7 +719,7 @@ class T::Props::Decorator
|
|
|
777
719
|
storytime: {method: force_fk_method, class: self.class}
|
|
778
720
|
)
|
|
779
721
|
end
|
|
780
|
-
|
|
722
|
+
loaded_foreign
|
|
781
723
|
end
|
|
782
724
|
|
|
783
725
|
@class.send(:define_method, "#{prop_name}_record") do |allow_direct_mutation: nil|
|
|
@@ -826,16 +768,17 @@ class T::Props::Decorator
|
|
|
826
768
|
#
|
|
827
769
|
# This gets called when a module or class that extends T::Props gets included, extended,
|
|
828
770
|
# prepended, or inherited.
|
|
829
|
-
sig {params(child:
|
|
771
|
+
sig {params(child: Module).void.checked(:never)}
|
|
830
772
|
def model_inherited(child)
|
|
831
773
|
child.extend(T::Props::ClassMethods)
|
|
832
|
-
child.
|
|
774
|
+
child = T.cast(child, T.all(Module, T::Props::ClassMethods))
|
|
833
775
|
|
|
776
|
+
child.plugins.concat(decorated_class.plugins)
|
|
834
777
|
decorated_class.plugins.each do |mod|
|
|
835
778
|
# NB: apply_class_methods must not be an instance method on the decorator itself,
|
|
836
779
|
# otherwise we'd have to call child.decorator here, which would create the decorator
|
|
837
780
|
# before any `decorator_class` override has a chance to take effect (see the comment below).
|
|
838
|
-
Private.apply_class_methods(mod, child)
|
|
781
|
+
T::Props::Plugin::Private.apply_class_methods(mod, child)
|
|
839
782
|
end
|
|
840
783
|
|
|
841
784
|
props.each do |name, rules|
|
|
@@ -855,9 +798,18 @@ class T::Props::Decorator
|
|
|
855
798
|
#
|
|
856
799
|
unless rules[:without_accessors]
|
|
857
800
|
if child.decorator.method(:prop_get).owner != method(:prop_get).owner &&
|
|
858
|
-
child.instance_method(name).source_location
|
|
801
|
+
child.instance_method(name).source_location&.first == __FILE__
|
|
859
802
|
child.send(:define_method, name) do
|
|
860
|
-
self.class.decorator.prop_get(self, name, rules)
|
|
803
|
+
T.unsafe(self.class).decorator.prop_get(self, name, rules)
|
|
804
|
+
end
|
|
805
|
+
end
|
|
806
|
+
|
|
807
|
+
unless rules[:immutable]
|
|
808
|
+
if child.decorator.method(:prop_set).owner != method(:prop_set).owner &&
|
|
809
|
+
child.instance_method("#{name}=").source_location&.first == __FILE__
|
|
810
|
+
child.send(:define_method, "#{name}=") do |val|
|
|
811
|
+
T.unsafe(self.class).decorator.prop_set(self, name, val, rules)
|
|
812
|
+
end
|
|
861
813
|
end
|
|
862
814
|
end
|
|
863
815
|
end
|
|
@@ -867,27 +819,7 @@ class T::Props::Decorator
|
|
|
867
819
|
sig {params(mod: Module).void.checked(:never)}
|
|
868
820
|
def plugin(mod)
|
|
869
821
|
decorated_class.plugins << mod
|
|
870
|
-
Private.apply_class_methods(mod, decorated_class)
|
|
871
|
-
Private.apply_decorator_methods(mod, self)
|
|
872
|
-
end
|
|
873
|
-
|
|
874
|
-
module Private
|
|
875
|
-
# These need to be non-instance methods so we can use them without prematurely creating the
|
|
876
|
-
# child decorator in `model_inherited` (see comments there for details).
|
|
877
|
-
def self.apply_class_methods(plugin, target)
|
|
878
|
-
if plugin.const_defined?('ClassMethods')
|
|
879
|
-
# FIXME: This will break preloading, selective test execution, etc if `mod::ClassMethods`
|
|
880
|
-
# is ever defined in a separate file from `mod`.
|
|
881
|
-
target.extend(plugin::ClassMethods) # rubocop:disable PrisonGuard/NoDynamicConstAccess
|
|
882
|
-
end
|
|
883
|
-
end
|
|
884
|
-
|
|
885
|
-
def self.apply_decorator_methods(plugin, target)
|
|
886
|
-
if plugin.const_defined?('DecoratorMethods')
|
|
887
|
-
# FIXME: This will break preloading, selective test execution, etc if `mod::DecoratorMethods`
|
|
888
|
-
# is ever defined in a separate file from `mod`.
|
|
889
|
-
target.extend(plugin::DecoratorMethods) # rubocop:disable PrisonGuard/NoDynamicConstAccess
|
|
890
|
-
end
|
|
891
|
-
end
|
|
822
|
+
T::Props::Plugin::Private.apply_class_methods(mod, decorated_class)
|
|
823
|
+
T::Props::Plugin::Private.apply_decorator_methods(mod, self)
|
|
892
824
|
end
|
|
893
825
|
end
|
data/lib/types/props/plugin.rb
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
# typed:
|
|
2
|
+
# typed: false
|
|
3
3
|
|
|
4
4
|
module T::Props::Plugin
|
|
5
5
|
include T::Props
|
|
@@ -12,4 +12,26 @@ module T::Props::Plugin
|
|
|
12
12
|
end
|
|
13
13
|
end
|
|
14
14
|
mixes_in_class_methods(ClassMethods)
|
|
15
|
+
|
|
16
|
+
module Private
|
|
17
|
+
# These need to be non-instance methods so we can use them without prematurely creating the
|
|
18
|
+
# child decorator in `model_inherited` (see comments there for details).
|
|
19
|
+
#
|
|
20
|
+
# The dynamic constant access below forces this file to be `typed: false`
|
|
21
|
+
def self.apply_class_methods(plugin, target)
|
|
22
|
+
if plugin.const_defined?('ClassMethods')
|
|
23
|
+
# FIXME: This will break preloading, selective test execution, etc if `mod::ClassMethods`
|
|
24
|
+
# is ever defined in a separate file from `mod`.
|
|
25
|
+
target.extend(plugin::ClassMethods) # rubocop:disable PrisonGuard/NoDynamicConstAccess
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.apply_decorator_methods(plugin, target)
|
|
30
|
+
if plugin.const_defined?('DecoratorMethods')
|
|
31
|
+
# FIXME: This will break preloading, selective test execution, etc if `mod::DecoratorMethods`
|
|
32
|
+
# is ever defined in a separate file from `mod`.
|
|
33
|
+
target.extend(plugin::DecoratorMethods) # rubocop:disable PrisonGuard/NoDynamicConstAccess
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
15
37
|
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# typed: strict
|
|
3
|
+
|
|
4
|
+
module T::Props
|
|
5
|
+
module Private
|
|
6
|
+
module SetterFactory
|
|
7
|
+
extend T::Sig
|
|
8
|
+
|
|
9
|
+
SetterProc = T.type_alias {T.proc.params(val: T.untyped).void}
|
|
10
|
+
|
|
11
|
+
sig do
|
|
12
|
+
params(
|
|
13
|
+
klass: T.all(Module, T::Props::ClassMethods),
|
|
14
|
+
prop: Symbol,
|
|
15
|
+
rules: T::Hash[Symbol, T.untyped]
|
|
16
|
+
)
|
|
17
|
+
.returns(SetterProc)
|
|
18
|
+
.checked(:never)
|
|
19
|
+
end
|
|
20
|
+
def self.build_setter_proc(klass, prop, rules)
|
|
21
|
+
# Our nil check works differently than a simple T.nilable for various
|
|
22
|
+
# reasons (including the `raise_on_nil_write` setting, the existence
|
|
23
|
+
# of defaults & factories, and the fact that we allow `T.nilable(Foo)`
|
|
24
|
+
# where Foo < T::Props::CustomType as a prop type even though calling
|
|
25
|
+
# `valid?` on it won't work as expected), so unwrap any T.nilable and
|
|
26
|
+
# do a check manually. (Note this hack does not fix custom types as
|
|
27
|
+
# collection elements.)
|
|
28
|
+
non_nil_type = if rules[:type_is_custom_type]
|
|
29
|
+
rules.fetch(:type)
|
|
30
|
+
else
|
|
31
|
+
T::Utils::Nilable.get_underlying_type_object(rules.fetch(:type_object))
|
|
32
|
+
end
|
|
33
|
+
accessor_key = rules.fetch(:accessor_key)
|
|
34
|
+
raise_error = ->(val) {raise_pretty_error(klass, prop, non_nil_type, val)}
|
|
35
|
+
|
|
36
|
+
# It seems like a bug that this affects the behavior of setters, but
|
|
37
|
+
# some existing code relies on this behavior
|
|
38
|
+
has_explicit_nil_default = rules.key?(:default) && rules.fetch(:default).nil?
|
|
39
|
+
|
|
40
|
+
if !T::Props::Utils.need_nil_write_check?(rules) || has_explicit_nil_default
|
|
41
|
+
proc do |val|
|
|
42
|
+
if val.nil?
|
|
43
|
+
instance_variable_set(accessor_key, nil)
|
|
44
|
+
elsif non_nil_type.valid?(val)
|
|
45
|
+
instance_variable_set(accessor_key, val)
|
|
46
|
+
else
|
|
47
|
+
raise_error.call(val)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
else
|
|
51
|
+
proc do |val|
|
|
52
|
+
if non_nil_type.valid?(val)
|
|
53
|
+
instance_variable_set(accessor_key, val)
|
|
54
|
+
else
|
|
55
|
+
raise_error.call(val)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
sig do
|
|
62
|
+
params(
|
|
63
|
+
klass: T.all(Module, T::Props::ClassMethods),
|
|
64
|
+
prop: Symbol,
|
|
65
|
+
type: T.any(T::Types::Base, Module),
|
|
66
|
+
val: T.untyped,
|
|
67
|
+
)
|
|
68
|
+
.void
|
|
69
|
+
end
|
|
70
|
+
private_class_method def self.raise_pretty_error(klass, prop, type, val)
|
|
71
|
+
base_message = "Can't set #{klass.name}.#{prop} to #{val.inspect} (instance of #{val.class}) - need a #{type}"
|
|
72
|
+
|
|
73
|
+
pretty_message = "Parameter '#{prop}': #{base_message}\n"
|
|
74
|
+
caller_loc = caller_locations&.find {|l| !l.to_s.include?('sorbet-runtime/lib/types/props')}
|
|
75
|
+
if caller_loc
|
|
76
|
+
pretty_message += "Caller: #{caller_loc.path}:#{caller_loc.lineno}\n"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
T::Configuration.call_validation_error_handler(
|
|
80
|
+
nil,
|
|
81
|
+
message: base_message,
|
|
82
|
+
pretty_message: pretty_message,
|
|
83
|
+
kind: 'Parameter',
|
|
84
|
+
name: prop,
|
|
85
|
+
type: type,
|
|
86
|
+
value: val,
|
|
87
|
+
location: caller_loc,
|
|
88
|
+
)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sorbet-runtime
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.5.
|
|
4
|
+
version: 0.5.5284
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Stripe
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2020-01-
|
|
11
|
+
date: 2020-01-25 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: minitest
|
|
@@ -112,6 +112,7 @@ files:
|
|
|
112
112
|
- lib/types/props/optional.rb
|
|
113
113
|
- lib/types/props/plugin.rb
|
|
114
114
|
- lib/types/props/pretty_printable.rb
|
|
115
|
+
- lib/types/props/private/setter_factory.rb
|
|
115
116
|
- lib/types/props/serializable.rb
|
|
116
117
|
- lib/types/props/type_validation.rb
|
|
117
118
|
- lib/types/props/utils.rb
|