rtype 0.0.2
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.
- checksums.yaml +7 -0
- data/Gemfile +3 -0
- data/LICENSE +9 -0
- data/README.md +406 -0
- data/Rakefile +12 -0
- data/benchmark/benchmark.rb +157 -0
- data/lib/rtype/argument_type_error.rb +4 -0
- data/lib/rtype/behavior/and.rb +24 -0
- data/lib/rtype/behavior/base.rb +17 -0
- data/lib/rtype/behavior/nilable.rb +21 -0
- data/lib/rtype/behavior/not.rb +24 -0
- data/lib/rtype/behavior/or.rb +24 -0
- data/lib/rtype/behavior/xor.rb +25 -0
- data/lib/rtype/behavior.rb +11 -0
- data/lib/rtype/core_ext.rb +60 -0
- data/lib/rtype/return_type_error.rb +4 -0
- data/lib/rtype/type_signature.rb +9 -0
- data/lib/rtype/type_signature_error.rb +4 -0
- data/lib/rtype/version.rb +3 -0
- data/lib/rtype.rb +172 -0
- data/spec/rtype_spec.rb +408 -0
- data/spec/spec_helper.rb +3 -0
- metadata +95 -0
@@ -0,0 +1,24 @@
|
|
1
|
+
module Rtype
|
2
|
+
module Behavior
|
3
|
+
class Or < Base
|
4
|
+
def initialize(*types)
|
5
|
+
@types = types
|
6
|
+
end
|
7
|
+
|
8
|
+
def valid?(value)
|
9
|
+
@types.any? do |e|
|
10
|
+
Rtype::valid? e, value
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def error_message(value)
|
15
|
+
arr = @types.map { |e| Rtype::type_error_message(e, value) }
|
16
|
+
arr.join "\nOR "
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.or(*args)
|
22
|
+
Behavior::Or[*args]
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Rtype
|
2
|
+
module Behavior
|
3
|
+
class Xor < Base
|
4
|
+
def initialize(*types)
|
5
|
+
@types = types
|
6
|
+
end
|
7
|
+
|
8
|
+
def valid?(value)
|
9
|
+
result = @types.map do |e|
|
10
|
+
Rtype::valid? e, value
|
11
|
+
end
|
12
|
+
result.count(true) == 1
|
13
|
+
end
|
14
|
+
|
15
|
+
def error_message(value)
|
16
|
+
arr = @types.map { |e| Rtype::type_error_message(e, value) }
|
17
|
+
arr.join "\nXOR "
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.xor(*args)
|
23
|
+
Behavior::Xor[*args]
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Boolean; end
|
2
|
+
class TrueClass; include Boolean; end
|
3
|
+
class FalseClass; include Boolean; end
|
4
|
+
Any = BasicObject
|
5
|
+
|
6
|
+
module Kernel
|
7
|
+
private
|
8
|
+
def _rtype_proxy
|
9
|
+
unless @_rtype_proxy
|
10
|
+
@_rtype_proxy = Module.new
|
11
|
+
prepend @_rtype_proxy
|
12
|
+
end
|
13
|
+
@_rtype_proxy
|
14
|
+
end
|
15
|
+
|
16
|
+
def rtype(method_name, type_sig_info)
|
17
|
+
if is_a?(Module)
|
18
|
+
::Rtype.define_typed_method(self, method_name, type_sig_info)
|
19
|
+
else
|
20
|
+
rtype_self(method_name, type_sig_info)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def rtype_self(method_name, type_sig_info)
|
25
|
+
::Rtype.define_typed_method(singleton_class, method_name, type_sig_info)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class Method
|
30
|
+
def type_signature
|
31
|
+
::Rtype.type_signatures[owner][name]
|
32
|
+
end
|
33
|
+
|
34
|
+
def type_info
|
35
|
+
type_signature.info
|
36
|
+
end
|
37
|
+
|
38
|
+
def argument_type
|
39
|
+
type_signature.argument_type
|
40
|
+
end
|
41
|
+
|
42
|
+
def return_type
|
43
|
+
type_signature.return_type
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class Fixnum
|
48
|
+
def ordinalize
|
49
|
+
if (11..13).include?(self % 100)
|
50
|
+
"#{self}th"
|
51
|
+
else
|
52
|
+
case self % 10
|
53
|
+
when 1; "#{self}st"
|
54
|
+
when 2; "#{self}nd"
|
55
|
+
when 3; "#{self}rd"
|
56
|
+
else "#{self}th"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/rtype.rb
ADDED
@@ -0,0 +1,172 @@
|
|
1
|
+
require_relative 'rtype/core_ext'
|
2
|
+
require_relative 'rtype/version'
|
3
|
+
require_relative 'rtype/type_signature_error'
|
4
|
+
require_relative 'rtype/argument_type_error'
|
5
|
+
require_relative 'rtype/return_type_error'
|
6
|
+
require_relative 'rtype/type_signature'
|
7
|
+
require_relative 'rtype/behavior'
|
8
|
+
|
9
|
+
module Rtype
|
10
|
+
# This is just the 'information'
|
11
|
+
# Any change of this doesn't affect type checking
|
12
|
+
@@type_signatures = Hash.new({})
|
13
|
+
|
14
|
+
class << self
|
15
|
+
def define_typed_method(owner, method_name, type_sig_info)
|
16
|
+
raise TypeSignatureError, "Invalid type signature" unless valid_type_sig_info_form?(type_sig_info)
|
17
|
+
method_name = method_name.to_sym
|
18
|
+
raise ArgumentError, "method_name is nil" if method_name.nil?
|
19
|
+
|
20
|
+
el = type_sig_info.first
|
21
|
+
arg_sig = el[0]
|
22
|
+
return_sig = el[1]
|
23
|
+
|
24
|
+
if arg_sig.is_a?(Array)
|
25
|
+
expected_args = arg_sig
|
26
|
+
if expected_args.last.is_a?(Hash)
|
27
|
+
expected_kwargs = expected_args.pop
|
28
|
+
else
|
29
|
+
expected_kwargs = {}
|
30
|
+
end
|
31
|
+
elsif arg_sig.is_a?(Hash)
|
32
|
+
expected_args = []
|
33
|
+
expected_kwargs = arg_sig
|
34
|
+
end
|
35
|
+
|
36
|
+
expected_args.each { |e| valid?(e, nil) }
|
37
|
+
if expected_kwargs.keys.any? { |e| !e.is_a?(Symbol) }
|
38
|
+
raise TypeSignatureError, "Invalid type signature: keyword arguments contain non-symbol key"
|
39
|
+
end
|
40
|
+
expected_kwargs.each_value { |e| valid?(e, nil) }
|
41
|
+
valid?(return_sig, nil) unless return_sig.nil?
|
42
|
+
|
43
|
+
sig = TypeSignature.new
|
44
|
+
sig.argument_type = arg_sig
|
45
|
+
sig.return_type = return_sig
|
46
|
+
@@type_signatures[owner][method_name] = sig
|
47
|
+
|
48
|
+
owner.method(:_rtype_proxy).call.send :define_method, method_name do |*args, **kwargs, &block|
|
49
|
+
if kwargs.empty?
|
50
|
+
::Rtype.assert_arguments_type(expected_args, args)
|
51
|
+
result = super(*args, &block)
|
52
|
+
else
|
53
|
+
::Rtype.assert_arguments_type(expected_args, args)
|
54
|
+
::Rtype.assert_keyword_arguments_type(expected_kwargs, kwargs)
|
55
|
+
result = super(*args, **kwargs, &block)
|
56
|
+
end
|
57
|
+
::Rtype.assert_return_type(return_sig, result)
|
58
|
+
result
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# validate argument type
|
63
|
+
def valid?(expected, value)
|
64
|
+
case expected
|
65
|
+
when Rtype::Behavior::Base
|
66
|
+
expected.valid? value
|
67
|
+
when Module
|
68
|
+
value.is_a? expected
|
69
|
+
when Symbol
|
70
|
+
value.respond_to? expected
|
71
|
+
when Regexp
|
72
|
+
!!(expected =~ value.to_s)
|
73
|
+
when Range
|
74
|
+
expected.include?(value)
|
75
|
+
when Array
|
76
|
+
return false unless value.is_a?(Array)
|
77
|
+
return false unless expected.length == value.length
|
78
|
+
idx = -1
|
79
|
+
expected.all? { |e| idx += 1; valid?(e, value[idx]) }
|
80
|
+
when Proc
|
81
|
+
!!expected.call(value)
|
82
|
+
when true
|
83
|
+
!!value
|
84
|
+
when false
|
85
|
+
!value
|
86
|
+
else
|
87
|
+
raise TypeSignatureError, "Invalid type signature: Unknown type behavior #{expected}"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def type_signatures
|
92
|
+
@@type_signatures
|
93
|
+
end
|
94
|
+
|
95
|
+
def assert_arguments_type(expected_args, args)
|
96
|
+
args.each_with_index do |value, idx|
|
97
|
+
expected = expected_args[idx]
|
98
|
+
unless expected.nil?
|
99
|
+
unless valid?(expected, value)
|
100
|
+
raise ArgumentTypeError, "for #{(idx+1).ordinalize} argument:\n" + type_error_message(expected, value)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def assert_keyword_arguments_type(expected_kwargs, kwargs)
|
107
|
+
kwargs.each do |key, value|
|
108
|
+
expected = expected_kwargs[key]
|
109
|
+
unless expected.nil?
|
110
|
+
unless valid?(expected, value)
|
111
|
+
raise ArgumentTypeError, "for '#{key}' argument:\n" + type_error_message(expected, value)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def assert_return_type(expected, result)
|
118
|
+
if expected.nil?
|
119
|
+
unless result.nil?
|
120
|
+
raise ReturnTypeError, "for return:\n" + type_error_message(expected, result)
|
121
|
+
end
|
122
|
+
else
|
123
|
+
unless valid?(expected, result)
|
124
|
+
raise ReturnTypeError, "for return:\n" + type_error_message(expected, result)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def type_error_message(expected, value)
|
130
|
+
case expected
|
131
|
+
when Rtype::Behavior::Base
|
132
|
+
expected.error_message(value)
|
133
|
+
when Module
|
134
|
+
"Expected #{value.inspect} to be a #{expected}"
|
135
|
+
when Symbol
|
136
|
+
"Expected #{value.inspect} to respond to :#{expected}"
|
137
|
+
when Regexp
|
138
|
+
"Expected stringified #{value.inspect} to match regexp #{expected.inspect}"
|
139
|
+
when Range
|
140
|
+
"Expected #{value.inspect} to be included in range #{expected.inspect}"
|
141
|
+
when Array
|
142
|
+
if value.is_a?(Array)
|
143
|
+
arr = expected.map.with_index do |e, idx|
|
144
|
+
if e.is_a?(Array)
|
145
|
+
"- [#{idx}] index : {\n" + type_error_message(e, value[idx]) + "\n}"
|
146
|
+
else
|
147
|
+
"- [#{idx}] index : " + type_error_message(e, value[idx])
|
148
|
+
end
|
149
|
+
end
|
150
|
+
"Expected #{value.inspect} to be an array with #{expected.length} elements:\n" + arr.join("\n")
|
151
|
+
else
|
152
|
+
"Expected #{value.inspect} to be an array"
|
153
|
+
end
|
154
|
+
when Proc
|
155
|
+
"Expected #{value.inspect} to return a truthy value for proc #{expected}"
|
156
|
+
when true
|
157
|
+
"Expected #{value.inspect} to be a truthy value"
|
158
|
+
when false
|
159
|
+
"Expected #{value.inspect} to be a falsy value"
|
160
|
+
when nil # for return
|
161
|
+
"Expected #{value.inspect} to be nil"
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
private
|
166
|
+
def valid_type_sig_info_form?(hash)
|
167
|
+
return false unless hash.is_a?(Hash)
|
168
|
+
arg_sig = hash.first[0]
|
169
|
+
arg_sig.is_a?(Array) || arg_sig.is_a?(Hash)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|