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.
Files changed (70) hide show
  1. checksums.yaml +5 -5
  2. data/lib/sorbet-runtime.rb +15 -3
  3. data/lib/types/_types.rb +26 -17
  4. data/lib/types/boolean.rb +1 -1
  5. data/lib/types/compatibility_patches.rb +65 -10
  6. data/lib/types/configuration.rb +93 -7
  7. data/lib/types/enum.rb +371 -0
  8. data/lib/types/generic.rb +2 -2
  9. data/lib/types/interface_wrapper.rb +4 -4
  10. data/lib/types/non_forcing_constants.rb +61 -0
  11. data/lib/types/private/abstract/data.rb +2 -2
  12. data/lib/types/private/abstract/declare.rb +3 -0
  13. data/lib/types/private/abstract/validate.rb +7 -7
  14. data/lib/types/private/casts.rb +27 -0
  15. data/lib/types/private/class_utils.rb +8 -5
  16. data/lib/types/private/methods/_methods.rb +80 -28
  17. data/lib/types/private/methods/call_validation.rb +5 -47
  18. data/lib/types/private/methods/decl_builder.rb +14 -56
  19. data/lib/types/private/methods/modes.rb +5 -7
  20. data/lib/types/private/methods/signature.rb +32 -18
  21. data/lib/types/private/methods/signature_validation.rb +29 -35
  22. data/lib/types/private/retry.rb +10 -0
  23. data/lib/types/private/sealed.rb +21 -1
  24. data/lib/types/private/types/type_alias.rb +31 -0
  25. data/lib/types/private/types/void.rb +4 -3
  26. data/lib/types/profile.rb +5 -1
  27. data/lib/types/props/_props.rb +3 -7
  28. data/lib/types/props/constructor.rb +29 -9
  29. data/lib/types/props/custom_type.rb +51 -27
  30. data/lib/types/props/decorator.rb +248 -405
  31. data/lib/types/props/generated_code_validation.rb +268 -0
  32. data/lib/types/props/has_lazily_specialized_methods.rb +92 -0
  33. data/lib/types/props/optional.rb +37 -41
  34. data/lib/types/props/plugin.rb +23 -1
  35. data/lib/types/props/pretty_printable.rb +3 -3
  36. data/lib/types/props/private/apply_default.rb +170 -0
  37. data/lib/types/props/private/deserializer_generator.rb +165 -0
  38. data/lib/types/props/private/parser.rb +32 -0
  39. data/lib/types/props/private/serde_transform.rb +186 -0
  40. data/lib/types/props/private/serializer_generator.rb +77 -0
  41. data/lib/types/props/private/setter_factory.rb +139 -0
  42. data/lib/types/props/serializable.rb +137 -192
  43. data/lib/types/props/type_validation.rb +19 -6
  44. data/lib/types/props/utils.rb +3 -7
  45. data/lib/types/props/weak_constructor.rb +51 -14
  46. data/lib/types/sig.rb +6 -6
  47. data/lib/types/types/attached_class.rb +37 -0
  48. data/lib/types/types/base.rb +26 -2
  49. data/lib/types/types/fixed_array.rb +28 -2
  50. data/lib/types/types/fixed_hash.rb +11 -10
  51. data/lib/types/types/intersection.rb +6 -0
  52. data/lib/types/types/noreturn.rb +4 -0
  53. data/lib/types/types/self_type.rb +4 -0
  54. data/lib/types/types/simple.rb +22 -1
  55. data/lib/types/types/t_enum.rb +38 -0
  56. data/lib/types/types/type_parameter.rb +1 -1
  57. data/lib/types/types/type_variable.rb +1 -1
  58. data/lib/types/types/typed_array.rb +7 -2
  59. data/lib/types/types/typed_enumerable.rb +28 -17
  60. data/lib/types/types/typed_enumerator.rb +7 -2
  61. data/lib/types/types/typed_hash.rb +8 -3
  62. data/lib/types/types/typed_range.rb +7 -2
  63. data/lib/types/types/typed_set.rb +7 -2
  64. data/lib/types/types/union.rb +37 -5
  65. data/lib/types/types/untyped.rb +4 -0
  66. data/lib/types/utils.rb +43 -11
  67. metadata +103 -11
  68. data/lib/types/private/error_handler.rb +0 -0
  69. data/lib/types/runtime_profiled.rb +0 -24
  70. data/lib/types/types/opus_enum.rb +0 -33
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
- # typed: true
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)
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)
34
+ end
35
+ end
36
+ end
15
37
  end
