rtype 0.6.0 → 0.6.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +73 -35
- data/lib/rtype.rb +153 -39
- data/lib/rtype/behavior.rb +1 -0
- data/lib/rtype/behavior/core_ext.rb +10 -0
- data/lib/rtype/behavior/typed_array.rb +25 -0
- data/lib/rtype/core_ext.rb +71 -24
- data/lib/rtype/type_signature.rb +3 -2
- data/lib/rtype/version.rb +1 -1
- data/spec/rtype_spec.rb +37 -3
- data/spec/spec_helper.rb +9 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4edbfe36753db1a9d6a413bf31ccbed37c499df5
|
4
|
+
data.tar.gz: bfa734ba5689e7fca457b247e0f03dbcde507f8e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 60031209128806548c02edc8944e18a103c3f34a2ddca144dd53716050e939d9f7e4baf9ec7d33a3cf70da28ef156b5fcafd8177cddba6f11be6029cf0a4357a
|
7
|
+
data.tar.gz: 5f40a360733043804e02478347ec2a8a3c187b3d7ea943e31d470a7b4727fa4d89770ad93d1e08bb349550ea1a733fc4f2908d0776db4df777567dcc6083b8e1
|
data/README.md
CHANGED
@@ -32,9 +32,9 @@ Test::invert(state: 0)
|
|
32
32
|
## Requirements
|
33
33
|
- Ruby >= 2.1
|
34
34
|
- MRI
|
35
|
-
- If C native extension is used
|
36
|
-
- JRuby
|
37
|
-
- If Java extension is used
|
35
|
+
- If C native extension is used. otherwise it is not required
|
36
|
+
- JRuby (JRuby 9000+)
|
37
|
+
- If Java extension is used. otherwise it is not required
|
38
38
|
|
39
39
|
## Features
|
40
40
|
- Provides type checking for arguments and return
|
@@ -80,52 +80,58 @@ then, Rtype use it. (Do not `require 'rtype-java'`)
|
|
80
80
|
|
81
81
|
### Supported Type Behaviors
|
82
82
|
- `Module`
|
83
|
-
-
|
83
|
+
- A value must be an instance of the module/class or one of its superclasses (`is_a?`)
|
84
84
|
- `Any` : An alias for `BasicObject` (means Any Object)
|
85
85
|
- `Boolean` : `true` or `false`
|
86
86
|
- `Symbol`
|
87
|
-
-
|
87
|
+
- A value must have(respond to) a method with the name
|
88
88
|
- `Regexp`
|
89
|
-
-
|
89
|
+
- A value must match the regexp pattern
|
90
90
|
- `Range`
|
91
|
-
-
|
91
|
+
- A value must be included in the range
|
92
92
|
- `Array`
|
93
|
-
-
|
93
|
+
- A value can be any type in the array
|
94
94
|
- `Hash`
|
95
|
-
-
|
96
|
-
- Each of value’s elements must be valid
|
97
|
-
-
|
95
|
+
- A value must be a hash
|
96
|
+
- Each of the value’s elements must be valid
|
97
|
+
- The value's key list must be equal to the hash's key list
|
98
98
|
- **String** key is **different** from **symbol** key
|
99
|
-
- vs Keyword arguments (e.g.)
|
100
|
-
- `[{}]` is **not** hash type argument. it is keyword argument because its position is last
|
101
|
-
- `[{}, {}]` is empty hash type argument (first) and one empty keyword argument (second)
|
102
|
-
- `[{}, {}, {}]` is two empty hash type argument (first, second) and empty keyword argument (last)
|
99
|
+
- vs. Keyword arguments (e.g.)
|
100
|
+
- `[{}]` is **not** hash type argument. it is keyword argument, because its position is last
|
101
|
+
- `[{}, {}]` is empty hash type argument (first), and one empty keyword argument (second)
|
102
|
+
- `[{}, {}, {}]` is two empty hash type argument (first, second), and empty keyword argument (last)
|
103
103
|
- `{}` is keyword argument. non-keyword arguments must be in array.
|
104
104
|
- Of course, nested hash works
|
105
105
|
- Example: [Hash](#hash)
|
106
106
|
- `Proc`
|
107
|
-
-
|
107
|
+
- A value must return a truthy value for the proc
|
108
108
|
- `true`
|
109
|
-
-
|
109
|
+
- A value must be **truthy**
|
110
110
|
- `false`
|
111
|
-
-
|
111
|
+
- A value must be **falsy**
|
112
112
|
- `nil`
|
113
|
-
-
|
113
|
+
- A value must be nil
|
114
|
+
|
114
115
|
- Special Behaviors
|
115
|
-
- `Rtype::
|
116
|
+
- `Rtype::TypedArray` : Ensures a value is an array with the type (type signature)
|
117
|
+
- `Array::of(type)` (recommended)
|
118
|
+
- `Rtype::Behavior::TypedArray[type]`
|
119
|
+
- Example: [TypedArray](#typed-array)
|
120
|
+
|
121
|
+
- `Rtype::and(*types)` : Ensures a value is valid for all the types
|
116
122
|
- `Rtype::and(*types)`, `Rtype::Behavior::And[*types]`, `include Rtype::Behavior; And[...]`
|
117
123
|
- `Array#comb`
|
118
124
|
- `Object#and(*others)`
|
119
125
|
|
120
|
-
- `Rtype::xor(*types)` :
|
126
|
+
- `Rtype::xor(*types)` : Ensures a value is valid for only one of the types
|
121
127
|
- `Rtype::xor(*types)`, `Rtype::Behavior::Xor[*types]`, `include Rtype::Behavior; Xor[...]`
|
122
128
|
- `Object#xor(*others)`
|
123
129
|
|
124
|
-
- `Rtype::not(*types)` :
|
130
|
+
- `Rtype::not(*types)` : Ensures a value is not valid for all the types
|
125
131
|
- `Rtype::not(*types)`, `Rtype::Behavior::Not[*types]`, `include Rtype::Behavior; Not[...]`
|
126
132
|
- `Object#not`
|
127
133
|
|
128
|
-
- `Rtype::nilable(type)` :
|
134
|
+
- `Rtype::nilable(type)` : Ensures a value can be nil
|
129
135
|
- `Rtype::nilable(type)`, `Rtype::Behavior::Nilable[type]`, `include Rtype::Behavior; Nilable[...]`
|
130
136
|
- `Object#nilable`
|
131
137
|
- `Object#or_nil`
|
@@ -231,29 +237,29 @@ end
|
|
231
237
|
# last hash is keyword arguments
|
232
238
|
func({}, {})
|
233
239
|
# (Rtype::ArgumentTypeError) for 1st argument:
|
234
|
-
# Expected {} to be
|
240
|
+
# Expected {} to be a hash with 1 elements:
|
235
241
|
# - msg : Expected nil to be a String
|
236
242
|
|
237
243
|
func({msg: 123}, {})
|
238
244
|
# (Rtype::ArgumentTypeError) for 1st argument:
|
239
|
-
# Expected {:msg=>123} to be
|
245
|
+
# Expected {:msg=>123} to be a hash with 1 elements:
|
240
246
|
# - msg : Expected 123 to be a String
|
241
247
|
|
242
248
|
func({msg: "hello", key: 'value'}, {})
|
243
249
|
# (Rtype::ArgumentTypeError) for 1st argument:
|
244
|
-
# Expected {:msg=>"hello", :key=>"value"} to be
|
250
|
+
# Expected {:msg=>"hello", :key=>"value"} to be a hash with 1 elements:
|
245
251
|
# - msg : Expected "hello" to be a String
|
246
252
|
|
247
253
|
func({"msg" => "hello hash"}, {})
|
248
254
|
# (Rtype::ArgumentTypeError) for 1st argument:
|
249
|
-
# Expected {"msg"=>"hello hash"} to be
|
255
|
+
# Expected {"msg"=>"hello hash"} to be a hash with 1 elements:
|
250
256
|
# - msg : Expected nil to be a String
|
251
257
|
|
252
258
|
func({msg: "hello hash"}, {}) # hello hash
|
253
259
|
```
|
254
260
|
|
255
261
|
#### rtype with attr_accessor
|
256
|
-
`rtype_accessor` : calls `attr_accessor` if the accessor method(getter/setter) is not defined
|
262
|
+
`rtype_accessor` : calls `attr_accessor` if the accessor method(getter/setter) is not defined. and makes it typed
|
257
263
|
|
258
264
|
You can use `rtype_accessor_self` for static accessor.
|
259
265
|
|
@@ -277,6 +283,38 @@ Example.new.value
|
|
277
283
|
# Expected 456 to be a String
|
278
284
|
```
|
279
285
|
|
286
|
+
#### Typed Array
|
287
|
+
```ruby
|
288
|
+
### TEST 1 ###
|
289
|
+
class Test
|
290
|
+
rtype [Array.of(Integer)] => Any
|
291
|
+
def sum(args)
|
292
|
+
num = 0
|
293
|
+
args.each { |e| num += e }
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
sum([1, 2, 3]) # => 6
|
298
|
+
|
299
|
+
sum([1.0, 2, 3])
|
300
|
+
# (Rtype::ArgumentTypeError) for 1st argument:
|
301
|
+
# Expected [1.0, 2, 3] to be an array with type Integer"
|
302
|
+
```
|
303
|
+
|
304
|
+
```ruby
|
305
|
+
### TEST 2 ###
|
306
|
+
class Test
|
307
|
+
rtype [ Array.of([Integer, Float]) ] => Any
|
308
|
+
def sum(args)
|
309
|
+
num = 0
|
310
|
+
args.each { |e| num += e }
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
sum([1, 2, 3]) # => 6
|
315
|
+
sum([1.0, 2, 3]) # => 6.0
|
316
|
+
```
|
317
|
+
|
280
318
|
#### Combined type
|
281
319
|
```ruby
|
282
320
|
### TEST 1 ###
|
@@ -342,7 +380,7 @@ Game::Player.new.attacks Game::Slime.new
|
|
342
380
|
# Player attacks 'Powerful Slime' (level 123)!
|
343
381
|
```
|
344
382
|
|
345
|
-
#### Position of `rtype` && (
|
383
|
+
#### Position of `rtype` && (specifying method name || annotation mode) && (symbol || string)
|
346
384
|
```ruby
|
347
385
|
require 'rtype'
|
348
386
|
|
@@ -371,7 +409,7 @@ class Example
|
|
371
409
|
puts "Hello? #{i} #{str}"
|
372
410
|
end
|
373
411
|
|
374
|
-
#
|
412
|
+
# Doesn't work. `rtype` works for following (next) method
|
375
413
|
def hello_world_four(i, str)
|
376
414
|
puts "Hello? #{i} #{str}"
|
377
415
|
end
|
@@ -380,7 +418,7 @@ end
|
|
380
418
|
```
|
381
419
|
|
382
420
|
#### Outside of module (root)
|
383
|
-
|
421
|
+
In the outside of module, annotation mode don't works. You must specify method name.
|
384
422
|
|
385
423
|
```ruby
|
386
424
|
rtype :say, [String] => Any
|
@@ -394,7 +432,7 @@ rtype [String] => Any
|
|
394
432
|
# (ArgumentError) Annotation mode not working out of module
|
395
433
|
```
|
396
434
|
|
397
|
-
####
|
435
|
+
#### Class method
|
398
436
|
rtype annotation mode works both instance and class method
|
399
437
|
|
400
438
|
```ruby
|
@@ -410,7 +448,7 @@ end
|
|
410
448
|
Example::say_ya(3) #say ya ya ya
|
411
449
|
```
|
412
450
|
|
413
|
-
|
451
|
+
if you specify method name, however, you must use `rtype_self` instead of `rtype`
|
414
452
|
|
415
453
|
```ruby
|
416
454
|
require 'rtype'
|
@@ -425,7 +463,7 @@ end
|
|
425
463
|
Example::say_ya(3) #say ya ya ya
|
426
464
|
```
|
427
465
|
|
428
|
-
####
|
466
|
+
#### Checking type information
|
429
467
|
This is just the 'information'
|
430
468
|
|
431
469
|
Any change of this doesn't affect type checking
|
@@ -524,7 +562,7 @@ Comparison:
|
|
524
562
|
## Rubype, Sig
|
525
563
|
Rtype is influenced by [Rubype](https://github.com/gogotanaka/Rubype) and [Sig](https://github.com/janlelis/sig).
|
526
564
|
|
527
|
-
If you don't like Rtype, You can use other
|
565
|
+
If you don't like Rtype, You can use other library such as Contracts, Rubype, Rtc, Typecheck, Sig.
|
528
566
|
|
529
567
|
## Author
|
530
568
|
Sputnik Gugja (sputnikgugja@gmail.com)
|
data/lib/rtype.rb
CHANGED
@@ -2,16 +2,16 @@ if defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby"
|
|
2
2
|
begin
|
3
3
|
require 'java'
|
4
4
|
require 'rtype/rtype_java'
|
5
|
-
puts "Rtype with Java extension"
|
5
|
+
# puts "Rtype with Java extension"
|
6
6
|
rescue LoadError
|
7
|
-
puts "Rtype without native extension"
|
7
|
+
# puts "Rtype without native extension"
|
8
8
|
end
|
9
9
|
else
|
10
10
|
begin
|
11
11
|
require "rtype/rtype_native"
|
12
|
-
puts "Rtype with C native extension"
|
12
|
+
# puts "Rtype with C native extension"
|
13
13
|
rescue LoadError
|
14
|
-
puts "Rtype without native extension"
|
14
|
+
# puts "Rtype without native extension"
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
@@ -28,10 +28,18 @@ require_relative 'rtype/behavior'
|
|
28
28
|
module Rtype
|
29
29
|
extend self
|
30
30
|
|
31
|
-
# This is just
|
31
|
+
# This is just 'information'
|
32
32
|
# Any change of this doesn't affect type checking
|
33
33
|
@@type_signatures = Hash.new
|
34
34
|
|
35
|
+
# Makes the method typed
|
36
|
+
# @param owner Owner of the method
|
37
|
+
# @param [#to_sym] method_name
|
38
|
+
# @param [Hash] type_sig_info A type signature. e.g. `[Integer, Float] => Float`
|
39
|
+
# @return [void]
|
40
|
+
#
|
41
|
+
# @raise [ArgumentError] If method_name is nil
|
42
|
+
# @raise [TypeSignatureError] If type_sig_info is invalid
|
35
43
|
def define_typed_method(owner, method_name, type_sig_info)
|
36
44
|
method_name = method_name.to_sym
|
37
45
|
raise ArgumentError, "method_name is nil" if method_name.nil?
|
@@ -64,7 +72,20 @@ module Rtype
|
|
64
72
|
define_typed_method_to_proxy(owner, method_name, expected_args, expected_kwargs, return_sig)
|
65
73
|
end
|
66
74
|
|
75
|
+
# Calls `attr_accessor` if the accessor method(getter/setter) is not defined.
|
76
|
+
# and makes it typed.
|
77
|
+
#
|
78
|
+
# this method uses `define_typed_method` for getter and setter.
|
79
|
+
#
|
80
|
+
# @param owner Owner of the accessor
|
81
|
+
# @param [#to_sym] accessor_name
|
82
|
+
# @param type_behavior A type behavior. e.g. Integer
|
83
|
+
# @return [void]
|
84
|
+
#
|
85
|
+
# @raise [ArgumentError] If accessor_name is nil
|
86
|
+
# @raise [TypeSignatureError]
|
67
87
|
def define_typed_accessor(owner, accessor_name, type_behavior)
|
88
|
+
raise ArgumentError, "accessor_name is nil" if accessor_name.nil?
|
68
89
|
getter = accessor_name.to_sym
|
69
90
|
setter = :"#{accessor_name}="
|
70
91
|
valid?(type_behavior, nil)
|
@@ -72,6 +93,11 @@ module Rtype
|
|
72
93
|
define_typed_method owner, setter, [type_behavior] => Any
|
73
94
|
end
|
74
95
|
|
96
|
+
# This is just 'information'
|
97
|
+
# Any change of this doesn't affect type checking
|
98
|
+
#
|
99
|
+
# @return [Hash]
|
100
|
+
# @note type_signatures[owner][method_name]
|
75
101
|
def type_signatures
|
76
102
|
@@type_signatures
|
77
103
|
end
|
@@ -89,22 +115,44 @@ module Rtype
|
|
89
115
|
end
|
90
116
|
=end
|
91
117
|
|
118
|
+
# @param [Integer] idx
|
119
|
+
# @param expected A type behavior
|
120
|
+
# @param value
|
121
|
+
# @return [String] A error message
|
122
|
+
#
|
123
|
+
# @raise [ArgumentError] If expected is invalid
|
92
124
|
def arg_type_error_message(idx, expected, value)
|
93
125
|
"#{arg_message(idx)}\n" + type_error_message(expected, value)
|
94
126
|
end
|
95
127
|
|
128
|
+
# @param [String, Symbol] key
|
129
|
+
# @param expected A type behavior
|
130
|
+
# @param value
|
131
|
+
# @return [String] A error message
|
132
|
+
#
|
133
|
+
# @raise [ArgumentError] If expected is invalid
|
96
134
|
def kwarg_type_error_message(key, expected, value)
|
97
135
|
"#{kwarg_message(key)}\n" + type_error_message(expected, value)
|
98
136
|
end
|
99
|
-
|
137
|
+
|
138
|
+
# @return [String]
|
100
139
|
def arg_message(idx)
|
101
140
|
"for #{ordinalize_number(idx+1)} argument:"
|
102
141
|
end
|
103
142
|
|
143
|
+
# @return [String]
|
104
144
|
def kwarg_message(key)
|
105
145
|
"for '#{key}' argument:"
|
106
146
|
end
|
107
147
|
|
148
|
+
# Returns a error message for the pair of type behavior and value
|
149
|
+
#
|
150
|
+
# @param expected A type behavior
|
151
|
+
# @param value
|
152
|
+
# @return [String] error message
|
153
|
+
#
|
154
|
+
# @note This method doesn't check the value is valid
|
155
|
+
# @raise [TypeSignatureError] If expected is invalid
|
108
156
|
def type_error_message(expected, value)
|
109
157
|
case expected
|
110
158
|
when Rtype::Behavior::Base
|
@@ -130,9 +178,9 @@ module Rtype
|
|
130
178
|
arr << "- #{k} : " + type_error_message(v, value[k])
|
131
179
|
end
|
132
180
|
end
|
133
|
-
"Expected #{value.inspect} to be
|
181
|
+
"Expected #{value.inspect} to be a hash with #{expected.length} elements:\n" + arr.join("\n")
|
134
182
|
else
|
135
|
-
"Expected #{value.inspect} to be
|
183
|
+
"Expected #{value.inspect} to be a hash"
|
136
184
|
end
|
137
185
|
when Proc
|
138
186
|
"Expected #{value.inspect} to return a truthy value for proc #{expected}"
|
@@ -142,9 +190,19 @@ module Rtype
|
|
142
190
|
"Expected #{value.inspect} to be a falsy value"
|
143
191
|
when nil # for return
|
144
192
|
"Expected #{value.inspect} to be nil"
|
193
|
+
else
|
194
|
+
raise TypeSignatureError, "Invalid type behavior #{expected}"
|
145
195
|
end
|
146
196
|
end
|
147
197
|
|
198
|
+
# Checks the type signature is valid
|
199
|
+
#
|
200
|
+
# e.g.
|
201
|
+
# `[Integer] => Any` is valid.
|
202
|
+
# `[Integer]` or `Any` are invalid
|
203
|
+
#
|
204
|
+
# @param sig A type signature
|
205
|
+
# @raise [TypeSignatureError] If sig is invalid
|
148
206
|
def assert_valid_type_sig(sig)
|
149
207
|
unless sig.is_a?(Hash)
|
150
208
|
raise TypeSignatureError, "Invalid type signature: type signature is not hash"
|
@@ -156,6 +214,14 @@ module Rtype
|
|
156
214
|
assert_valid_return_type_sig(sig.first[1])
|
157
215
|
end
|
158
216
|
|
217
|
+
# Checks the arguments type signature is valid
|
218
|
+
#
|
219
|
+
# e.g.
|
220
|
+
# `[Integer]`, `{key: "value"} are valid.
|
221
|
+
# `Integer` is invalid
|
222
|
+
#
|
223
|
+
# @param sig A arguments type signature
|
224
|
+
# @raise [TypeSignatureError] If sig is invalid
|
159
225
|
def assert_valid_arguments_type_sig(sig)
|
160
226
|
if sig.is_a?(Array)
|
161
227
|
sig = sig.dup
|
@@ -179,6 +245,10 @@ module Rtype
|
|
179
245
|
end
|
180
246
|
end
|
181
247
|
|
248
|
+
# Checks the type behavior is valid
|
249
|
+
#
|
250
|
+
# @param sig A type behavior
|
251
|
+
# @raise [TypeSignatureError] If sig is invalid
|
182
252
|
def assert_valid_argument_type_sig_element(sig)
|
183
253
|
case sig
|
184
254
|
when Rtype::Behavior::Base
|
@@ -203,42 +273,19 @@ module Rtype
|
|
203
273
|
end
|
204
274
|
end
|
205
275
|
|
276
|
+
# @see #assert_valid_argument_type_sig_element
|
206
277
|
def assert_valid_return_type_sig(sig)
|
207
278
|
assert_valid_argument_type_sig_element(sig)
|
208
279
|
end
|
209
|
-
|
210
|
-
private
|
211
|
-
def define_typed_method_to_proxy(owner, method_name, expected_args, expected_kwargs, return_sig)
|
212
|
-
# `send` is faster than `method(...).call`
|
213
|
-
owner.send(:_rtype_proxy).send :define_method, method_name do |*args, **kwargs, &block|
|
214
|
-
if kwargs.empty?
|
215
|
-
::Rtype::assert_arguments_type(expected_args, args)
|
216
|
-
result = super(*args, &block)
|
217
|
-
else
|
218
|
-
::Rtype::assert_arguments_type_with_keywords(expected_args, args, expected_kwargs, kwargs)
|
219
|
-
result = super(*args, **kwargs, &block)
|
220
|
-
end
|
221
|
-
::Rtype::assert_return_type(return_sig, result)
|
222
|
-
result
|
223
|
-
end
|
224
|
-
nil
|
225
|
-
end
|
226
280
|
|
227
|
-
def ordinalize_number(num)
|
228
|
-
if (11..13).include?(num % 100)
|
229
|
-
"#{num}th"
|
230
|
-
else
|
231
|
-
case num % 10
|
232
|
-
when 1; "#{num}st"
|
233
|
-
when 2; "#{num}nd"
|
234
|
-
when 3; "#{num}rd"
|
235
|
-
else "#{num}th"
|
236
|
-
end
|
237
|
-
end
|
238
|
-
end
|
239
|
-
public
|
240
281
|
unless respond_to?(:valid?)
|
241
|
-
#
|
282
|
+
# Checks the value is valid for the type behavior
|
283
|
+
#
|
284
|
+
# @param expected A type behavior
|
285
|
+
# @param value
|
286
|
+
# @return [Boolean]
|
287
|
+
#
|
288
|
+
# @raise [TypeSignatureError] If expected is invalid
|
242
289
|
def valid?(expected, value)
|
243
290
|
case expected
|
244
291
|
when Module
|
@@ -272,6 +319,14 @@ public
|
|
272
319
|
end
|
273
320
|
|
274
321
|
unless respond_to?(:assert_arguments_type)
|
322
|
+
# Validates arguments
|
323
|
+
#
|
324
|
+
# @param [Array] expected_args A type signature for non-keyword arguments
|
325
|
+
# @param args
|
326
|
+
# @return [void]
|
327
|
+
#
|
328
|
+
# @raise [TypeSignatureError] If expected_args is invalid
|
329
|
+
# @raise [ArgumentTypeError] If args is invalid
|
275
330
|
def assert_arguments_type(expected_args, args)
|
276
331
|
e_len = expected_args.length
|
277
332
|
# `length.times` is faster than `each_with_index`
|
@@ -283,10 +338,21 @@ public
|
|
283
338
|
raise ArgumentTypeError, "#{arg_message(i)}\n" + type_error_message(expected, value)
|
284
339
|
end
|
285
340
|
end
|
341
|
+
nil
|
286
342
|
end
|
287
343
|
end
|
288
344
|
|
289
345
|
unless respond_to?(:assert_arguments_type_with_keywords)
|
346
|
+
# Validates arguments and keyword arguments
|
347
|
+
#
|
348
|
+
# @param [Array] expected_args A type signature for non-keyword arguments
|
349
|
+
# @param args Arguments
|
350
|
+
# @param [Hash] expected_kwargs A type signature for keyword arguments
|
351
|
+
# @param kwargs Keword arguments
|
352
|
+
# @return [void]
|
353
|
+
#
|
354
|
+
# @raise [TypeSignatureError] If expected_args or expected_kwargs are invalid
|
355
|
+
# @raise [ArgumentTypeError] If args or kwargs are invalid
|
290
356
|
def assert_arguments_type_with_keywords(expected_args, args, expected_kwargs, kwargs)
|
291
357
|
e_len = expected_args.length
|
292
358
|
# `length.times` is faster than `each_with_index`
|
@@ -307,14 +373,62 @@ public
|
|
307
373
|
end
|
308
374
|
end
|
309
375
|
end
|
376
|
+
nil
|
310
377
|
end
|
311
378
|
end
|
312
379
|
|
380
|
+
# Validates result
|
381
|
+
#
|
382
|
+
# @param expected A type behavior
|
383
|
+
# @param result
|
384
|
+
# @return [void]
|
385
|
+
#
|
386
|
+
# @raise [TypeSignatureError] If expected is invalid
|
387
|
+
# @raise [ReturnTypeError] If result is invalid
|
313
388
|
unless respond_to?(:assert_return_type)
|
314
389
|
def assert_return_type(expected, result)
|
315
390
|
unless valid?(expected, result)
|
316
391
|
raise ReturnTypeError, "for return:\n" + type_error_message(expected, result)
|
317
392
|
end
|
393
|
+
nil
|
318
394
|
end
|
319
395
|
end
|
396
|
+
|
397
|
+
private
|
398
|
+
# @param owner
|
399
|
+
# @param [Symbol] method_name
|
400
|
+
# @param expected_args
|
401
|
+
# @param expected_kwargs
|
402
|
+
# @param return_sig
|
403
|
+
# @return [void]
|
404
|
+
def define_typed_method_to_proxy(owner, method_name, expected_args, expected_kwargs, return_sig)
|
405
|
+
# `send` is faster than `method(...).call`
|
406
|
+
owner.send(:_rtype_proxy).send :define_method, method_name do |*args, **kwargs, &block|
|
407
|
+
if kwargs.empty?
|
408
|
+
::Rtype::assert_arguments_type(expected_args, args)
|
409
|
+
result = super(*args, &block)
|
410
|
+
else
|
411
|
+
::Rtype::assert_arguments_type_with_keywords(expected_args, args, expected_kwargs, kwargs)
|
412
|
+
result = super(*args, **kwargs, &block)
|
413
|
+
end
|
414
|
+
::Rtype::assert_return_type(return_sig, result)
|
415
|
+
result
|
416
|
+
end
|
417
|
+
nil
|
418
|
+
end
|
419
|
+
|
420
|
+
# @param [Integer] num
|
421
|
+
# @return [String]
|
422
|
+
def ordinalize_number(num)
|
423
|
+
if (11..13).include?(num % 100)
|
424
|
+
"#{num}th"
|
425
|
+
else
|
426
|
+
case num % 10
|
427
|
+
when 1; "#{num}st"
|
428
|
+
when 2; "#{num}nd"
|
429
|
+
when 3; "#{num}rd"
|
430
|
+
else "#{num}th"
|
431
|
+
end
|
432
|
+
end
|
433
|
+
end
|
320
434
|
end
|
data/lib/rtype/behavior.rb
CHANGED
@@ -0,0 +1,25 @@
|
|
1
|
+
module Rtype
|
2
|
+
module Behavior
|
3
|
+
class TypedArray < Base
|
4
|
+
def initialize(type)
|
5
|
+
@type = type
|
6
|
+
Rtype.assert_valid_argument_type_sig_element(@type)
|
7
|
+
end
|
8
|
+
|
9
|
+
def valid?(value)
|
10
|
+
if value.is_a?(Array)
|
11
|
+
any = value.any? do |e|
|
12
|
+
!Rtype::valid?(@type, e)
|
13
|
+
end
|
14
|
+
!any
|
15
|
+
else
|
16
|
+
false
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def error_message(value)
|
21
|
+
"Expected #{value.inspect} to be an array with type #{@type.inspect}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/rtype/core_ext.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
+
# true or false
|
1
2
|
module Boolean; end
|
2
3
|
class TrueClass; include Boolean; end
|
3
4
|
class FalseClass; include Boolean; end
|
5
|
+
|
4
6
|
Any = BasicObject
|
5
7
|
|
6
8
|
class Object
|
@@ -17,74 +19,119 @@ private
|
|
17
19
|
@_rtype_proxy
|
18
20
|
end
|
19
21
|
|
22
|
+
# Makes the method typed
|
23
|
+
#
|
24
|
+
# With 'annotation mode', this method works for both instance method and singleton method (class method).
|
25
|
+
# Without it (specifying method name), this method only works for instance method.
|
26
|
+
#
|
27
|
+
# @param [#to_sym, nil] method_name The name of method. If nil, annotation mode works
|
28
|
+
# @param [Hash] type_sig_info A type signature. e.g. [Integer] => Any
|
29
|
+
# @return [void]
|
30
|
+
#
|
31
|
+
# @note Annotation mode doesn't work in the outside of module
|
32
|
+
# @raise [ArgumentError] If method_name is nil in the outside of module
|
33
|
+
# @raise [TypeSignatureError] If type_sig_info is invalid
|
20
34
|
def rtype(method_name=nil, type_sig_info)
|
21
35
|
if is_a?(Module)
|
22
36
|
if method_name.nil?
|
23
37
|
::Rtype::assert_valid_type_sig(type_sig_info)
|
24
38
|
_rtype_proxy.annotation_mode = true
|
25
39
|
_rtype_proxy.annotation_type_sig = type_sig_info
|
40
|
+
nil
|
26
41
|
else
|
27
42
|
::Rtype::define_typed_method(self, method_name, type_sig_info)
|
28
43
|
end
|
29
44
|
else
|
30
45
|
if method_name.nil?
|
31
|
-
raise ArgumentError, "Annotation mode
|
46
|
+
raise ArgumentError, "Annotation mode doesn't work in the outside of module"
|
32
47
|
else
|
33
48
|
rtype_self(method_name, type_sig_info)
|
34
49
|
end
|
35
50
|
end
|
36
51
|
end
|
37
52
|
|
53
|
+
# Makes the singleton method (class method) typed
|
54
|
+
#
|
55
|
+
# @param [#to_sym] method_name
|
56
|
+
# @param [Hash] type_sig_info A type signature. e.g. [Integer] => Any
|
57
|
+
# @return [void]
|
58
|
+
#
|
59
|
+
# @raise [ArgumentError] If method_name is nil
|
60
|
+
# @raise [TypeSignatureError] If type_sig_info is invalid
|
38
61
|
def rtype_self(method_name, type_sig_info)
|
39
62
|
::Rtype.define_typed_method(singleton_class, method_name, type_sig_info)
|
40
63
|
end
|
64
|
+
|
65
|
+
# Makes the accessor methods (getter and setter) typed
|
66
|
+
#
|
67
|
+
# @param [Array<#to_sym>] accessor_names
|
68
|
+
# @param type_behavior A type behavior
|
69
|
+
# @return [void]
|
70
|
+
#
|
71
|
+
# @raise [ArgumentError] If accessor_names contains nil
|
72
|
+
# @raise [TypeSignatureError] If type_behavior is invalid
|
73
|
+
# @see #rtype
|
74
|
+
def rtype_accessor(*accessor_names, type_behavior)
|
75
|
+
accessor_names.each do |accessor_name|
|
76
|
+
accessor_name = accessor_name.to_sym
|
77
|
+
if !respond_to?(accessor_name) || !respond_to?(:"#{accessor_name}=")
|
78
|
+
attr_accessor accessor_name
|
79
|
+
end
|
41
80
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
if is_a?(Module)
|
49
|
-
::Rtype::define_typed_accessor(self, accessor_name, type_behavior)
|
50
|
-
else
|
51
|
-
rtype_accessor_self(accessor_name, type_behavior)
|
81
|
+
if is_a?(Module)
|
82
|
+
::Rtype::define_typed_accessor(self, accessor_name, type_behavior)
|
83
|
+
else
|
84
|
+
rtype_accessor_self(accessor_name, type_behavior)
|
85
|
+
end
|
52
86
|
end
|
87
|
+
nil
|
53
88
|
end
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
89
|
+
|
90
|
+
# Makes the accessor methods (getter and setter) typed
|
91
|
+
#
|
92
|
+
# @param [Array<#to_sym>] accessor_names
|
93
|
+
# @param type_behavior A type behavior
|
94
|
+
# @return [void]
|
95
|
+
#
|
96
|
+
# @raise [ArgumentError] If accessor_names contains nil
|
97
|
+
# @raise [TypeSignatureError] If type_behavior is invalid
|
98
|
+
# @see #rtype_self
|
99
|
+
def rtype_accessor_self(*accessor_names, type_behavior)
|
100
|
+
accessor_names.each do |accessor_name|
|
101
|
+
accessor_name = accessor_name.to_sym
|
102
|
+
if !respond_to?(accessor_name) || !respond_to?(:"#{accessor_name}=")
|
103
|
+
singleton_class.send(:attr_accessor, accessor_name)
|
104
|
+
end
|
105
|
+
::Rtype::define_typed_accessor(singleton_class, accessor_name, type_behavior)
|
59
106
|
end
|
60
|
-
|
107
|
+
nil
|
61
108
|
end
|
62
109
|
end
|
63
110
|
|
64
111
|
class Method
|
112
|
+
# @return [Boolean] Whether the method is typed with rtype
|
65
113
|
def typed?
|
66
114
|
!!::Rtype.type_signatures[owner][name]
|
67
115
|
end
|
68
116
|
|
117
|
+
# @return [TypeSignature]
|
69
118
|
def type_signature
|
70
119
|
::Rtype.type_signatures[owner][name]
|
71
120
|
end
|
72
121
|
|
122
|
+
# @return [Hash]
|
123
|
+
# @see TypeSignature#info
|
73
124
|
def type_info
|
74
125
|
::Rtype.type_signatures[owner][name].info
|
75
126
|
end
|
76
|
-
|
127
|
+
|
128
|
+
# @return [Array, Hash]
|
77
129
|
def argument_type
|
78
130
|
::Rtype.type_signatures[owner][name].argument_type
|
79
131
|
end
|
80
132
|
|
133
|
+
# @return A type behavior
|
81
134
|
def return_type
|
82
135
|
::Rtype.type_signatures[owner][name].return_type
|
83
136
|
end
|
84
137
|
end
|
85
|
-
|
86
|
-
class Array
|
87
|
-
def comb
|
88
|
-
::Rtype::Behavior::And[*self]
|
89
|
-
end
|
90
|
-
end
|
data/lib/rtype/type_signature.rb
CHANGED
data/lib/rtype/version.rb
CHANGED
data/spec/rtype_spec.rb
CHANGED
@@ -150,24 +150,30 @@ describe Rtype do
|
|
150
150
|
|
151
151
|
it 'Kernel#rtype_accessor' do
|
152
152
|
class TestClass
|
153
|
-
rtype_accessor :value, String
|
153
|
+
rtype_accessor :value, :value2, String
|
154
154
|
|
155
155
|
def initialize
|
156
156
|
@value = 123
|
157
|
+
@value2 = 123
|
157
158
|
end
|
158
159
|
end
|
159
160
|
expect {TestClass.new.value = 123}.to raise_error Rtype::ArgumentTypeError
|
160
161
|
expect {TestClass.new.value}.to raise_error Rtype::ReturnTypeError
|
162
|
+
expect {TestClass.new.value2 = 123}.to raise_error Rtype::ArgumentTypeError
|
163
|
+
expect {TestClass.new.value2}.to raise_error Rtype::ReturnTypeError
|
161
164
|
end
|
162
165
|
|
163
166
|
it 'Kernel#rtype_accessor_self' do
|
164
167
|
class TestClass
|
165
|
-
@@
|
168
|
+
@@value = 123
|
169
|
+
@@value2 = 123
|
166
170
|
|
167
|
-
rtype_accessor_self :value, String
|
171
|
+
rtype_accessor_self :value, :value2, String
|
168
172
|
end
|
169
173
|
expect {TestClass::value = 123}.to raise_error Rtype::ArgumentTypeError
|
170
174
|
expect {TestClass::value}.to raise_error Rtype::ReturnTypeError
|
175
|
+
expect {TestClass::value2 = 123}.to raise_error Rtype::ArgumentTypeError
|
176
|
+
expect {TestClass::value2}.to raise_error Rtype::ReturnTypeError
|
171
177
|
end
|
172
178
|
|
173
179
|
describe 'Test type behaviors' do
|
@@ -440,6 +446,34 @@ describe Rtype do
|
|
440
446
|
expect {instance.return_nil("abc")}.to raise_error Rtype::ArgumentTypeError
|
441
447
|
end
|
442
448
|
end
|
449
|
+
|
450
|
+
describe 'Rtype::Behavior::TypedArray' do
|
451
|
+
it 'class singleton [] method' do
|
452
|
+
klass.send :rtype, :return_nil, [ Rtype::Behavior::TypedArray[Integer] ] => nil
|
453
|
+
instance.return_nil([123])
|
454
|
+
expect {instance.return_nil(123)}.to raise_error Rtype::ArgumentTypeError
|
455
|
+
expect {instance.return_nil([1.0])}.to raise_error Rtype::ArgumentTypeError
|
456
|
+
end
|
457
|
+
|
458
|
+
it 'core extension method (Array::of)' do
|
459
|
+
klass.send :rtype, :return_nil, [ Array.of(Integer) ] => nil
|
460
|
+
instance.return_nil([123])
|
461
|
+
expect {instance.return_nil(123)}.to raise_error Rtype::ArgumentTypeError
|
462
|
+
expect {instance.return_nil([1.0])}.to raise_error Rtype::ArgumentTypeError
|
463
|
+
end
|
464
|
+
|
465
|
+
it 'complicated type sig' do
|
466
|
+
klass.send :rtype, :return_nil, [ Array.of(:to_i.and(:chars)) ] => nil
|
467
|
+
instance.return_nil(["hello"])
|
468
|
+
expect {instance.return_nil("hello")}.to raise_error Rtype::ArgumentTypeError
|
469
|
+
expect {instance.return_nil([123])}.to raise_error Rtype::ArgumentTypeError
|
470
|
+
end
|
471
|
+
|
472
|
+
it 'allows empty array' do
|
473
|
+
klass.send :rtype, :return_nil, [ Array.of(Integer) ] => nil
|
474
|
+
instance.return_nil([])
|
475
|
+
end
|
476
|
+
end
|
443
477
|
end
|
444
478
|
end
|
445
479
|
|
data/spec/spec_helper.rb
CHANGED
@@ -2,4 +2,12 @@ require 'coveralls'
|
|
2
2
|
Coveralls.wear!
|
3
3
|
|
4
4
|
require 'rtype'
|
5
|
-
require 'rspec'
|
5
|
+
require 'rspec'
|
6
|
+
|
7
|
+
if !Rtype::NATIVE_EXT_VERSION.nil?
|
8
|
+
puts "Rtype with native extension"
|
9
|
+
elsif !Rtype::JAVA_EXT_VERSION.nil?
|
10
|
+
puts "Rtype with java extension"
|
11
|
+
else
|
12
|
+
puts "Rtype without native extension"
|
13
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rtype
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sputnik Gugja
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-07-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -72,6 +72,7 @@ files:
|
|
72
72
|
- lib/rtype/behavior/core_ext.rb
|
73
73
|
- lib/rtype/behavior/nilable.rb
|
74
74
|
- lib/rtype/behavior/not.rb
|
75
|
+
- lib/rtype/behavior/typed_array.rb
|
75
76
|
- lib/rtype/behavior/xor.rb
|
76
77
|
- lib/rtype/core_ext.rb
|
77
78
|
- lib/rtype/method_annotator.rb
|