Class: T::Private::Methods::Signature

Inherits:
Object
  • Object
show all
Defined in:
lib/types/private/methods/signature.rb

Overview

typed: true

Constant Summary collapse

EMPTY_HASH =
{}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(method:, method_name:, raw_arg_types:, raw_return_type:, bind:, mode:, check_level:, parameters: method.parameters, on_failure:, generated: false, override_allow_incompatible: false) ⇒ Signature

Returns a new instance of Signature.



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/types/private/methods/signature.rb', line 35

def initialize(method:, method_name:, raw_arg_types:, raw_return_type:, bind:, mode:, check_level:, parameters: method.parameters, on_failure:, generated: false, override_allow_incompatible: false)
  @method = method
  @method_name = method_name
  @arg_types = []
  @kwarg_types = {}
  @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
  @req_arg_count = 0
  @req_kwarg_names = []
  @has_rest = false
  @has_keyrest = false
  @parameters = parameters
  @on_failure = on_failure
  @override_allow_incompatible = override_allow_incompatible
  @generated = generated
  @ever_failed = false

  param_names = parameters.map {|_, name| name}
  declared_param_names = raw_arg_types.keys
  missing_names = param_names - declared_param_names
  extra_names = declared_param_names - param_names
  if !missing_names.empty?
    raise "The declaration for `#{method.name}` is missing parameter(s): #{missing_names.join(', ')}"
  end
  if !extra_names.empty?
    raise "The declaration for `#{method.name}` has extra parameter(s): #{extra_names.join(', ')}"
  end

  parameters.zip(raw_arg_types) do |(param_kind, param_name), (type_name, raw_type)|
    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 " \
            "#{declared_param_names.index(type_name) + 1}, defined in the method as arg number " \
            "#{param_names.index(type_name) + 1}).#{hint}\nMethod: #{method_desc}"
    end

    type = T::Utils.coerce(raw_type)

    case param_kind
    when :req
      if @arg_types.length > @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.
        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
  end
end

Instance Attribute Details

#arg_typesObject (readonly)

Returns the value of attribute arg_types.



5
6
7
# File 'lib/types/private/methods/signature.rb', line 5

def arg_types
  @arg_types
end

#bindObject (readonly)

Returns the value of attribute bind.



5
6
7
# File 'lib/types/private/methods/signature.rb', line 5

def bind
  @bind
end

#block_nameObject (readonly)

Returns the value of attribute block_name.



5
6
7
# File 'lib/types/private/methods/signature.rb', line 5

def block_name
  @block_name
end

#block_typeObject (readonly)

Returns the value of attribute block_type.



5
6
7
# File 'lib/types/private/methods/signature.rb', line 5

def block_type
  @block_type
end

#check_levelObject (readonly)

Returns the value of attribute check_level.



5
6
7
# File 'lib/types/private/methods/signature.rb', line 5

def check_level
  @check_level
end

#ever_failedObject (readonly)

Returns the value of attribute ever_failed.



5
6
7
# File 'lib/types/private/methods/signature.rb', line 5

def ever_failed
  @ever_failed
end

#generatedObject (readonly)

Returns the value of attribute generated.



5
6
7
# File 'lib/types/private/methods/signature.rb', line 5

def generated
  @generated
end

#has_keyrestObject (readonly)

Returns the value of attribute has_keyrest.



5
6
7
# File 'lib/types/private/methods/signature.rb', line 5

def has_keyrest
  @has_keyrest
end

#has_restObject (readonly)

Returns the value of attribute has_rest.



5
6
7
# File 'lib/types/private/methods/signature.rb', line 5

def has_rest
  @has_rest
end

#keyrest_nameObject (readonly)

Returns the value of attribute keyrest_name.



5
6
7
# File 'lib/types/private/methods/signature.rb', line 5

def keyrest_name
  @keyrest_name
end

#keyrest_typeObject (readonly)

Returns the value of attribute keyrest_type.



5
6
7
# File 'lib/types/private/methods/signature.rb', line 5

def keyrest_type
  @keyrest_type
end

#kwarg_typesObject (readonly)

Returns the value of attribute kwarg_types.



5
6
7
# File 'lib/types/private/methods/signature.rb', line 5

def kwarg_types
  @kwarg_types
end

#methodObject (readonly)

Returns the value of attribute method.



5
6
7
# File 'lib/types/private/methods/signature.rb', line 5

def method
  @method
end

#method_nameObject (readonly)

Returns the value of attribute method_name.



5
6
7
# File 'lib/types/private/methods/signature.rb', line 5

def method_name
  @method_name
end

#modeObject (readonly)

Returns the value of attribute mode.



5
6
7
# File 'lib/types/private/methods/signature.rb', line 5

def mode
  @mode
end

#on_failureObject (readonly)

Returns the value of attribute on_failure.



5
6
7
# File 'lib/types/private/methods/signature.rb', line 5

def on_failure
  @on_failure
end

#override_allow_incompatibleObject (readonly)

Returns the value of attribute override_allow_incompatible.