@@ -18,9 +18,9 @@ module T::Props::PrettyPrintable
18
18
  module DecoratorMethods
19
19
  extend T::Sig
20
20
 
21
- sig {returns(T::Array[Symbol])}
22
- def valid_props
23
- super + [:inspect]
21
+ sig {params(key: Symbol).returns(T::Boolean).checked(:never)}
22
+ def valid_rule_key?(key)
23
+ super || key == :inspect
24
24
  end
25
25
 
26
26
  sig do
@@ -0,0 +1,170 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ module T::Props
5
+ module Private
6
+ class ApplyDefault
7
+ extend T::Sig
8
+ extend T::Helpers
9
+ abstract!
10
+
11
+ # checked(:never) - O(object construction x prop count)
12
+ sig {returns(SetterFactory::SetterProc).checked(:never)}
13
+ attr_reader :setter_proc
14
+
15
+ # checked(:never) - We do this with `T.let` instead
16
+ sig {params(accessor_key: Symbol, setter_proc: SetterFactory::SetterProc).void.checked(:never)}
17
+ def initialize(accessor_key, setter_proc)
18
+ @accessor_key = T.let(accessor_key, Symbol)
19
+ @setter_proc = T.let(setter_proc, SetterFactory::SetterProc)
20
+ end
21
+
22
+ # checked(:never) - O(object construction x prop count)
23
+ sig {abstract.returns(T.untyped).checked(:never)}
24
+ def default; end
25
+
26
+ # checked(:never) - O(object construction x prop count)
27
+ sig {abstract.params(instance: T.all(T::Props::Optional, Object)).void.checked(:never)}
28
+ def set_default(instance); end
29
+
30
+ NO_CLONE_TYPES = T.let([TrueClass, FalseClass, NilClass, Symbol, Numeric, T::Enum].freeze, T::Array[Module])
31
+
32
+ # checked(:never) - Rules hash is expensive to check
33
+ sig {params(cls: Module, rules: T::Hash[Symbol, T.untyped]).returns(T.nilable(ApplyDefault)).checked(:never)}
34
+ def self.for(cls, rules)
35
+ accessor_key = rules.fetch(:accessor_key)
36
+ setter = rules.fetch(:setter_proc)
37
+
38
+ if rules.key?(:factory)
39
+ ApplyDefaultFactory.new(cls, rules.fetch(:factory), accessor_key, setter)
40
+ elsif rules.key?(:default)
41
+ default = rules.fetch(:default)
42
+ case default
43
+ when *NO_CLONE_TYPES
44
+ return ApplyPrimitiveDefault.new(default, accessor_key, setter)
45
+ when String
46
+ if default.frozen?
47
+ return ApplyPrimitiveDefault.new(default, accessor_key, setter)
48
+ end
49
+ when Array
50
+ if default.empty? && default.class == Array
51
+ return ApplyEmptyArrayDefault.new(accessor_key, setter)
52
+ end
53
+ when Hash
54
+ if default.empty? && default.default.nil? && T.unsafe(default).default_proc.nil? && default.class == Hash
55
+ return ApplyEmptyHashDefault.new(accessor_key, setter)
56
+ end
57
+ end
58
+
59
+ ApplyComplexDefault.new(default, accessor_key, setter)
60
+ else
61
+ nil
62
+ end
63
+ end
64
+ end
65
+
66
+ class ApplyFixedDefault < ApplyDefault
67
+ abstract!
68
+
69
+ # checked(:never) - We do this with `T.let` instead
70
+ sig {params(default: BasicObject, accessor_key: Symbol, setter_proc: SetterFactory::SetterProc).void.checked(:never)}
71
+ def initialize(default, accessor_key, setter_proc)
72
+ # FIXME: Ideally we'd check here that the default is actually a valid
73
+ # value for this field, but existing code relies on the fact that we don't.
74
+ #
75
+ # :(
76
+ #
77
+ # setter_proc.call(default)
78
+ @default = T.let(default, BasicObject)
79
+ super(accessor_key, setter_proc)
80
+ end
81
+
82
+ # checked(:never) - O(object construction x prop count)
83
+ sig {override.params(instance: T.all(T::Props::Optional, Object)).void.checked(:never)}
84
+ def set_default(instance)
85
+ instance.instance_variable_set(@accessor_key, default)
86
+ end
87
+ end
88
+
89
+ class ApplyPrimitiveDefault < ApplyFixedDefault
90
+ # checked(:never) - O(object construction x prop count)
91
+ sig {override.returns(T.untyped).checked(:never)}
92
+ attr_reader :default
93
+ end
94
+
95
+ class ApplyComplexDefault < ApplyFixedDefault
96
+ # checked(:never) - O(object construction x prop count)
97
+ sig {override.returns(T.untyped).checked(:never)}
98
+ def default
99
+ T::Props::Utils.deep_clone_object(@default)
100
+ end
101
+ end
102
+
103
+ # Special case since it's so common, and a literal `[]` is meaningfully
104
+ # faster than falling back to ApplyComplexDefault or even calling
105
+ # `some_empty_array.dup`
106
+ class ApplyEmptyArrayDefault < ApplyDefault
107
+ # checked(:never) - O(object construction x prop count)
108
+ sig {override.params(instance: T.all(T::Props::Optional, Object)).void.checked(:never)}
109
+ def set_default(instance)
110
+ instance.instance_variable_set(@accessor_key, [])
111
+ end
112
+
113
+ # checked(:never) - O(object construction x prop count)
114
+ sig {override.returns(T::Array[T.untyped]).checked(:never)}
115
+ def default
116
+ []
117
+ end
118
+ end
119
+
120
+ # Special case since it's so common, and a literal `{}` is meaningfully
121
+ # faster than falling back to ApplyComplexDefault or even calling
122
+ # `some_empty_hash.dup`
123
+ class ApplyEmptyHashDefault < ApplyDefault
124
+ # checked(:never) - O(object construction x prop count)
125
+ sig {override.params(instance: T.all(T::Props::Optional, Object)).void.checked(:never)}
126
+ def set_default(instance)
127
+ instance.instance_variable_set(@accessor_key, {})
128
+ end
129
+
130
+ # checked(:never) - O(object construction x prop count)
131
+ sig {override.returns(T::Hash[T.untyped, T.untyped]).checked(:never)}
132
+ def default
133
+ {}
134
+ end
135
+ end
136
+
137
+ class ApplyDefaultFactory < ApplyDefault
138
+ # checked(:never) - We do this with `T.let` instead
139
+ sig do
140
+ params(
141
+ cls: Module,
142
+ factory: T.any(Proc, Method),
143
+ accessor_key: Symbol,
144
+ setter_proc: SetterFactory::SetterProc,
145
+ )
146
+ .void
147
+ .checked(:never)
148
+ end
149
+ def initialize(cls, factory, accessor_key, setter_proc)
150
+ @class = T.let(cls, Module)
151
+ @factory = T.let(factory, T.any(Proc, Method))
152
+ super(accessor_key, setter_proc)
153
+ end
154
+
155
+ # checked(:never) - O(object construction x prop count)
156
+ sig {override.params(instance: T.all(T::Props::Optional, Object)).void.checked(:never)}
157
+ def set_default(instance)
158
+ # Use the actual setter to validate the factory returns a legitimate
159
+ # value every time
160
+ instance.instance_exec(default, &@setter_proc)
161
+ end
162
+
163
+ # checked(:never) - O(object construction x prop count)
164
+ sig {override.returns(T.untyped).checked(:never)}
165
+ def default
166
+ @class.class_exec(&@factory)
167
+ end
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,165 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ module T::Props
5
+ module Private
6
+
7
+ # Generates a specialized `deserialize` implementation for a subclass of
8
+ # T::Props::Serializable.
9
+ #
10
+ # The basic idea is that we analyze the props and for each prop, generate
11
+ # the simplest possible logic as a block of Ruby source, so that we don't
12
+ # pay the cost of supporting types like T:::Hash[CustomType, SubstructType]
13
+ # when deserializing a simple Integer. Then we join those together,
14
+ # with a little shared logic to be able to detect when we get input keys
15
+ # that don't match any prop.
16
+ module DeserializerGenerator
17
+ extend T::Sig
18
+
19
+ # Generate a method that takes a T::Hash[String, T.untyped] representing
20
+ # serialized props, sets instance variables for each prop found in the
21
+ # input, and returns the count of we props set (which we can use to check
22
+ # for unexpected input keys with minimal effect on the fast path).
23
+ sig do
24
+ params(
25
+ props: T::Hash[Symbol, T::Hash[Symbol, T.untyped]],
26
+ defaults: T::Hash[Symbol, T::Props::Private::ApplyDefault],
27
+ )
28
+ .returns(String)
29
+ .checked(:never)
30
+ end
31
+ def self.generate(props, defaults)
32
+ stored_props = props.reject {|_, rules| rules[:dont_store]}
33
+ parts = stored_props.map do |prop, rules|
34
+ # All of these strings should already be validated (directly or
35
+ # indirectly) in `validate_prop_name`, so we don't bother with a nice
36
+ # error message, but we double check here to prevent a refactoring
37
+ # from introducing a security vulnerability.
38
+ raise unless T::Props::Decorator::SAFE_NAME.match?(prop.to_s)
39
+
40
+ hash_key = rules.fetch(:serialized_form)
41
+ raise unless T::Props::Decorator::SAFE_NAME.match?(hash_key)
42
+
43
+ ivar_name = rules.fetch(:accessor_key).to_s
44
+ raise unless ivar_name.start_with?('@') && T::Props::Decorator::SAFE_NAME.match?(ivar_name[1..-1])
45
+
46
+ transformation = SerdeTransform.generate(
47
+ T::Utils::Nilable.get_underlying_type_object(rules.fetch(:type_object)),
48
+ SerdeTransform::Mode::DESERIALIZE,
49
+ 'val'
50
+ )
51
+ transformed_val = if transformation
52
+ # Rescuing exactly NoMethodError is intended as a temporary hack
53
+ # to preserve the semantics from before codegen. More generally
54
+ # we are inconsistent about typechecking on deser and need to decide
55
+ # our strategy here.
56
+ <<~RUBY
57
+ begin
58
+ #{transformation}
59
+ rescue NoMethodError => e
60
+ T::Configuration.soft_assert_handler(
61
+ 'Deserialization error (probably unexpected stored type)',
62
+ storytime: {
63
+ klass: self.class,
64
+ prop: #{prop.inspect},
65
+ value: val,
66
+ error: e.message,
67
+ notify: 'djudd'
68
+ }
69
+ )
70
+ val
71
+ end
72
+ RUBY
73
+ else
74
+ 'val'
75
+ end
76
+
77
+ nil_handler = generate_nil_handler(
78
+ prop: prop,
79
+ serialized_form: hash_key,
80
+ default: defaults[prop],
81
+ nilable_type: T::Props::Utils.optional_prop?(rules),
82
+ raise_on_nil_write: !!rules[:raise_on_nil_write],
83
+ )
84
+
85
+ <<~RUBY
86
+ val = hash[#{hash_key.inspect}]
87
+ #{ivar_name} = if val.nil?
88
+ found -= 1 unless hash.key?(#{hash_key.inspect})
89
+ #{nil_handler}
90
+ else
91
+ #{transformed_val}
92
+ end
93
+ RUBY
94
+ end
95
+
96
+ <<~RUBY
97
+ def __t_props_generated_deserialize(hash)
98
+ found = #{stored_props.size}
99
+ #{parts.join("\n\n")}
100
+ found
101
+ end
102
+ RUBY
103
+ end
104
+
105
+ # This is very similar to what we do in ApplyDefault, but has a few
106
+ # key differences that mean we don't just re-use the code:
107
+ #
108
+ # 1. Where the logic in construction is that we generate a default
109
+ # if & only if the prop key isn't present in the input, here we'll
110
+ # generate a default even to override an explicit nil, but only
111
+ # if the prop is actually required.
112
+ # 2. Since we're generating raw Ruby source, we can remove a layer
113
+ # of indirection for marginally better performance; this seems worth
114
+ # it for the common cases of literals and empty arrays/hashes.
115
+ # 3. We need to care about the distinction between `raise_on_nil_write`
116
+ # and actually non-nilable, where new-instance construction doesn't.
117
+ #
118
+ # So we fall back to ApplyDefault only when one of the cases just
119
+ # mentioned doesn't apply.
120
+ sig do
121
+ params(
122
+ prop: Symbol,
123
+ serialized_form: String,
124
+ default: T.nilable(ApplyDefault),
125
+ nilable_type: T::Boolean,
126
+ raise_on_nil_write: T::Boolean,
127
+ )
128
+ .returns(String)
129
+ .checked(:never)
130
+ end
131
+ private_class_method def self.generate_nil_handler(
132
+ prop:,
133
+ serialized_form:,
134
+ default:,
135
+ nilable_type:,
136
+ raise_on_nil_write:
137
+ )
138
+ if !nilable_type
139
+ case default
140
+ when NilClass
141
+ "self.class.decorator.raise_nil_deserialize_error(#{serialized_form.inspect})"
142
+ when ApplyPrimitiveDefault
143
+ literal = default.default
144
+ case literal
145
+ when String, Integer, Symbol, Float, TrueClass, FalseClass, NilClass
146
+ literal.inspect
147
+ else
148
+ "self.class.decorator.props_with_defaults.fetch(#{prop.inspect}).default"
149
+ end
150
+ when ApplyEmptyArrayDefault
151
+ '[]'
152
+ when ApplyEmptyHashDefault
153
+ '{}'
154
+ else
155
+ "self.class.decorator.props_with_defaults.fetch(#{prop.inspect}).default"
156
+ end
157
+ elsif raise_on_nil_write
158
+ "required_prop_missing_from_deserialize(#{prop.inspect})"
159
+ else
160
+ 'nil'
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+ # typed: false
3
+
4
+ module T::Props
5
+ module Private
6
+ module Parse
7
+ def parse(source)
8
+ @current_ruby ||= require_parser(:CurrentRuby)
9
+ @current_ruby.parse(source)
10
+ end
11
+
12
+ def s(type, *children)
13
+ @node ||= require_parser(:AST, :Node)
14
+ @node.new(type, children)
15
+ end
16
+
17
+ private def require_parser(*constants)
18
+ # This is an optional dependency for sorbet-runtime in general,
19
+ # but is required here
20
+ require 'parser/current'
21
+
22
+ # Hack to work around the static checker thinking the constant is
23
+ # undefined
24
+ cls = Kernel.const_get(:Parser, true)
25
+ while (const = constants.shift)
26
+ cls = cls.const_get(const, false)
27
+ end
28
+ cls
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,186 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ module T::Props
5
+ module Private
6
+ module SerdeTransform
7
+ extend T::Sig
8
+
9
+ class Serialize; end
10
+ private_constant :Serialize
11
+ class Deserialize; end
12
+ private_constant :Deserialize
13
+ ModeType = T.type_alias {T.any(Serialize, Deserialize)}
14
+ private_constant :ModeType
15
+
16
+ module Mode
17
+ SERIALIZE = T.let(Serialize.new.freeze, Serialize)
18
+ DESERIALIZE = T.let(Deserialize.new.freeze, Deserialize)
19
+ end
20
+
21
+ NO_TRANSFORM_TYPES = T.let(
22
+ [TrueClass, FalseClass, NilClass, Symbol, String].freeze,
23
+ T::Array[Module],
24
+ )
25
+ private_constant :NO_TRANSFORM_TYPES
26
+
27
+ sig do
28
+ params(
29
+ type: T::Types::Base,
30
+ mode: ModeType,
31
+ varname: String,
32
+ )
33
+ .returns(T.nilable(String))
34
+ .checked(:never)
35
+ end
36
+ def self.generate(type, mode, varname)
37
+ case type
38
+ when T::Types::TypedArray
39
+ inner = generate(type.type, mode, 'v')
40
+ if inner.nil?
41
+ "#{varname}.dup"
42
+ else
43
+ "#{varname}.map {|v| #{inner}}"
44
+ end
45
+ when T::Types::TypedSet
46
+ inner = generate(type.type, mode, 'v')
47
+ if inner.nil?
48
+ "#{varname}.dup"
49
+ else
50
+ "Set.new(#{varname}) {|v| #{inner}}"
51
+ end
52
+ when T::Types::TypedHash
53
+ keys = generate(type.keys, mode, 'k')
54
+ values = generate(type.values, mode, 'v')
55
+ if keys && values
56
+ "#{varname}.each_with_object({}) {|(k,v),h| h[#{keys}] = #{values}}"
57
+ elsif keys
58
+ "#{varname}.transform_keys {|k| #{keys}}"
59
+ elsif values
60
+ "#{varname}.transform_values {|v| #{values}}"
61
+ else
62
+ "#{varname}.dup"
63
+ end
64
+ when T::Types::Simple
65
+ raw = type.raw_type
66
+ if NO_TRANSFORM_TYPES.any? {|cls| raw <= cls}
67
+ nil
68
+ elsif raw <= Float
69
+ case mode
70
+ when Deserialize then "#{varname}.to_f"
71
+ when Serialize then nil
72
+ else T.absurd(mode)
73
+ end
74
+ elsif raw <= Numeric
75
+ nil
76
+ elsif raw < T::Props::Serializable
77
+ handle_serializable_subtype(varname, raw, mode)
78
+ elsif raw.singleton_class < T::Props::CustomType
79
+ handle_custom_type(varname, T.unsafe(raw), mode)
80
+ elsif T::Configuration.scalar_types.include?(raw.name)
81
+ # It's a bit of a hack that this is separate from NO_TRANSFORM_TYPES
82
+ # and doesn't check inheritance (like `T::Props::CustomType.scalar_type?`
83
+ # does), but it covers the main use case (pay-server's custom `Boolean`
84
+ # module) without either requiring `T::Configuration.scalar_types` to
85
+ # accept modules instead of strings (which produces load-order issues
86
+ # and subtle behavior changes) or eating the performance cost of doing
87
+ # an inheritance check by manually crawling a class hierarchy and doing
88
+ # string comparisons.
89
+ nil
90
+ else
91
+ "T::Props::Utils.deep_clone_object(#{varname})"
92
+ end
93
+ when T::Types::Union
94
+ non_nil_type = T::Utils.unwrap_nilable(type)
95
+ if non_nil_type
96
+ inner = generate(non_nil_type, mode, varname)
97
+ if inner.nil?
98
+ nil
99
+ else
100
+ "#{varname}.nil? ? nil : #{inner}"
101
+ end
102
+ elsif type.types.all? {|t| generate(t, mode, varname).nil?}
103
+ # Handle, e.g., T::Boolean
104
+ nil
105
+ else
106
+ # We currently deep_clone_object if the type was T.any(Integer, Float).
107
+ # When we get better support for union types (maybe this specific
108
+ # union type, because it would be a replacement for
109
+ # Chalk::ODM::DeprecatedNumemric), we could opt to special case
110
+ # this union to have no specific serde transform (the only reason
111
+ # why Float has a special case is because round tripping through
112
+ # JSON might normalize Floats to Integers)
113
+ "T::Props::Utils.deep_clone_object(#{varname})"
114
+ end
115
+ when T::Types::Intersection
116
+ dynamic_fallback = "T::Props::Utils.deep_clone_object(#{varname})"
117
+
118
+ # Transformations for any members of the intersection type where we
119
+ # know what we need to do and did not have to fall back to the
120
+ # dynamic deep clone method.
121
+ #
122
+ # NB: This deliberately does include `nil`, which means we know we
123
+ # don't need to do any transforming.
124
+ inner_known = type.types
125
+ .map {|t| generate(t, mode, varname)}
126
+ .reject {|t| t == dynamic_fallback}
127
+ .uniq
128
+
129
+ if inner_known.size != 1
130
+ # If there were no cases where we could tell what we need to do,
131
+ # e.g. if this is `T.all(SomethingWeird, WhoKnows)`, just use the
132
+ # dynamic fallback.
133
+ #
134
+ # If there were multiple cases and they weren't consistent, e.g.
135
+ # if this is `T.all(String, T::Array[Integer])`, the type is probably
136
+ # bogus/uninhabited, but use the dynamic fallback because we still
137
+ # don't have a better option, and this isn't the place to raise that
138
+ # error.
139
+ dynamic_fallback
140
+ else
141
+ # This is probably something like `T.all(String, SomeMarker)` or
142
+ # `T.all(SomeEnum, T.enum(SomeEnum::FOO))` and we should treat it
143
+ # like String or SomeEnum even if we don't know what to do with
144
+ # the rest of the type.
145
+ inner_known.first
146
+ end
147
+ when T::Types::Enum
148
+ generate(T::Utils.lift_enum(type), mode, varname)
149
+ else
150
+ "T::Props::Utils.deep_clone_object(#{varname})"
151
+ end
152
+ end
153
+
154
+ sig {params(varname: String, type: Module, mode: ModeType).returns(String).checked(:never)}
155
+ private_class_method def self.handle_serializable_subtype(varname, type, mode)
156
+ case mode
157
+ when Serialize
158
+ "#{varname}.serialize(strict)"
159
+ when Deserialize
160
+ type_name = T.must(module_name(type))
161
+ "#{type_name}.from_hash(#{varname})"
162
+ else
163
+ T.absurd(mode)
164
+ end
165
+ end
166
+
167
+ sig {params(varname: String, type: Module, mode: ModeType).returns(String).checked(:never)}
168
+ private_class_method def self.handle_custom_type(varname, type, mode)
169
+ case mode
170
+ when Serialize
171
+ "T::Props::CustomType.checked_serialize(#{varname})"
172
+ when Deserialize
173
+ type_name = T.must(module_name(type))
174
+ "#{type_name}.deserialize(#{varname})"
175
+ else
176
+ T.absurd(mode)
177
+ end
178
+ end
179
+
180
+ sig {params(type: Module).returns(T.nilable(String)).checked(:never)}
181
+ private_class_method def self.module_name(type)
182
+ T::Configuration.module_name_mangler.call(type)
183
+ end
184
+ end
185
+ end
186
+ end