module T::Props::Private::SerdeTransform
def self.generate(type, mode, varname)
def self.generate(type, mode, varname)
case type
when T::Types::TypedArray
inner = generate(type.type, mode, 'v')
if inner.nil?
"#{varname}.dup"
else
"#{varname}.map {|v| #{inner}}"
end
when T::Types::TypedSet
inner = generate(type.type, mode, 'v')
if inner.nil?
"#{varname}.dup"
else
"Set.new(#{varname}) {|v| #{inner}}"
end
when T::Types::TypedHash
keys = generate(type.keys, mode, 'k')
values = generate(type.values, mode, 'v')
if keys && values
"#{varname}.each_with_object({}) {|(k,v),h| h[#{keys}] = #{values}}"
elsif keys
"#{varname}.transform_keys {|k| #{keys}}"
elsif values
"#{varname}.transform_values {|v| #{values}}"
else
"#{varname}.dup"
end
when T::Types::Simple
raw = type.raw_type
if NO_TRANSFORM_TYPES.any? {|cls| raw <= cls}
nil
elsif raw <= Float
case mode
when Deserialize then "#{varname}.to_f"
when Serialize then nil
else T.absurd(mode)
end
elsif raw <= Numeric
nil
elsif raw < T::Props::Serializable
handle_serializable_subtype(varname, raw, mode)
elsif raw.singleton_class < T::Props::CustomType
handle_custom_type(varname, T.unsafe(raw), mode)
elsif T::Configuration.scalar_types.include?(raw.name)
# It's a bit of a hack that this is separate from NO_TRANSFORM_TYPES
# and doesn't check inheritance (like `T::Props::CustomType.scalar_type?`
# does), but it covers the main use case (pay-server's custom `Boolean`
# module) without either requiring `T::Configuration.scalar_types` to
# accept modules instead of strings (which produces load-order issues
# and subtle behavior changes) or eating the performance cost of doing
# an inheritance check by manually crawling a class hierarchy and doing
# string comparisons.
nil
else
"T::Props::Utils.deep_clone_object(#{varname})"
end
when T::Types::Union
non_nil_type = T::Utils.unwrap_nilable(type)
if non_nil_type
inner = generate(non_nil_type, mode, varname)
if inner.nil?
nil
else
"#{varname}.nil? ? nil : #{inner}"
end
elsif type.types.all? {|t| generate(t, mode, varname).nil?}
# Handle, e.g., T::Boolean
nil
else
# We currently deep_clone_object if the type was T.any(Integer, Float).
# When we get better support for union types (maybe this specific
# union type, because it would be a replacement for
# Chalk::ODM::DeprecatedNumemric), we could opt to special case
# this union to have no specific serde transform (the only reason
# why Float has a special case is because round tripping through
# JSON might normalize Floats to Integers)
"T::Props::Utils.deep_clone_object(#{varname})"
end
when T::Types::Intersection
dynamic_fallback = "T::Props::Utils.deep_clone_object(#{varname})"
# Transformations for any members of the intersection type where we
# know what we need to do and did not have to fall back to the
# dynamic deep clone method.
#
# NB: This deliberately does include `nil`, which means we know we
# don't need to do any transforming.
inner_known = type.types
.map {|t| generate(t, mode, varname)}
.reject {|t| t == dynamic_fallback}
.uniq
if inner_known.size != 1
# If there were no cases where we could tell what we need to do,
# e.g. if this is `T.all(SomethingWeird, WhoKnows)`, just use the
# dynamic fallback.
#
# If there were multiple cases and they weren't consistent, e.g.
# if this is `T.all(String, T::Array[Integer])`, the type is probably
# bogus/uninhabited, but use the dynamic fallback because we still
# don't have a better option, and this isn't the place to raise that
# error.
dynamic_fallback
else
# This is probably something like `T.all(String, SomeMarker)` or
# `T.all(SomeEnum, T.deprecated_enum(SomeEnum::FOO))` and we should
# treat it like String or SomeEnum even if we don't know what to do
# with the rest of the type.
inner_known.first
end
when T::Types::Enum
generate(T::Utils.lift_enum(type), mode, varname)
else
"T::Props::Utils.deep_clone_object(#{varname})"
end
end