typeguard 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,237 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yard'
4
+
5
+ module Typeguard
6
+ module TypeModel
7
+ module Builder
8
+ # Takes YARD documentation and returns a generic type model
9
+ class YardBuilder
10
+ include Typeguard::TypeModel::Definitions
11
+
12
+ # @see https://rubydoc.info/gems/typeguard/YARD/Registry
13
+ # @param reparse_files [Boolean] has no effect if target is a string.
14
+ # If false and target is an array, the files are only reparsed if no .yardoc is present.
15
+ # If true and target is an array, the files are always reparsed.
16
+ def initialize(target, reparse_files)
17
+ @object_vars = {}
18
+ return unless YARD::Registry.load(target, reparse_files).root.children.empty?
19
+
20
+ if target.is_a?(String)
21
+ puts "WARNING: could not find YARD objects for target directory '#{target}'. " \
22
+ "Confirm that the directory exists and/or execute 'yardoc [...]' again."
23
+ else
24
+ puts "WARNING: could not find YARD objects for target files after reparsing array #{target}. " \
25
+ "Confirm that the files exist and/or execute 'yardoc #{target.join(' ')}' again in the correct directory."
26
+ end
27
+ end
28
+
29
+ def build
30
+ # Deduplicated tree-like structure where the root is an array of
31
+ # objects whose parent is undefined or YARD root/proxy
32
+ YARD::Registry.all(:class, :module, :method).filter_map do |object|
33
+ build_object(object) if object.parent.nil? || %i[root proxy].include?(object.parent.type)
34
+ end
35
+ end
36
+
37
+ def build_object(object)
38
+ case object.type
39
+ when :class
40
+ children = object.children.map { |child| build_object(child) }.compact
41
+ ClassDefinition.new(
42
+ name: object.path.gsub('.self', ''),
43
+ source: "#{object.file}:#{object.line}",
44
+ vars: build_inherit_vars(object),
45
+ parent: object.superclass&.path,
46
+ type_parameters: nil,
47
+ children: children
48
+ )
49
+ when :module
50
+ children = object.children.map { |child| build_object(child) }.compact
51
+ ModuleDefinition.new(
52
+ name: object.path.gsub('.self', ''),
53
+ source: "#{object.file}:#{object.line}",
54
+ vars: build_inherit_vars(object),
55
+ type_parameters: nil,
56
+ children: children
57
+ )
58
+ when :method
59
+ return_tag = object.tag(:return)
60
+ returns = ReturnDefinition.new(
61
+ source: "#{object.file}:#{object.line}",
62
+ types: build_types(return_tag),
63
+ types_string: build_types_string(return_tag)
64
+ )
65
+ MethodDefinition.new(
66
+ name: object.name,
67
+ source: "#{object.file}:#{object.line}",
68
+ scope: object.scope,
69
+ visibility: object.visibility,
70
+ parameters: build_parameters(object),
71
+ returns: returns
72
+ )
73
+ when :constant, :classvariable, :proxy
74
+ # Covered by build_vars and build
75
+ else
76
+ raise "Unsupported YARD object: #{object.class}"
77
+ end
78
+ end
79
+
80
+ def build_parameters(object)
81
+ unbound_children = object.tags(:option).each_with_object({}) do |tag, hash|
82
+ index = tag.name.gsub(/:/, '')
83
+ hash[index] ||= [[], []]
84
+ key = build_symbol
85
+ key.metadata[:key] = tag.pair.name.gsub(/:/, '')
86
+ value = build_types(tag.pair)
87
+ value.each { |node| node.metadata[:defaults] = tag.pair.defaults }
88
+ hash[index].first << key
89
+ hash[index].last << value
90
+ end
91
+
92
+ ps = object.parameters.dup
93
+ parameters = object.tags(:param).map do |tag|
94
+ param = ps.find { |name, _| name.gsub(/[*:]/, '') == tag.name }
95
+ next unless param
96
+
97
+ ps.delete(param)
98
+ bound_children = unbound_children.delete(tag.name)
99
+ ParameterDefinition.new(
100
+ name: tag.name.to_sym,
101
+ source: "#{object.file}:#{object.line}",
102
+ default: param.last,
103
+ types: bound_children ? [build_fixed_hash(bound_children)] : build_types(tag),
104
+ types_string: build_types_string(tag)
105
+ )
106
+ end
107
+
108
+ untyped_defaults = ps.reject { |_, default| default.nil? }
109
+ untyped_defaults.each do |name, default|
110
+ parameter = ParameterDefinition.new(
111
+ name: name.to_sym,
112
+ source: "#{object.file}:#{object.line}",
113
+ default: default,
114
+ types: build_types(nil),
115
+ types_string: build_types_string(nil)
116
+ )
117
+ parameters << parameter
118
+ end
119
+
120
+ unbound_children.each do |k, v|
121
+ parameter = ParameterDefinition.new(
122
+ name: k,
123
+ source: "#{object.file}:#{object.line}",
124
+ default: nil,
125
+ types: [build_fixed_hash(v)],
126
+ types_string: 'Hash'
127
+ )
128
+ parameters << parameter
129
+ end
130
+
131
+ parameters
132
+ end
133
+
134
+ def build_inherit_vars(object)
135
+ # Looks at mixins and superclasses to build a full set
136
+ # of (inherited) vars, order-preserved such that the
137
+ # narrowest namespace takes precedence: if class A and
138
+ # B < A define attribute c, the definition of A::B holds
139
+ # pp "build_inherit_vars for #{object}"
140
+ # object.inheritance_tree(true).flat_map do |inherited|
141
+ object.inheritance_tree(true).flat_map do |inherited|
142
+ @object_vars[inherited] ||= build_vars(inherited)
143
+ end.uniq(&:name)
144
+ end
145
+
146
+ def build_vars(object)
147
+ return [] unless %i[class module].include?(object.type)
148
+
149
+ # NOTE: When a module is defined with .self syntax
150
+ # and also referenced with :: syntax, the reference
151
+ # is interpreted as a proxy. You could eventually
152
+ # find the actual code object, but iteratively
153
+ # replacing every :: with .self or vice versa and
154
+ # performing the lookup is not very nice. So, we
155
+ # simply don't propagate in this case.
156
+ return [] if object.is_a? YARD::CodeObjects::Proxy
157
+
158
+ vars = []
159
+ object.cvars.each do |cvar|
160
+ return_tag = cvar.tag(:return)
161
+ vars << VarDefinition.new(
162
+ name: cvar.name,
163
+ source: "#{cvar.file}:#{cvar.line}",
164
+ scope: :class,
165
+ types: build_types(return_tag),
166
+ types_string: build_types_string(return_tag)
167
+ )
168
+ end
169
+ object.constants.each do |const|
170
+ return_tag = const.tag(:return)
171
+ vars << VarDefinition.new(
172
+ name: const.name,
173
+ source: "#{const.file}:#{const.line}",
174
+ scope: :constant,
175
+ types: build_types(return_tag),
176
+ types_string: build_types_string(return_tag)
177
+ )
178
+ end
179
+ object.attributes[:instance].each do |key, value|
180
+ method = value[:read]
181
+ return_tag = method.tag(:return)
182
+ vars << VarDefinition.new(
183
+ name: "@#{key}".to_sym,
184
+ source: "#{method.file}:#{method.line}",
185
+ scope: :instance,
186
+ types: build_types(return_tag),
187
+ types_string: build_types_string(return_tag)
188
+ )
189
+ end
190
+ object.attributes[:class].each do |key, value|
191
+ method = value[:read]
192
+ return_tag = method.tag(:return)
193
+ vars << VarDefinition.new(
194
+ name: key,
195
+ source: "#{method.file}:#{method.line}",
196
+ scope: :self,
197
+ types: build_types(return_tag),
198
+ types_string: build_types_string(return_tag)
199
+ )
200
+ end
201
+
202
+ vars
203
+ end
204
+
205
+ def build_types(tag)
206
+ if tag.respond_to?(:types) && tag.types && !tag.types.empty?
207
+ tag.types.map { |t| Typeguard::TypeModel::Mapper::YardMapper.parse_map(t) }
208
+ else
209
+ result = TypeNode.new(
210
+ kind: :untyped,
211
+ shape: :untyped,
212
+ children: [],
213
+ metadata: { note: 'Types specifier list is empty: untyped' }
214
+ )
215
+ [result]
216
+ end
217
+ end
218
+
219
+ def build_types_string(tag)
220
+ tag.respond_to?(:types) && !tag.types.nil? ? tag.types.join(' or ') : []
221
+ end
222
+
223
+ def build_symbol
224
+ Typeguard::TypeModel::Mapper::YardMapper.parse_map('Symbol')
225
+ end
226
+
227
+ def build_fixed_hash(children)
228
+ node = Typeguard::TypeModel::Mapper::YardMapper.parse_map('Hash')
229
+ node.shape = :fixed_hash
230
+ node.children = children
231
+ node.metadata[:note] = 'Hash specified via @options'
232
+ node
233
+ end
234
+ end
235
+ end
236
+ end
237
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Typeguard
4
+ module TypeModel
5
+ module Builder
6
+ IMPL_SYM = :IMPLEMENTATION
7
+ def self.yard
8
+ require_relative 'builder/yard_builder'
9
+ require_relative 'mapper/yard_mapper'
10
+ const_set(IMPL_SYM, YardBuilder)
11
+ end
12
+
13
+ def self.rbs
14
+ require_relative 'builder/rbs_builder'
15
+ require_relative 'mapper/rbs_mapper'
16
+ const_set(IMPL_SYM, RBSBuilder)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Typeguard
4
+ module TypeModel
5
+ module Definitions
6
+ TypeNode = Struct.new(:kind, :shape, :children, :metadata, keyword_init: true)
7
+ ModuleDefinition = Struct.new(:name, :source, :vars, :type_parameters, :children, keyword_init: true)
8
+ ClassDefinition = Struct.new(:name, :source, :vars, :parent, :type_parameters, :children, keyword_init: true)
9
+ MethodDefinition = Struct.new(:name, :source, :scope, :visibility, :parameters, :returns, keyword_init: true)
10
+ ParameterDefinition = Struct.new(:name, :source, :default, :types, :types_string, keyword_init: true)
11
+ ReturnDefinition = Struct.new(:source, :types, :types_string, keyword_init: true)
12
+ VarDefinition = Struct.new(:name, :source, :scope, :types, :types_string, keyword_init: true)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rbs'
4
+
5
+ module Typeguard
6
+ module TypeModel
7
+ module Mapper
8
+ # Maps RBS types to the generic type model
9
+ class RBSMapper
10
+ include Typeguard::TypeModel::Definitions
11
+
12
+ # @param type [RBS::Types::Base] an RBS type object
13
+ # @return [TypeNode] a mapped type node in the generic model.
14
+ def self.parse_map(type)
15
+ map_rbs(type)
16
+ end
17
+
18
+ def self.map_rbs(node)
19
+ name = (node.respond_to?(:name) ? node.name.relative!.to_s : node.to_s).to_sym
20
+ mapped_node = TypeNode.new(kind: name, shape: :basic, children: [], metadata: {})
21
+
22
+ case node
23
+ when RBS::Types::ClassInstance
24
+ if node.args.empty?
25
+ mapped_node.shape = :basic
26
+ elsif node.name.name == :Hash && node.args.length == 2
27
+ key_node = map_rbs(node.args.first)
28
+ value_node = map_rbs(node.args.last)
29
+ mapped_node.shape = :hash
30
+ mapped_node.children = [[key_node], [value_node]]
31
+ mapped_node.metadata[:note] = 'Hash specified via parametrized types: one key and one value type'
32
+ else
33
+ children = node.args.map { |arg| map_rbs(arg) }
34
+ mapped_node.children = children
35
+ mapped_node.shape = :generic
36
+ end
37
+ when RBS::Types::Tuple
38
+ children = node.types.map { |child| map_rbs(child) }
39
+ mapped_node.kind = :Array
40
+ mapped_node.shape = :fixed
41
+ mapped_node.children = children
42
+ mapped_node.metadata[:note] = 'Tuples are a fixed-length array with known types for each element'
43
+ when RBS::Types::Union
44
+ children = node.types.map { |child| map_rbs(child) }
45
+ mapped_node.shape = :union
46
+ mapped_node.children = children
47
+ mapped_node.metadata[:note] = 'Union types denote a type of one of the given types'
48
+ when RBS::Types::Optional
49
+ child = map_rbs(node.type)
50
+ nil_node = TypeNode.new(kind: :nil, shape: :literal, children: [], metadata: { note: 'Optional nil' })
51
+ mapped_node.shape = :union
52
+ mapped_node.children = [child, nil_node]
53
+ mapped_node.metadata[:note] = 'Optional type'
54
+ when RBS::Types::Bases::Bool
55
+ true_node = TypeNode.new(kind: :TrueClass, shape: :basic, children: [], metadata: {})
56
+ false_node = TypeNode.new(kind: :FalseClass, shape: :basic, children: [], metadata: {})
57
+ mapped_node.kind = :boolean
58
+ mapped_node.shape = :union
59
+ mapped_node.children = [true_node, false_node]
60
+ mapped_node.metadata = { note: 'Boolean represents both TrueClass and FalseClass' }
61
+ when RBS::Types::Bases::Any
62
+ mapped_node.kind = :untyped
63
+ mapped_node.shape = :untyped
64
+ mapped_node.metadata[:note] = 'Type not specified: any'
65
+ when RBS::Types::Bases::Self
66
+ mapped_node.shape = :literal
67
+ mapped_node.metadata[:note] = 'self indicates that calling this method on will return the same type as the type of the receiver'
68
+ when RBS::Types::Bases::Void
69
+ mapped_node.shape = :literal
70
+ mapped_node.metadata[:note] = 'void is only allowed as a return type or a generic parameter'
71
+ when RBS::Types::Bases::Nil
72
+ mapped_node.shape = :literal
73
+ mapped_node.metadata[:note] = 'nil is for nil. nil is recommended over NilClass'
74
+ when RBS::Types::Literal
75
+ mapped_node.shape = :literal
76
+ mapped_node.metadata[:note] = 'Literal types denote a type with only one value of the literal'
77
+ else raise "Unknown node type: #{node.class} #{node}"
78
+ end
79
+ mapped_node
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Typeguard
4
+ module TypeModel
5
+ module Mapper
6
+ # Maps YARD types to the type model
7
+ class YardMapper
8
+ include Typeguard::TypeModel::Definitions
9
+
10
+ SPECIAL_LITERALS = %w[true false nil self void].freeze
11
+
12
+ def self.parse_map(type)
13
+ # NOTE: Using private YARD API here.
14
+ map_yard(YARD::Tags::TypesExplainer::Parser.parse(type).first)
15
+ end
16
+
17
+ def self.map_yard(node)
18
+ mapped_node = TypeNode.new(kind: node.name.to_sym, shape: :basic, children: [], metadata: {})
19
+ case node
20
+ when YARD::Tags::TypesExplainer::FixedCollectionType
21
+ children = node.types.map { |child| map_yard(child) }
22
+ mapped_node.shape = :fixed
23
+ mapped_node.children = children
24
+ mapped_node.metadata[:note] = 'Order-dependent lists must appear in the exact order'
25
+ when YARD::Tags::TypesExplainer::CollectionType
26
+ if node.name == 'Hash' && node.types.length == 2
27
+ key_node = map_yard(node.types.first)
28
+ value_node = map_yard(node.types.last)
29
+ mapped_node.shape = :hash
30
+ mapped_node.children = [[key_node], [value_node]]
31
+ mapped_node.metadata[:note] = 'Hash specified via parametrized types: one key and one value type'
32
+ else
33
+ children = node.types.map { |child| map_yard(child) }
34
+ mapped_node.shape = :generic
35
+ mapped_node.children = children
36
+ mapped_node.metadata[:note] = 'Generics specify one or more parametrized types'
37
+ end
38
+ when YARD::Tags::TypesExplainer::HashCollectionType
39
+ key_nodes = node.key_types.map { |k| map_yard(k) }
40
+ value_nodes = node.value_types.map { |v| map_yard(v) }
41
+ mapped_node.shape = :hash
42
+ mapped_node.children = [key_nodes, value_nodes]
43
+ mapped_node.metadata[:note] = 'Hash specified via rocket syntax: multiple key and value type'
44
+ when YARD::Tags::TypesExplainer::Type
45
+ map_base(node, mapped_node)
46
+ else raise "Unknown node type: #{node.class}"
47
+ end
48
+ mapped_node
49
+ end
50
+
51
+ def self.map_base(node, mapped_node)
52
+ case node.name
53
+ when *SPECIAL_LITERALS
54
+ mapped_node.shape = :literal
55
+ mapped_node.metadata = { note: 'Ruby (and YARD) supported literal' }
56
+ when 'Boolean'
57
+ true_node = TypeNode.new(kind: :TrueClass, shape: :basic, children: [], metadata: {})
58
+ false_node = TypeNode.new(kind: :FalseClass, shape: :basic, children: [], metadata: {})
59
+ mapped_node.kind = :boolean
60
+ mapped_node.shape = :union
61
+ mapped_node.children = [true_node, false_node]
62
+ mapped_node.metadata = { note: 'Boolean represents both TrueClass and FalseClass' }
63
+ when /^#\w+/
64
+ mapped_node.shape = :duck
65
+ mapped_node.metadata = { note: "Duck-type: object should respond to :#{node.name[1..]}" }
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,177 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Typeguard
4
+ module Validation
5
+ class Base
6
+ def self.from(node)
7
+ case node.shape
8
+ when :basic
9
+ Basic.new(node)
10
+ when :generic
11
+ Generic.new(node)
12
+ when :fixed
13
+ Fixed.new(node)
14
+ when :hash
15
+ GenericHash.new(node)
16
+ when :fixed_hash
17
+ FixedHash.new(node)
18
+ when :union
19
+ Union.new(node)
20
+ when :literal
21
+ case node.kind
22
+ when :nil then Nil.new
23
+ when :void, :self then Untyped.new
24
+ else Literal.new(node)
25
+ end
26
+ when :duck
27
+ Duck.new(node)
28
+ when :untyped
29
+ Untyped.new
30
+ else
31
+ raise "Unexpected type node shape: #{node.shape}"
32
+ end
33
+ end
34
+
35
+ def valid?(_value)
36
+ raise NotImplementedError, 'Abstract'
37
+ end
38
+ end
39
+
40
+ class Basic < Base
41
+ def initialize(node)
42
+ @klass = node.metadata[:const]
43
+ end
44
+
45
+ def valid?(value)
46
+ value.is_a?(@klass)
47
+ end
48
+ end
49
+
50
+ class Generic < Base
51
+ def initialize(node)
52
+ @klass = node.metadata[:const]
53
+ @children = node.children.map { |child| Base.from(child) }
54
+ end
55
+
56
+ def valid?(value)
57
+ return false unless value.is_a?(@klass)
58
+
59
+ value.all? do |element|
60
+ @children.any? { |child| child.valid?(element) }
61
+ end
62
+ end
63
+ end
64
+
65
+ class Fixed < Base
66
+ def initialize(node)
67
+ @klass = node.metadata[:const]
68
+ @children = node.children.map { |child| Base.from(child) }
69
+ end
70
+
71
+ def valid?(value)
72
+ return false unless value.is_a?(@klass)
73
+ return false unless value.size == @children.size
74
+
75
+ @children.each_with_index.all? do |child, i|
76
+ child.valid?(value[i])
77
+ end
78
+ end
79
+ end
80
+
81
+ class GenericHash < Base
82
+ def initialize(node)
83
+ @klass = node.metadata[:const]
84
+ @keys = node.children.first.map { |k| Base.from(k) }
85
+ @values = node.children.last.map { |v| Base.from(v) }
86
+ end
87
+
88
+ def valid?(value)
89
+ return false unless value.is_a?(@klass)
90
+
91
+ value.all? do |k, v|
92
+ key_valid = @keys.any? { |child| child.valid?(k) }
93
+ value_valid = @values.any? { |child| child.valid?(v) }
94
+ key_valid && value_valid
95
+ end
96
+ end
97
+ end
98
+
99
+ class FixedHash < Base
100
+ def initialize(node)
101
+ @klass = node.metadata[:const]
102
+ @map = node.children.transpose.each_with_object({}) do |(k, v), h|
103
+ index = k.metadata[:key]
104
+ k_validator = Base.from(k)
105
+ v_validators = v.map { |val| Base.from(val) }
106
+ h[index] = [k_validator, v_validators]
107
+ end
108
+ end
109
+
110
+ def valid?(value)
111
+ return false unless value.is_a?(@klass)
112
+
113
+ value.all? do |k, v|
114
+ index = k.to_s
115
+ return false unless @map.key?(index)
116
+
117
+ k_validator, v_validator = @map[index]
118
+ key_valid = k_validator.valid?(k)
119
+ value_valid = v_validator.any? { |child| child.valid?(v) }
120
+ key_valid && value_valid
121
+ end
122
+ end
123
+ end
124
+
125
+ class Union < Base
126
+ def initialize(node)
127
+ @children = node.children.map { |child| Base.from(child) }
128
+ end
129
+
130
+ def valid?(value)
131
+ @children.any? { |child| child.valid?(value) }
132
+ end
133
+ end
134
+
135
+ class Literal < Base
136
+ def initialize(node)
137
+ @expected = node.kind.to_s
138
+ end
139
+
140
+ def valid?(value)
141
+ value.to_s == @expected
142
+ end
143
+ end
144
+
145
+ class Nil < Base
146
+ def valid?(value)
147
+ value.nil?
148
+ end
149
+ end
150
+
151
+ class Duck < Base
152
+ def initialize(node)
153
+ @name = node.kind[1..]
154
+ end
155
+
156
+ def valid?(value)
157
+ value.respond_to?(@name)
158
+ end
159
+ end
160
+
161
+ class Untyped < Base
162
+ def valid?(_)
163
+ true
164
+ end
165
+ end
166
+
167
+ class UnionOf < Base
168
+ def initialize(children)
169
+ @children = children
170
+ end
171
+
172
+ def valid?(value)
173
+ @children.any? { |v| v.valid?(value) }
174
+ end
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Typeguard
4
+ module Validation
5
+ class Validator
6
+ def self.param_validator(types)
7
+ children = types.map { |node| Base.from(node) }
8
+ if children.size == 1
9
+ children.first
10
+ else
11
+ UnionOf.new(children)
12
+ end
13
+ end
14
+
15
+ def self.zip_params(method_params, sig_params)
16
+ method_params.map do |mp|
17
+ sig_param = sig_params.find { |sp| sp.name.to_s.gsub(/[*:]/, '').to_sym == mp.last }
18
+ validator = param_validator(sig_param.types) if sig_param
19
+ [mp, sig_param, validator]
20
+ end
21
+ end
22
+
23
+ def self.param_names(zipped_params)
24
+ # Tuples of [parameter, invocation] names
25
+ zipped_params.map do |(type, name), sp, _|
26
+ name = name.to_s
27
+ case type
28
+ when :req then [name, name] # foo
29
+ when :keyreq then ["#{name}:", "#{name}: #{name}"] # foo:
30
+ when :keyrest then [name == '**' ? name : "**#{name}"] * 2 # **foo
31
+ when :rest then [name == '*' ? name : "*#{name}"] * 2 # *foo
32
+ when :block then [name == '&' ? name : "&#{name}"] * 2 # &foo
33
+ when :opt then ["#{name} = (#{sp.default})", name] # foo = (bar)
34
+ when :key then ["#{name}: (#{sp.default})", "#{name}: #{name}"] # foo: (bar)
35
+ when :nokey then ['**nil'] * 2 # **nil
36
+ else raise type.to_s
37
+ end
38
+ end
39
+ end
40
+
41
+ def self.exhaustive_path(mod, method, sig)
42
+ zipped_params = zip_params(method.parameters, sig.parameters)
43
+ return_validator = (param_validator(sig.returns.types) if sig.returns && !sig.returns.types.empty?)
44
+ p_names = param_names(zipped_params)
45
+ block_params = p_names.map(&:first).join(', ')
46
+ call_args = p_names.map(&:last).reject { |s| ['*', '**', '&'].include?(s) }.join(', ')
47
+ locals = method.parameters.map do |s|
48
+ return nil if s.size == 1 # Ruby 3.1.0 compatible
49
+
50
+ ['*', '**', '&'].include?(s.last.to_s) ? nil : s.last
51
+ end.compact.join(', ')
52
+ redefinition = sig.scope == :class ? 'define_singleton_method' : 'define_method'
53
+ if return_validator
54
+ mod.module_eval <<~RUBY, __FILE__, __LINE__ + 1
55
+ #{redefinition}(sig.name) do |#{block_params}|
56
+ zipped_params.zip([#{locals}]).each do |(mp, sp, validator), local|
57
+ next if validator.nil? || validator.valid?(local)
58
+
59
+ Metrics.report_unexpected_argument(sig, sp.types_string, local, mod.name, sp)
60
+ end
61
+ result = method.bind_call(self, #{call_args})
62
+ unless return_validator.valid?(result)
63
+ Metrics.report_unexpected_return(sig, sig.returns.types_string, result, mod.name)
64
+ end
65
+ result
66
+ end
67
+ RUBY
68
+ else
69
+ mod.module_eval <<~RUBY, __FILE__, __LINE__ + 1
70
+ #{redefinition}(sig.name) do |#{block_params}|
71
+ zipped_params.zip([#{locals}]).each do |(mp, sp, validator), local|
72
+ next if validator.nil? || validator.valid?(local)
73
+
74
+ Metrics.report_unexpected_argument(sig, sp.types_string, local, mod.name, sp)
75
+ end
76
+ method.bind_call(self, #{call_args})
77
+ end
78
+ RUBY
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Typeguard
4
+ VERSION = '1.0.0'
5
+ end