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.
@@ -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