5
6
7
# File 'lib/types/private/methods/signature.rb', line 5

def override_allow_incompatible
  @override_allow_incompatible
end

#parametersObject (readonly)

Returns the value of attribute parameters.



5
6
7
# File 'lib/types/private/methods/signature.rb', line 5

def parameters
  @parameters
end

#req_arg_countObject (readonly)

Returns the value of attribute req_arg_count.



5
6
7
# File 'lib/types/private/methods/signature.rb', line 5

def req_arg_count
  @req_arg_count
end

#req_kwarg_namesObject (readonly)

Returns the value of attribute req_kwarg_names.



5
6
7
# File 'lib/types/private/methods/signature.rb', line 5

def req_kwarg_names
  @req_kwarg_names
end

#rest_nameObject (readonly)

Returns the value of attribute rest_name.



5
6
7
# File 'lib/types/private/methods/signature.rb', line 5

def rest_name
  @rest_name
end

#rest_typeObject (readonly)

Returns the value of attribute rest_type.



5
6
7
# File 'lib/types/private/methods/signature.rb', line 5

def rest_type
  @rest_type
end

#return_typeObject (readonly)

Returns the value of attribute return_type.



5
6
7
# File 'lib/types/private/methods/signature.rb', line 5

def return_type
  @return_type
end

Class Method Details

.new_untyped(method:, mode: T::Private::Methods::Modes.untyped, parameters: method.parameters) ⇒ Object



10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/types/private/methods/signature.rb', line 10

def self.new_untyped(method:, mode: T::Private::Methods::Modes.untyped, parameters: method.parameters)
  # Using `Untyped` ensures we'll get an error if we ever try validation on these.
  not_typed = T::Private::Types::NotTyped.new
  raw_return_type = not_typed
  raw_arg_types = parameters.map do |_param_kind, param_name|
    [param_name, not_typed]
  end.to_h

  self.new(
    method: method,
    method_name: method.name,
    raw_arg_types: raw_arg_types,
    raw_return_type: raw_return_type,
    bind: nil,
    mode: mode,
    check_level: :never,
    parameters: parameters,
    on_failure: nil,
  )
end

Instance Method Details

#arg_countObject



124
125
126
# File 'lib/types/private/methods/signature.rb', line 124

def arg_count
  @arg_types.length
end

#dsl_methodObject



136
137
138
# File 'lib/types/private/methods/signature.rb', line 136

def dsl_method
  "#{@mode}_method"
end

#each_args_value_type(args) ⇒ Hash

Returns a mapping like [val, type], …, for only those args actually present.

Returns:

  • (Hash)

    a mapping like [val, type], …, for only those args actually present.



141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/types/private/methods/signature.rb', line 141

def each_args_value_type(args)
  # Manually split out args and kwargs based on ruby's behavior. Do not try to implement this by
  # getting ruby to determine the kwargs for you (e.g., by defining this method to take *args and
  # **kwargs). That won't work, because ruby's behavior for determining kwargs is dependent on the
  # the other parameters in the method definition, and our method definition here doesn't (and
  # can't) match the definition of the method we're validating. In addition, Ruby has a bug that
  # causes forwarding **kwargs to do the wrong thing: see https://bugs.ruby-lang.org/issues/10708
  # and https://bugs.ruby-lang.org/issues/11860.
  if (args.length > @req_arg_count) && (!@kwarg_types.empty? || @has_keyrest) && args[-1].is_a?(Hash)
    kwargs = args[-1]
    args = args[0...-1]
  else
    kwargs = EMPTY_HASH
  end

  arg_types = @arg_types

  if @has_rest
    arg_types += [[@rest_name, @rest_type]] * (args.length - @arg_types.length)

  elsif (args.length < @req_arg_count) || (args.length > @arg_types.length)
    expected_str = @req_arg_count.to_s
    if @arg_types.length != @req_arg_count
      expected_str += "..#{@arg_types.length}"
    end
    raise ArgumentError.new("wrong number of arguments (given #{args.length}, expected #{expected_str})")
  end

  begin
    it = 0
    while it < args.length
      yield arg_types[it][0], args[it], arg_types[it][1]
      it += 1
    end
  end

  kwargs.each do |name, val|
    type = @kwarg_types[name]
    if !type && @has_keyrest
      type = @keyrest_type
    end
    yield name, val, type if type
  end
end

#kwarg_namesObject



128
129
130
# File 'lib/types/private/methods/signature.rb', line 128

def kwarg_names
  @kwarg_types.keys
end

#mark_failedObject



31
32
33
# File 'lib/types/private/methods/signature.rb', line 31

def mark_failed
  @ever_failed = true
end

#method_descObject



186
187
188
189
190
191
192
193
# File 'lib/types/private/methods/signature.rb', line 186

def method_desc
  if @method.source_location
    loc = @method.source_location.join(':')
  else
    loc = "<unknown location>"
  end
  "#{@method} at #{loc}"
end

#ownerObject



132
133
134
# File 'lib/types/private/methods/signature.rb', line 132

def owner
  @method.owner
end