# frozen_string_literal: true
# typed: strict
module T::Props
module Private
class ApplyDefault
extend T::Sig
extend T::Helpers
abstract!
# checked(:never) - O(object construction x prop count)
sig {returns(SetterFactory::SetterProc).checked(:never)}
attr_reader :setter_proc
# checked(:never) - We do this with `T.let` instead
sig {params(accessor_key: Symbol, setter_proc: SetterFactory::SetterProc).void.checked(:never)}
def initialize(accessor_key, setter_proc)
@accessor_key = T.let(accessor_key, Symbol)
@setter_proc = T.let(setter_proc, SetterFactory::SetterProc)
end
# checked(:never) - O(object construction x prop count)
sig {abstract.returns(T.untyped).checked(:never)}
def default; end
# checked(:never) - O(object construction x prop count)
sig {abstract.params(instance: T.all(T::Props::Optional, Object)).void.checked(:never)}
def set_default(instance); end
NO_CLONE_TYPES = T.let([TrueClass, FalseClass, NilClass, Symbol, Numeric, T::Enum].freeze, T::Array[Module])
# checked(:never) - Rules hash is expensive to check
sig {params(cls: Module, rules: T::Hash[Symbol, T.untyped]).returns(T.nilable(ApplyDefault)).checked(:never)}
def self.for(cls, rules)
accessor_key = rules.fetch(:accessor_key)
setter = rules.fetch(:setter_proc)
if rules.key?(:factory)
ApplyDefaultFactory.new(cls, rules.fetch(:factory), accessor_key, setter)
elsif rules.key?(:default)
default = rules.fetch(:default)
case default
when *NO_CLONE_TYPES
return ApplyPrimitiveDefault.new(default, accessor_key, setter)
when String
if default.frozen?
return ApplyPrimitiveDefault.new(default, accessor_key, setter)
end
when Array
if default.empty? && default.class == Array
return ApplyEmptyArrayDefault.new(accessor_key, setter)
end
when Hash
if default.empty? && default.default.nil? && T.unsafe(default).default_proc.nil? && default.class == Hash
return ApplyEmptyHashDefault.new(accessor_key, setter)
end
end
ApplyComplexDefault.new(default, accessor_key, setter)
else
nil
end
end
end
class ApplyFixedDefault < ApplyDefault
abstract!
# checked(:never) - We do this with `T.let` instead
sig {params(default: BasicObject, accessor_key: Symbol, setter_proc: SetterFactory::SetterProc).void.checked(:never)}
def initialize(default, accessor_key, setter_proc)
# FIXME: Ideally we'd check here that the default is actually a valid
# value for this field, but existing code relies on the fact that we don't.
#
# :(
#
# setter_proc.call(default)
@default = T.let(default, BasicObject)
super(accessor_key, setter_proc)
end
# checked(:never) - O(object construction x prop count)
sig {override.params(instance: T.all(T::Props::Optional, Object)).void.checked(:never)}
def set_default(instance)
instance.instance_variable_set(@accessor_key, default)
end
end
class ApplyPrimitiveDefault < ApplyFixedDefault
# checked(:never) - O(object construction x prop count)
sig {override.returns(T.untyped).checked(:never)}
attr_reader :default
end
class ApplyComplexDefault < ApplyFixedDefault
# checked(:never) - O(object construction x prop count)
sig {override.returns(T.untyped).checked(:never)}
def default
T::Props::Utils.deep_clone_object(@default)
end
end
# Special case since it's so common, and a literal `[]` is meaningfully
# faster than falling back to ApplyComplexDefault or even calling
# `some_empty_array.dup`
class ApplyEmptyArrayDefault < ApplyDefault
# checked(:never) - O(object construction x prop count)
sig {override.params(instance: T.all(T::Props::Optional, Object)).void.checked(:never)}
def set_default(instance)
instance.instance_variable_set(@accessor_key, [])
end
# checked(:never) - O(object construction x prop count)
sig {override.returns(T::Array[T.untyped]).checked(:never)}
def default
[]
end
end
# Special case since it's so common, and a literal `{}` is meaningfully
# faster than falling back to ApplyComplexDefault or even calling
# `some_empty_hash.dup`
class ApplyEmptyHashDefault < ApplyDefault
# checked(:never) - O(object construction x prop count)
sig {override.params(instance: T.all(T::Props::Optional, Object)).void.checked(:never)}
def set_default(instance)
instance.instance_variable_set(@accessor_key, {})
end
# checked(:never) - O(object construction x prop count)
sig {override.returns(T::Hash[T.untyped, T.untyped]).checked(:never)}
def default
{}
end
end
class ApplyDefaultFactory < ApplyDefault
# checked(:never) - We do this with `T.let` instead
sig do
params(
cls: Module,
factory: T.any(Proc, Method),
accessor_key: Symbol,
setter_proc: SetterFactory::SetterProc,
)
.void
.checked(:never)
end
def initialize(cls, factory, accessor_key, setter_proc)
@class = T.let(cls, Module)
@factory = T.let(factory, T.any(Proc, Method))
super(accessor_key, setter_proc)
end
# checked(:never) - O(object construction x prop count)
sig {override.params(instance: T.all(T::Props::Optional, Object)).void.checked(:never)}
def set_default(instance)
# Use the actual setter to validate the factory returns a legitimate
# value every time
instance.instance_exec(default, &@setter_proc)
end
# checked(:never) - O(object construction x prop count)
sig {override.returns(T.untyped).checked(:never)}
def default
@class.class_exec(&@factory)
end
end
end
end