rtype 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,11 @@
1
+ module Rtype
2
+ module Behavior
3
+ end
4
+ end
5
+
6
+ require_relative 'behavior/base'
7
+ require_relative 'behavior/or'
8
+ require_relative 'behavior/and'
9
+ require_relative 'behavior/xor'
10
+ require_relative 'behavior/not'
11
+ require_relative 'behavior/nilable'
@@ -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
@@ -0,0 +1,4 @@
1
+ module Rtype
2
+ class ReturnTypeError < StandardError
3
+ end
4
+ end
@@ -0,0 +1,9 @@
1
+ module Rtype
2
+ class TypeSignature
3
+ attr_accessor :argument_type, :return_type
4
+
5
+ def info
6
+ {argument_type => return_type}
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,4 @@
1
+ module Rtype
2
+ class TypeSignatureError < ArgumentError
3
+ end
4
+ end
@@ -0,0 +1,3 @@
1
+ module Rtype
2
+ VERSION = "0.0.2".freeze
3
+ 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