def initialize(method:, method_name:, raw_arg_types:, raw_return_type:, bind:, mode:, check_level:, on_failure:, parameters: method.parameters, override_allow_incompatible: false, defined_raw: false)
def initialize(method:, method_name:, raw_arg_types:, raw_return_type:, bind:, mode:, check_level:, on_failure:, parameters: method.parameters, override_allow_incompatible: false, defined_raw: false)
@method = method
@method_name = method_name
@block_type = nil
@block_name = nil
@rest_type = nil
@rest_name = nil
@keyrest_type = nil
@keyrest_name = nil
@return_type = T::Utils.coerce(raw_return_type)
@bind = bind ? T::Utils.coerce(bind) : bind
@mode = mode
@check_level = check_level
@has_rest = false
@has_keyrest = false
@parameters = parameters
@on_failure = on_failure
@override_allow_incompatible = override_allow_incompatible
@defined_raw = defined_raw
# Use T.untyped in lieu of T.nilable to try to avoid unnecessary allocations.
arg_types = T.let(nil, T.untyped)
kwarg_types = T.let(nil, T.untyped)
req_arg_count = 0
req_kwarg_names = T.let(nil, T.untyped)
# If sig params are declared but there is a single parameter with a missing name
# **and** the method ends with a "=", assume it is a writer method generated
# by attr_writer or attr_accessor
writer_method = !(raw_arg_types.size == 1 && raw_arg_types.key?(nil)) && parameters == UNNAMED_REQUIRED_PARAMETERS && method_name[-1] == "="
# For writer methods, map the single parameter to the method name without the "=" at the end
parameters = [[:req, method_name[0...-1].to_sym]] if writer_method
is_name_missing = parameters.any? {|_, name| !raw_arg_types.key?(name)}
if is_name_missing
param_names = parameters.map {|_, name| name}
missing_names = param_names - raw_arg_types.keys
raise "The declaration for `#{method.name}` is missing parameter(s): #{missing_names.join(', ')}"
elsif parameters.length == raw_arg_types.size
else
param_names = parameters.map {|_, name| name}
has_extra_names = parameters.count {|_, name| raw_arg_types.key?(name)} < raw_arg_types.size
if has_extra_names
extra_names = raw_arg_types.keys - param_names
raise "The declaration for `#{method.name}` has extra parameter(s): #{extra_names.join(', ')}"
end
end
if parameters.size != raw_arg_types.size
raise "The declaration for `#{method.name}` has arguments with duplicate names"
end
i = 0
raw_arg_types.each do |type_name, raw_type|
param_kind, param_name = parameters[i]
if type_name != param_name
hint = ""
# Ruby reorders params so that required keyword arguments
# always precede optional keyword arguments. We can't tell
# whether the culprit is the Ruby reordering or user error, so
# we error but include a note
if param_kind == :keyreq && parameters.any? {|k, _| k == :key}
hint = "\n\nNote: Any required keyword arguments must precede any optional keyword " \
"arguments. If your method declaration matches your `def`, try reordering any " \
"optional keyword parameters to the end of the method list."
end
raise "Parameter `#{type_name}` is declared out of order (declared as arg number " \
"#{i + 1}, defined in the method as arg number " \
"#{parameters.index {|_, name| name == type_name} + 1}).#{hint}\nMethod: #{method_desc}"
end
type = T::Utils.coerce(raw_type)
case param_kind
when :req
if (arg_types ? arg_types.length : 0) > req_arg_count
# Note that this is actually is supported by Ruby, but it would add complexity to
# support it here, and I'm happy to discourage its use anyway.
#
# If you are seeing this error and surprised by it, it's possible that you have
# overridden the method described in the error message. For example, Rails defines
# def self.update!(id = :all, attributes)
# on AR models. If you have also defined `self.update!` on an AR model you might
# see this error. The simplest resolution is to rename your method.
raise "Required params after optional params are not supported in method declarations. Method: #{method_desc}"
end
(arg_types ||= []) << [param_name, type]
req_arg_count += 1
when :opt
(arg_types ||= []) << [param_name, type]
when :key, :keyreq
(kwarg_types ||= {})[param_name] = type
if param_kind == :keyreq
(req_kwarg_names ||= []) << param_name
end
when :block
@block_name = param_name
@block_type = type
when :rest
@has_rest = true
@rest_name = param_name
@rest_type = type
when :keyrest
@has_keyrest = true
@keyrest_name = param_name
@keyrest_type = type
else
raise "Unexpected param_kind: `#{param_kind}`. Method: #{method_desc}"
end
i += 1
end
@arg_types = arg_types || EMPTY_LIST
@kwarg_types = kwarg_types || EMPTY_HASH
@req_arg_count = req_arg_count
@req_kwarg_names = req_kwarg_names || EMPTY_LIST
end