sorbet-runtime 0.6.13299 → 0.6.13300
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/types/props/constructor.rb +2 -2
- data/lib/types/props/custom_type.rb +7 -1
- data/lib/types/props/decorator.rb +42 -25
- data/lib/types/props/generated_code_validation.rb +10 -4
- data/lib/types/props/has_lazily_specialized_methods.rb +36 -4
- data/lib/types/props/optional.rb +2 -2
- data/lib/types/props/private/apply_default.rb +21 -21
- data/lib/types/props/private/deserializer_generator.rb +7 -1
- data/lib/types/props/private/serde_transform.rb +4 -4
- data/lib/types/props/private/setter_factory.rb +68 -5
- data/lib/types/props/utils.rb +38 -10
- data/lib/types/props/weak_constructor.rb +9 -4
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 24e2e39ecb07eff0a9d4e9efeeebe17935b16363cf0e27b070e9ac9b5e90eccf
|
|
4
|
+
data.tar.gz: 7e4c6aca4672cfe3aa3a33779e640a343c981330fbda03a2d1471b7ac619ae20
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 86e102983a1b56070cc100e77a809cf77ffdc4cc0c3ea3d4986020e9ecf85763af195cd24517157ddf2d2d4ed25c32ba9d92a19ecb1857c61b2c9503e737ac98
|
|
7
|
+
data.tar.gz: 63d055b3273fff38053757d9e439a8e82f945d1edc0d883b2a82a1f442feb13af73a199eb56aa5216aa67e2f9c749431c29a50b5d27d24e483f580d4beca7fb5
|
|
@@ -20,10 +20,10 @@ module T::Props::Constructor::DecoratorMethods
|
|
|
20
20
|
# Use `each_pair` rather than `count` because, as of Ruby 2.6, the latter delegates to Enumerator
|
|
21
21
|
# and therefore allocates for each entry.
|
|
22
22
|
result = 0
|
|
23
|
-
props_without_defaults&.each_pair do |p,
|
|
23
|
+
props_without_defaults&.each_pair do |p, bound_setter|
|
|
24
24
|
begin
|
|
25
25
|
val = hash[p]
|
|
26
|
-
|
|
26
|
+
bound_setter.call(instance, val)
|
|
27
27
|
if val || hash.key?(p)
|
|
28
28
|
result += 1
|
|
29
29
|
end
|
|
@@ -62,9 +62,15 @@ module T::Props
|
|
|
62
62
|
# We don't need to check for val's included modules in
|
|
63
63
|
# T::Configuration.scalar_types, because T::Configuration.scalar_types
|
|
64
64
|
# are all classes.
|
|
65
|
+
#
|
|
66
|
+
# `name` rather than `to_s`: identical for real classes, but returns the
|
|
67
|
+
# cached frozen string (Ruby 3.2+) where to_s allocates per call.
|
|
68
|
+
# Anonymous classes yield nil, and `include?(nil)` is false (an
|
|
69
|
+
# anonymous class can never be a registered scalar type).
|
|
70
|
+
scalar_types = T::Configuration.scalar_types
|
|
65
71
|
klass = val.class
|
|
66
72
|
until klass.nil?
|
|
67
|
-
return true if
|
|
73
|
+
return true if scalar_types.include?(klass.name)
|
|
68
74
|
klass = klass.superclass
|
|
69
75
|
end
|
|
70
76
|
false
|
|
@@ -71,7 +71,12 @@ class T::Props::Decorator
|
|
|
71
71
|
raise ArgumentError.new("Attempted to redefine prop #{name.inspect} on class #{@class} that's already defined without specifying :override => true: #{prop_rules(name)}")
|
|
72
72
|
end
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
# dup/store/freeze rather than @props.merge(name => rules.freeze).freeze:
|
|
75
|
+
# merge would allocate a temporary single-entry Hash and re-insert every
|
|
76
|
+
# existing entry. The published hash stays frozen between adds.
|
|
77
|
+
new_props = @props.dup
|
|
78
|
+
new_props[name] = rules.freeze
|
|
79
|
+
@props = new_props.freeze
|
|
75
80
|
end
|
|
76
81
|
|
|
77
82
|
# Heads up!
|
|
@@ -126,7 +131,7 @@ class T::Props::Decorator
|
|
|
126
131
|
# preexisting behavior).
|
|
127
132
|
#
|
|
128
133
|
# Note this path is NOT used by generated setters on instances,
|
|
129
|
-
# which are defined using
|
|
134
|
+
# which are defined using the 1-arity `:setter_proc` directly.
|
|
130
135
|
#
|
|
131
136
|
# checked(:never) - O(prop accesses)
|
|
132
137
|
sig do
|
|
@@ -140,7 +145,9 @@ class T::Props::Decorator
|
|
|
140
145
|
.checked(:never)
|
|
141
146
|
end
|
|
142
147
|
def prop_set(instance, prop, val, rules=prop_rules(prop))
|
|
143
|
-
|
|
148
|
+
# The 2-arity bound proc does the same validation/assignment as the
|
|
149
|
+
# 1-arity setter proc without instance_exec's per-call self-rebinding.
|
|
150
|
+
rules.fetch(:_bound_setter_proc).call(instance, val)
|
|
144
151
|
end
|
|
145
152
|
alias_method :set, :prop_set
|
|
146
153
|
|
|
@@ -182,7 +189,7 @@ class T::Props::Decorator
|
|
|
182
189
|
if !val.nil?
|
|
183
190
|
val
|
|
184
191
|
elsif (d = rules[:ifunset])
|
|
185
|
-
T::Props::Utils.
|
|
192
|
+
T::Props::Utils.deep_clone(d)
|
|
186
193
|
else
|
|
187
194
|
nil
|
|
188
195
|
end
|
|
@@ -421,11 +428,20 @@ class T::Props::Decorator
|
|
|
421
428
|
end
|
|
422
429
|
end
|
|
423
430
|
|
|
424
|
-
setter_proc, value_validate_proc = T::Props::Private::SetterFactory.build_setter_proc(@class, name, rules)
|
|
431
|
+
setter_proc, value_validate_proc, bound_setter_proc = T::Props::Private::SetterFactory.build_setter_proc(@class, name, rules)
|
|
425
432
|
setter_proc.freeze
|
|
426
433
|
value_validate_proc.freeze
|
|
427
|
-
|
|
434
|
+
bound_setter_proc.freeze
|
|
428
435
|
rules[:value_validate_proc] = value_validate_proc
|
|
436
|
+
# :setter_proc is a pre-existing rules key and is preserved as-is: the
|
|
437
|
+
# 1-arity, self-bound proc used only to define the generated `name=`
|
|
438
|
+
# instance setter via `define_method(&proc)` (its fast path). The net-new
|
|
439
|
+
# :_bound_setter_proc is the 2-arity (instance, val) equivalent that every
|
|
440
|
+
# other write goes through without instance_exec's per-call self-rebinding;
|
|
441
|
+
# it is underscore-prefixed (like :_tnilable) so external code that replays
|
|
442
|
+
# rules hashes back into prop() filters it out as internal.
|
|
443
|
+
rules[:setter_proc] = setter_proc
|
|
444
|
+
rules[:_bound_setter_proc] = bound_setter_proc
|
|
429
445
|
|
|
430
446
|
validate_overrides(name, rules)
|
|
431
447
|
add_prop_definition(name, rules)
|
|
@@ -644,12 +660,25 @@ class T::Props::Decorator
|
|
|
644
660
|
T::Props::Plugin::Private.apply_class_methods(mod, child)
|
|
645
661
|
end
|
|
646
662
|
|
|
647
|
-
|
|
663
|
+
parent_props = props
|
|
664
|
+
# Return before child.decorator below: forcing decorator creation for a
|
|
665
|
+
# prop-less parent would defeat any decorator_class override (see the NB).
|
|
666
|
+
return if parent_props.empty?
|
|
667
|
+
|
|
668
|
+
# NB: Calling `child.decorator` here is a time bomb that's going to give someone a really bad
|
|
669
|
+
# time. Any class that defines props and also overrides the `decorator_class` method is going
|
|
670
|
+
# to reach this line before its override take effect, turning it into a no-op.
|
|
671
|
+
child_decorator = child.decorator
|
|
672
|
+
|
|
673
|
+
# Computed once for all props: the owner comparison cannot change inside
|
|
674
|
+
# the loop below (it only defines prop accessors on child), and each
|
|
675
|
+
# Object#method call allocates a fresh Method.
|
|
676
|
+
clobber_getters = child_decorator.method(:prop_get).owner != method(:prop_get).owner
|
|
677
|
+
clobber_setters = child_decorator.method(:prop_set).owner != method(:prop_set).owner
|
|
678
|
+
|
|
679
|
+
parent_props.each do |name, rules|
|
|
648
680
|
copied_rules = rules.dup
|
|
649
|
-
|
|
650
|
-
# time. Any class that defines props and also overrides the `decorator_class` method is going
|
|
651
|
-
# to reach this line before its override take effect, turning it into a no-op.
|
|
652
|
-
child.decorator.add_prop_definition(name, copied_rules)
|
|
681
|
+
child_decorator.add_prop_definition(name, copied_rules)
|
|
653
682
|
|
|
654
683
|
# It's a bit tricky to support `prop_get` hooks added by plugins without
|
|
655
684
|
# sacrificing the `attr_reader` fast path or clobbering customized getters
|
|
@@ -660,13 +689,13 @@ class T::Props::Decorator
|
|
|
660
689
|
# (b) it's safe because the getter was defined by this file.
|
|
661
690
|
#
|
|
662
691
|
unless rules[:without_accessors]
|
|
663
|
-
if
|
|
692
|
+
if clobber_getters && child.instance_method(name).source_location&.first == __FILE__
|
|
664
693
|
child.send(:define_method, name) do
|
|
665
694
|
T.unsafe(self.class).decorator.prop_get(self, name, rules)
|
|
666
695
|
end
|
|
667
696
|
end
|
|
668
697
|
|
|
669
|
-
if !rules[:immutable] &&
|
|
698
|
+
if !rules[:immutable] && clobber_setters && child.instance_method("#{name}=").source_location&.first == __FILE__
|
|
670
699
|
child.send(:define_method, "#{name}=") do |val|
|
|
671
700
|
T.unsafe(self.class).decorator.prop_set(self, name, val, rules)
|
|
672
701
|
end
|
|
@@ -721,18 +750,6 @@ class T::Props::Decorator
|
|
|
721
750
|
result
|
|
722
751
|
end
|
|
723
752
|
|
|
724
|
-
sig { params(child: DecoratedClassType, prop: Symbol).returns(T::Boolean).checked(:never) }
|
|
725
|
-
private def clobber_getter?(child, prop)
|
|
726
|
-
!!(child.decorator.method(:prop_get).owner != method(:prop_get).owner &&
|
|
727
|
-
child.instance_method(prop).source_location&.first == __FILE__)
|
|
728
|
-
end
|
|
729
|
-
|
|
730
|
-
sig { params(child: DecoratedClassType, prop: Symbol).returns(T::Boolean).checked(:never) }
|
|
731
|
-
private def clobber_setter?(child, prop)
|
|
732
|
-
!!(child.decorator.method(:prop_set).owner != method(:prop_set).owner &&
|
|
733
|
-
child.instance_method("#{prop}=").source_location&.first == __FILE__)
|
|
734
|
-
end
|
|
735
|
-
|
|
736
753
|
sig { params(mod: T::Module[T.anything]).void.checked(:never) }
|
|
737
754
|
def plugin(mod)
|
|
738
755
|
decorated_class.plugins << mod
|
|
@@ -119,7 +119,7 @@ module T::Props
|
|
|
119
119
|
|
|
120
120
|
private_class_method def self.validate_deserialize_ivar_set(clause)
|
|
121
121
|
# %<accessor_key>s = if val.nil?
|
|
122
|
-
# found -= 1 unless hash.key?(%<serialized_form>s)
|
|
122
|
+
# found -= 1 unless hash.key?(%<serialized_form>s.freeze)
|
|
123
123
|
# %<nil_handler>s
|
|
124
124
|
# else
|
|
125
125
|
# %<serialized_val>s
|
|
@@ -143,7 +143,13 @@ module T::Props
|
|
|
143
143
|
receiver, method, arg = found_condition.children
|
|
144
144
|
assert_equal(s(:lvar, :hash), receiver)
|
|
145
145
|
assert_equal(:key?, method)
|
|
146
|
-
|
|
146
|
+
# hash.key?(%<serialized_form>s.freeze), where `.freeze` on a string
|
|
147
|
+
# literal is side-effect-free (and avoids a per-call allocation)
|
|
148
|
+
assert_equal(:send, arg.type)
|
|
149
|
+
arg_receiver, arg_method, *arg_rest = arg.children
|
|
150
|
+
assert_equal(:str, arg_receiver.type)
|
|
151
|
+
assert_equal(:freeze, arg_method)
|
|
152
|
+
assert_equal([], arg_rest)
|
|
147
153
|
assert_equal(nil, found_if_body)
|
|
148
154
|
assert_equal(s(:op_asgn, s(:lvasgn, :found), :-, s(:int, 1)), found_else_body)
|
|
149
155
|
|
|
@@ -262,7 +268,7 @@ module T::Props
|
|
|
262
268
|
@whitelisted_methods_for_serialize ||= {
|
|
263
269
|
lvar: %i{dup map transform_values transform_keys each_with_object nil? []= serialize},
|
|
264
270
|
ivar: %i[dup map transform_values transform_keys each_with_object serialize],
|
|
265
|
-
const: %i[checked_serialize deep_clone_object],
|
|
271
|
+
const: %i[checked_serialize deep_clone deep_clone_object],
|
|
266
272
|
}
|
|
267
273
|
end
|
|
268
274
|
|
|
@@ -270,7 +276,7 @@ module T::Props
|
|
|
270
276
|
private_class_method def self.whitelisted_methods_for_deserialize
|
|
271
277
|
@whitelisted_methods_for_deserialize ||= {
|
|
272
278
|
lvar: %i{dup map transform_values transform_keys each_with_object nil? []= to_f},
|
|
273
|
-
const: %i[deserialize from_hash deep_clone_object],
|
|
279
|
+
const: %i[deserialize from_hash deep_clone deep_clone_object],
|
|
274
280
|
}
|
|
275
281
|
end
|
|
276
282
|
end
|
|
@@ -59,13 +59,23 @@ module T::Props
|
|
|
59
59
|
raise SourceEvaluationDisabled.new
|
|
60
60
|
end
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
blk = lazily_defined_methods[name]
|
|
63
|
+
# A concurrent first call can have already evaluated and removed
|
|
64
|
+
# this entry; the specialized method is installed, so the
|
|
65
|
+
# placeholder's retry dispatch will reach it directly.
|
|
66
|
+
return if blk.nil?
|
|
67
|
+
|
|
68
|
+
source = blk.call
|
|
63
69
|
|
|
64
70
|
cls = decorated_class
|
|
65
71
|
T::Configuration.without_ruby_warnings do
|
|
66
72
|
cls.class_eval(source.to_s)
|
|
67
73
|
end
|
|
68
74
|
cls.send(:private, name)
|
|
75
|
+
# Removing the entry records that no placeholder is installed, so a
|
|
76
|
+
# later prop addition (possible, if unusual: props added after first
|
|
77
|
+
# use) re-enqueues a fresh placeholder instead of being skipped.
|
|
78
|
+
lazily_defined_methods.delete(name)
|
|
69
79
|
end
|
|
70
80
|
|
|
71
81
|
sig { params(name: Symbol).void }
|
|
@@ -74,15 +84,31 @@ module T::Props
|
|
|
74
84
|
raise SourceEvaluationDisabled.new
|
|
75
85
|
end
|
|
76
86
|
|
|
77
|
-
lazily_defined_vm_methods
|
|
87
|
+
blk = lazily_defined_vm_methods[name]
|
|
88
|
+
# See eval_lazily_defined_method!.
|
|
89
|
+
return if blk.nil?
|
|
90
|
+
|
|
91
|
+
blk.call
|
|
78
92
|
|
|
79
93
|
cls = decorated_class
|
|
80
94
|
cls.send(:private, name)
|
|
95
|
+
lazily_defined_vm_methods.delete(name)
|
|
81
96
|
end
|
|
82
97
|
|
|
83
98
|
sig { params(name: Symbol, blk: T.proc.returns(String)).void }
|
|
84
99
|
private def enqueue_lazy_method_definition!(name, &blk)
|
|
85
|
-
|
|
100
|
+
methods = lazily_defined_methods
|
|
101
|
+
if methods.key?(name)
|
|
102
|
+
# The placeholder installed below is already in place (every prop
|
|
103
|
+
# addition lands here, so this is hit from the second prop of a
|
|
104
|
+
# class onward, and again for every prop re-added to a subclass).
|
|
105
|
+
# It reads the generator from the hash at call time, so updating
|
|
106
|
+
# the entry suffices; skipping the re-install avoids 2-4 method
|
|
107
|
+
# table writes (and their cache invalidations) per addition.
|
|
108
|
+
methods[name] = blk
|
|
109
|
+
return
|
|
110
|
+
end
|
|
111
|
+
methods[name] = blk
|
|
86
112
|
|
|
87
113
|
cls = decorated_class
|
|
88
114
|
if cls.method_defined?(name) || cls.private_method_defined?(name)
|
|
@@ -102,7 +128,13 @@ module T::Props
|
|
|
102
128
|
|
|
103
129
|
sig { params(name: Symbol, blk: T.untyped).void }
|
|
104
130
|
private def enqueue_lazy_vm_method_definition!(name, &blk)
|
|
105
|
-
|
|
131
|
+
methods = lazily_defined_vm_methods
|
|
132
|
+
if methods.key?(name)
|
|
133
|
+
# See enqueue_lazy_method_definition!.
|
|
134
|
+
methods[name] = blk
|
|
135
|
+
return
|
|
136
|
+
end
|
|
137
|
+
methods[name] = blk
|
|
106
138
|
|
|
107
139
|
cls = decorated_class
|
|
108
140
|
cls.send(:define_method, name) do |*args|
|
data/lib/types/props/optional.rb
CHANGED
|
@@ -47,7 +47,7 @@ module T::Props::Optional::DecoratorMethods
|
|
|
47
47
|
attr_reader :props_with_defaults
|
|
48
48
|
|
|
49
49
|
# checked(:never) - O(runtime object construction)
|
|
50
|
-
sig { returns(T::Hash[Symbol, T::Props::Private::SetterFactory::
|
|
50
|
+
sig { returns(T::Hash[Symbol, T::Props::Private::SetterFactory::BoundSetterProc]).checked(:never) }
|
|
51
51
|
attr_reader :props_without_defaults
|
|
52
52
|
|
|
53
53
|
def add_prop_definition(prop, rules)
|
|
@@ -62,7 +62,7 @@ module T::Props::Optional::DecoratorMethods
|
|
|
62
62
|
rules[DEFAULT_SETTER_RULE_KEY] = default_setter
|
|
63
63
|
else
|
|
64
64
|
@props_without_defaults ||= {}
|
|
65
|
-
@props_without_defaults[prop] = rules.fetch(:
|
|
65
|
+
@props_without_defaults[prop] = rules.fetch(:_bound_setter_proc)
|
|
66
66
|
props_with_defaults&.delete(prop) # Handle potential override
|
|
67
67
|
end
|
|
68
68
|
|
|
@@ -9,14 +9,14 @@ module T::Props
|
|
|
9
9
|
abstract!
|
|
10
10
|
|
|
11
11
|
# checked(:never) - O(object construction x prop count)
|
|
12
|
-
sig { returns(SetterFactory::
|
|
13
|
-
attr_reader :
|
|
12
|
+
sig { returns(SetterFactory::BoundSetterProc).checked(:never) }
|
|
13
|
+
attr_reader :bound_setter_proc
|
|
14
14
|
|
|
15
15
|
# checked(:never) - We do this with `T.let` instead
|
|
16
|
-
sig { params(accessor_key: Symbol,
|
|
17
|
-
def initialize(accessor_key,
|
|
16
|
+
sig { params(accessor_key: Symbol, bound_setter_proc: SetterFactory::BoundSetterProc).void.checked(:never) }
|
|
17
|
+
def initialize(accessor_key, bound_setter_proc)
|
|
18
18
|
@accessor_key = T.let(accessor_key, Symbol)
|
|
19
|
-
@
|
|
19
|
+
@bound_setter_proc = T.let(bound_setter_proc, SetterFactory::BoundSetterProc)
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
# checked(:never) - O(object construction x prop count)
|
|
@@ -33,30 +33,30 @@ module T::Props
|
|
|
33
33
|
sig { params(cls: T::Module[T.anything], rules: T::Hash[Symbol, T.untyped]).returns(T.nilable(ApplyDefault)).checked(:never) }
|
|
34
34
|
def self.for(cls, rules)
|
|
35
35
|
accessor_key = rules.fetch(:accessor_key)
|
|
36
|
-
|
|
36
|
+
bound_setter = rules.fetch(:_bound_setter_proc)
|
|
37
37
|
|
|
38
38
|
if rules.key?(:factory)
|
|
39
|
-
ApplyDefaultFactory.new(cls, rules.fetch(:factory), accessor_key,
|
|
39
|
+
ApplyDefaultFactory.new(cls, rules.fetch(:factory), accessor_key, bound_setter)
|
|
40
40
|
elsif rules.key?(:default)
|
|
41
41
|
default = rules.fetch(:default)
|
|
42
42
|
case default
|
|
43
43
|
when *NO_CLONE_TYPES
|
|
44
|
-
return ApplyPrimitiveDefault.new(default, accessor_key,
|
|
44
|
+
return ApplyPrimitiveDefault.new(default, accessor_key, bound_setter)
|
|
45
45
|
when String
|
|
46
46
|
if default.frozen?
|
|
47
|
-
return ApplyPrimitiveDefault.new(default, accessor_key,
|
|
47
|
+
return ApplyPrimitiveDefault.new(default, accessor_key, bound_setter)
|
|
48
48
|
end
|
|
49
49
|
when Array
|
|
50
50
|
if default.empty? && default.class == Array
|
|
51
|
-
return ApplyEmptyArrayDefault.new(accessor_key,
|
|
51
|
+
return ApplyEmptyArrayDefault.new(accessor_key, bound_setter)
|
|
52
52
|
end
|
|
53
53
|
when Hash
|
|
54
54
|
if default.empty? && default.default.nil? && T.unsafe(default).default_proc.nil? && default.class == Hash
|
|
55
|
-
return ApplyEmptyHashDefault.new(accessor_key,
|
|
55
|
+
return ApplyEmptyHashDefault.new(accessor_key, bound_setter)
|
|
56
56
|
end
|
|
57
57
|
end
|
|
58
58
|
|
|
59
|
-
ApplyComplexDefault.new(default, accessor_key,
|
|
59
|
+
ApplyComplexDefault.new(default, accessor_key, bound_setter)
|
|
60
60
|
else
|
|
61
61
|
nil
|
|
62
62
|
end
|
|
@@ -67,16 +67,16 @@ module T::Props
|
|
|
67
67
|
abstract!
|
|
68
68
|
|
|
69
69
|
# checked(:never) - We do this with `T.let` instead
|
|
70
|
-
sig { params(default: BasicObject, accessor_key: Symbol,
|
|
71
|
-
def initialize(default, accessor_key,
|
|
70
|
+
sig { params(default: BasicObject, accessor_key: Symbol, bound_setter_proc: SetterFactory::BoundSetterProc).void.checked(:never) }
|
|
71
|
+
def initialize(default, accessor_key, bound_setter_proc)
|
|
72
72
|
# FIXME: Ideally we'd check here that the default is actually a valid
|
|
73
73
|
# value for this field, but existing code relies on the fact that we don't.
|
|
74
74
|
#
|
|
75
75
|
# :(
|
|
76
76
|
#
|
|
77
|
-
#
|
|
77
|
+
# bound_setter_proc.call(instance, default)
|
|
78
78
|
@default = T.let(default, BasicObject)
|
|
79
|
-
super(accessor_key,
|
|
79
|
+
super(accessor_key, bound_setter_proc)
|
|
80
80
|
end
|
|
81
81
|
|
|
82
82
|
# checked(:never) - O(object construction x prop count)
|
|
@@ -96,7 +96,7 @@ module T::Props
|
|
|
96
96
|
# checked(:never) - O(object construction x prop count)
|
|
97
97
|
sig { override.returns(T.untyped).checked(:never) }
|
|
98
98
|
def default
|
|
99
|
-
T::Props::Utils.
|
|
99
|
+
T::Props::Utils.deep_clone(@default)
|
|
100
100
|
end
|
|
101
101
|
end
|
|
102
102
|
|
|
@@ -141,15 +141,15 @@ module T::Props
|
|
|
141
141
|
cls: T::Module[T.anything],
|
|
142
142
|
factory: T.any(Proc, Method),
|
|
143
143
|
accessor_key: Symbol,
|
|
144
|
-
|
|
144
|
+
bound_setter_proc: SetterFactory::BoundSetterProc,
|
|
145
145
|
)
|
|
146
146
|
.void
|
|
147
147
|
.checked(:never)
|
|
148
148
|
end
|
|
149
|
-
def initialize(cls, factory, accessor_key,
|
|
149
|
+
def initialize(cls, factory, accessor_key, bound_setter_proc)
|
|
150
150
|
@class = T.let(cls, T::Module[T.anything])
|
|
151
151
|
@factory = T.let(factory, T.any(Proc, Method))
|
|
152
|
-
super(accessor_key,
|
|
152
|
+
super(accessor_key, bound_setter_proc)
|
|
153
153
|
end
|
|
154
154
|
|
|
155
155
|
# checked(:never) - O(object construction x prop count)
|
|
@@ -157,7 +157,7 @@ module T::Props
|
|
|
157
157
|
def set_default(instance)
|
|
158
158
|
# Use the actual setter to validate the factory returns a legitimate
|
|
159
159
|
# value every time
|
|
160
|
-
|
|
160
|
+
@bound_setter_proc.call(instance, default)
|
|
161
161
|
end
|
|
162
162
|
|
|
163
163
|
# checked(:never) - O(object construction x prop count)
|
|
@@ -81,10 +81,16 @@ module T::Props
|
|
|
81
81
|
raise_on_nil_write: !!rules[:raise_on_nil_write],
|
|
82
82
|
)
|
|
83
83
|
|
|
84
|
+
# The `.freeze` on the `hash.key?` argument matters: when this source
|
|
85
|
+
# is eval'd lazily (without a frozen-string-literal prefix, unlike
|
|
86
|
+
# `eagerly_define_lazy_methods!`), a bare literal here would allocate
|
|
87
|
+
# a new String on every call for each nil/missing prop. `"str".freeze`
|
|
88
|
+
# compiles to a no-allocation VM instruction (opt_str_freeze). The
|
|
89
|
+
# `hash[...]` read doesn't need it because of opt_aref_with.
|
|
84
90
|
<<~RUBY
|
|
85
91
|
val = hash[#{hash_key.inspect}]
|
|
86
92
|
#{ivar_name} = if val.nil?
|
|
87
|
-
found -= 1 unless hash.key?(#{hash_key.inspect})
|
|
93
|
+
found -= 1 unless hash.key?(#{hash_key.inspect}.freeze)
|
|
88
94
|
#{nil_handler}
|
|
89
95
|
else
|
|
90
96
|
#{transformed_val}
|
|
@@ -88,7 +88,7 @@ module T::Props
|
|
|
88
88
|
# string comparisons.
|
|
89
89
|
nil
|
|
90
90
|
else
|
|
91
|
-
"T::Props::Utils.
|
|
91
|
+
"T::Props::Utils.deep_clone(#{varname})"
|
|
92
92
|
end
|
|
93
93
|
when T::Types::Union
|
|
94
94
|
non_nil_type = T::Utils.unwrap_nilable(type)
|
|
@@ -110,10 +110,10 @@ module T::Props
|
|
|
110
110
|
# this union to have no specific serde transform (the only reason
|
|
111
111
|
# why Float has a special case is because round tripping through
|
|
112
112
|
# JSON might normalize Floats to Integers)
|
|
113
|
-
"T::Props::Utils.
|
|
113
|
+
"T::Props::Utils.deep_clone(#{varname})"
|
|
114
114
|
end
|
|
115
115
|
when T::Types::Intersection
|
|
116
|
-
dynamic_fallback = "T::Props::Utils.
|
|
116
|
+
dynamic_fallback = "T::Props::Utils.deep_clone(#{varname})"
|
|
117
117
|
|
|
118
118
|
# Transformations for any members of the intersection type where we
|
|
119
119
|
# know what we need to do and did not have to fall back to the
|
|
@@ -147,7 +147,7 @@ module T::Props
|
|
|
147
147
|
when T::Types::Enum
|
|
148
148
|
generate(T::Utils.lift_enum(type), mode, varname)
|
|
149
149
|
else
|
|
150
|
-
"T::Props::Utils.
|
|
150
|
+
"T::Props::Utils.deep_clone(#{varname})"
|
|
151
151
|
end
|
|
152
152
|
end
|
|
153
153
|
|
|
@@ -8,6 +8,10 @@ module T::Props
|
|
|
8
8
|
|
|
9
9
|
SetterProc = T.type_alias { T.proc.params(val: T.untyped).void }
|
|
10
10
|
ValueValidationProc = T.type_alias { T.proc.params(val: T.untyped).void }
|
|
11
|
+
# Same validation/assignment as SetterProc, but takes the instance
|
|
12
|
+
# explicitly so per-prop construction/prop_set paths skip the
|
|
13
|
+
# self-rebinding instance_exec dispatch.
|
|
14
|
+
BoundSetterProc = T.type_alias { T.proc.params(instance: T.untyped, val: T.untyped).void }
|
|
11
15
|
ValidateProc = T.type_alias { T.proc.params(prop: Symbol, value: T.untyped).void }
|
|
12
16
|
|
|
13
17
|
sig do
|
|
@@ -16,7 +20,7 @@ module T::Props
|
|
|
16
20
|
prop: Symbol,
|
|
17
21
|
rules: T::Hash[Symbol, T.untyped]
|
|
18
22
|
)
|
|
19
|
-
.returns([SetterProc, ValueValidationProc])
|
|
23
|
+
.returns([SetterProc, ValueValidationProc, BoundSetterProc])
|
|
20
24
|
.checked(:never)
|
|
21
25
|
end
|
|
22
26
|
def self.build_setter_proc(klass, prop, rules)
|
|
@@ -56,7 +60,7 @@ module T::Props
|
|
|
56
60
|
non_nil_type: T::Module[T.anything],
|
|
57
61
|
klass: T.all(T::Module[T.anything], T::Props::ClassMethods),
|
|
58
62
|
)
|
|
59
|
-
.returns([SetterProc, ValueValidationProc])
|
|
63
|
+
.returns([SetterProc, ValueValidationProc, BoundSetterProc])
|
|
60
64
|
end
|
|
61
65
|
private_class_method def self.simple_non_nil_proc(prop, accessor_key, non_nil_type, klass)
|
|
62
66
|
[
|
|
@@ -81,6 +85,19 @@ module T::Props
|
|
|
81
85
|
)
|
|
82
86
|
end
|
|
83
87
|
end,
|
|
88
|
+
# The ivar set is unconditional, exactly as above: the value must
|
|
89
|
+
# still be set when call_validation_error_handler does not raise.
|
|
90
|
+
proc do |instance, val|
|
|
91
|
+
unless val.is_a?(non_nil_type)
|
|
92
|
+
T::Props::Private::SetterFactory.raise_pretty_error(
|
|
93
|
+
klass,
|
|
94
|
+
prop,
|
|
95
|
+
T::Utils.coerce(non_nil_type),
|
|
96
|
+
val,
|
|
97
|
+
)
|
|
98
|
+
end
|
|
99
|
+
instance.instance_variable_set(accessor_key, val)
|
|
100
|
+
end,
|
|
84
101
|
]
|
|
85
102
|
end
|
|
86
103
|
|
|
@@ -92,7 +109,7 @@ module T::Props
|
|
|
92
109
|
klass: T.all(T::Module[T.anything], T::Props::ClassMethods),
|
|
93
110
|
validate: T.nilable(ValidateProc)
|
|
94
111
|
)
|
|
95
|
-
.returns([SetterProc, ValueValidationProc])
|
|
112
|
+
.returns([SetterProc, ValueValidationProc, BoundSetterProc])
|
|
96
113
|
end
|
|
97
114
|
private_class_method def self.non_nil_proc(prop, accessor_key, non_nil_type, klass, validate)
|
|
98
115
|
[
|
|
@@ -131,6 +148,21 @@ module T::Props
|
|
|
131
148
|
)
|
|
132
149
|
end
|
|
133
150
|
end,
|
|
151
|
+
# The ivar set is unconditional, exactly as above: the value must
|
|
152
|
+
# still be set when call_validation_error_handler does not raise.
|
|
153
|
+
proc do |instance, val|
|
|
154
|
+
if non_nil_type.recursively_valid?(val)
|
|
155
|
+
validate&.call(prop, val)
|
|
156
|
+
else
|
|
157
|
+
T::Props::Private::SetterFactory.raise_pretty_error(
|
|
158
|
+
klass,
|
|
159
|
+
prop,
|
|
160
|
+
non_nil_type,
|
|
161
|
+
val,
|
|
162
|
+
)
|
|
163
|
+
end
|
|
164
|
+
instance.instance_variable_set(accessor_key, val)
|
|
165
|
+
end,
|
|
134
166
|
]
|
|
135
167
|
end
|
|
136
168
|
|
|
@@ -141,7 +173,7 @@ module T::Props
|
|
|
141
173
|
non_nil_type: T::Module[T.anything],
|
|
142
174
|
klass: T.all(T::Module[T.anything], T::Props::ClassMethods),
|
|
143
175
|
)
|
|
144
|
-
.returns([SetterProc, ValueValidationProc])
|
|
176
|
+
.returns([SetterProc, ValueValidationProc, BoundSetterProc])
|
|
145
177
|
end
|
|
146
178
|
private_class_method def self.simple_nilable_proc(prop, accessor_key, non_nil_type, klass)
|
|
147
179
|
[
|
|
@@ -166,6 +198,19 @@ module T::Props
|
|
|
166
198
|
)
|
|
167
199
|
end
|
|
168
200
|
end,
|
|
201
|
+
# The ivar set is unconditional, exactly as above: the value must
|
|
202
|
+
# still be set when call_validation_error_handler does not raise.
|
|
203
|
+
proc do |instance, val|
|
|
204
|
+
unless val.nil? || val.is_a?(non_nil_type)
|
|
205
|
+
T::Props::Private::SetterFactory.raise_pretty_error(
|
|
206
|
+
klass,
|
|
207
|
+
prop,
|
|
208
|
+
T::Utils.coerce(non_nil_type),
|
|
209
|
+
val,
|
|
210
|
+
)
|
|
211
|
+
end
|
|
212
|
+
instance.instance_variable_set(accessor_key, val)
|
|
213
|
+
end,
|
|
169
214
|
]
|
|
170
215
|
end
|
|
171
216
|
|
|
@@ -177,7 +222,7 @@ module T::Props
|
|
|
177
222
|
klass: T.all(T::Module[T.anything], T::Props::ClassMethods),
|
|
178
223
|
validate: T.nilable(ValidateProc),
|
|
179
224
|
)
|
|
180
|
-
.returns([SetterProc, ValueValidationProc])
|
|
225
|
+
.returns([SetterProc, ValueValidationProc, BoundSetterProc])
|
|
181
226
|
end
|
|
182
227
|
private_class_method def self.nilable_proc(prop, accessor_key, non_nil_type, klass, validate)
|
|
183
228
|
[
|
|
@@ -220,6 +265,24 @@ module T::Props
|
|
|
220
265
|
)
|
|
221
266
|
end
|
|
222
267
|
end,
|
|
268
|
+
# Branch structure (including the set-after-soft-error in the
|
|
269
|
+
# invalid arm) replicated exactly from the first proc above.
|
|
270
|
+
proc do |instance, val|
|
|
271
|
+
if val.nil?
|
|
272
|
+
instance.instance_variable_set(accessor_key, nil)
|
|
273
|
+
elsif non_nil_type.recursively_valid?(val)
|
|
274
|
+
validate&.call(prop, val)
|
|
275
|
+
instance.instance_variable_set(accessor_key, val)
|
|
276
|
+
else
|
|
277
|
+
T::Props::Private::SetterFactory.raise_pretty_error(
|
|
278
|
+
klass,
|
|
279
|
+
prop,
|
|
280
|
+
non_nil_type,
|
|
281
|
+
val,
|
|
282
|
+
)
|
|
283
|
+
instance.instance_variable_set(accessor_key, val)
|
|
284
|
+
end
|
|
285
|
+
end,
|
|
223
286
|
]
|
|
224
287
|
end
|
|
225
288
|
|
data/lib/types/props/utils.rb
CHANGED
|
@@ -5,20 +5,48 @@ module T::Props::Utils
|
|
|
5
5
|
# Deep copy an object. The object must consist of Ruby primitive
|
|
6
6
|
# types and Hashes and Arrays.
|
|
7
7
|
def self.deep_clone_object(what, freeze: false)
|
|
8
|
+
freeze ? deep_clone_freeze(what) : deep_clone(what)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# deep_clone_object with freeze: false. Kept kwarg-free, with String (the
|
|
12
|
+
# most common serialized scalar) tested first: this is what generated
|
|
13
|
+
# serializers/deserializers emit for dynamic fallbacks, so it runs per
|
|
14
|
+
# element of every untyped container prop.
|
|
15
|
+
def self.deep_clone(what)
|
|
16
|
+
case what
|
|
17
|
+
when String
|
|
18
|
+
what.clone
|
|
19
|
+
when true, false, Symbol, NilClass, Numeric
|
|
20
|
+
what
|
|
21
|
+
when Array
|
|
22
|
+
what.map { |v| deep_clone(v) }
|
|
23
|
+
when Hash
|
|
24
|
+
h = what.class.new
|
|
25
|
+
what.each_pair do |k, v|
|
|
26
|
+
h[k] = deep_clone(v)
|
|
27
|
+
end
|
|
28
|
+
h
|
|
29
|
+
when Regexp
|
|
30
|
+
what.dup
|
|
31
|
+
when T::Enum
|
|
32
|
+
what
|
|
33
|
+
else
|
|
34
|
+
what.clone
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# deep_clone_object with freeze: true.
|
|
39
|
+
def self.deep_clone_freeze(what)
|
|
8
40
|
result = case what
|
|
9
|
-
when true
|
|
10
|
-
true
|
|
11
|
-
when false
|
|
12
|
-
false
|
|
13
|
-
when Symbol, NilClass, Numeric
|
|
41
|
+
when true, false, Symbol, NilClass, Numeric
|
|
14
42
|
what
|
|
15
43
|
when Array
|
|
16
|
-
what.map { |v|
|
|
44
|
+
what.map { |v| deep_clone_freeze(v) }
|
|
17
45
|
when Hash
|
|
18
46
|
h = what.class.new
|
|
19
|
-
what.
|
|
20
|
-
k.freeze
|
|
21
|
-
h[k] =
|
|
47
|
+
what.each_pair do |k, v|
|
|
48
|
+
k.freeze
|
|
49
|
+
h[k] = deep_clone_freeze(v)
|
|
22
50
|
end
|
|
23
51
|
h
|
|
24
52
|
when Regexp
|
|
@@ -28,7 +56,7 @@ module T::Props::Utils
|
|
|
28
56
|
else
|
|
29
57
|
what.clone
|
|
30
58
|
end
|
|
31
|
-
|
|
59
|
+
result.freeze
|
|
32
60
|
end
|
|
33
61
|
|
|
34
62
|
# The prop_rules indicate whether we should check for reading a nil value for the prop/field.
|
|
@@ -5,9 +5,14 @@ module T::Props::WeakConstructor
|
|
|
5
5
|
include T::Props::Optional
|
|
6
6
|
extend T::Sig
|
|
7
7
|
|
|
8
|
+
# Shared default so zero-arg construction doesn't allocate a fresh Hash;
|
|
9
|
+
# the construct_props_* methods only ever read from `hash`.
|
|
10
|
+
EMPTY_HASH = T.let({}.freeze, T::Hash[Symbol, T.untyped])
|
|
11
|
+
private_constant :EMPTY_HASH
|
|
12
|
+
|
|
8
13
|
# checked(:never) - O(runtime object construction)
|
|
9
14
|
sig { params(hash: T::Hash[Symbol, T.untyped]).void.checked(:never) }
|
|
10
|
-
def initialize(hash=
|
|
15
|
+
def initialize(hash=EMPTY_HASH)
|
|
11
16
|
decorator = self.class.decorator
|
|
12
17
|
|
|
13
18
|
hash_keys_matching_props = decorator.construct_props_with_defaults(self, hash) +
|
|
@@ -33,9 +38,9 @@ module T::Props::WeakConstructor::DecoratorMethods
|
|
|
33
38
|
# Use `each_pair` rather than `count` because, as of Ruby 2.6, the latter delegates to Enumerator
|
|
34
39
|
# and therefore allocates for each entry.
|
|
35
40
|
result = 0
|
|
36
|
-
props_without_defaults&.each_pair do |p,
|
|
41
|
+
props_without_defaults&.each_pair do |p, bound_setter|
|
|
37
42
|
if hash.key?(p)
|
|
38
|
-
|
|
43
|
+
bound_setter.call(instance, hash[p])
|
|
39
44
|
result += 1
|
|
40
45
|
end
|
|
41
46
|
end
|
|
@@ -56,7 +61,7 @@ module T::Props::WeakConstructor::DecoratorMethods
|
|
|
56
61
|
result = 0
|
|
57
62
|
props_with_defaults&.each_pair do |p, default_struct|
|
|
58
63
|
if hash.key?(p)
|
|
59
|
-
|
|
64
|
+
default_struct.bound_setter_proc.call(instance, hash[p])
|
|
60
65
|
result += 1
|
|
61
66
|
else
|
|
62
67
|
default_struct.set_default(instance)
|