rubype 0.2.5 → 0.2.6
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 +4 -4
- data/README.md +35 -14
- data/lib/rubype.rb +72 -90
- data/lib/rubype/version.rb +1 -1
- data/test/test_rubype.rb +2 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 942f53bc7020fb73e9af0158cd6c5d75fa7476f7
|
4
|
+
data.tar.gz: e1fe176b85235b86007b967c040d5971a3851093
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5c60d752bd40eedcb3a85163d3c92d091f2a2e5e0098dd35afd61412a42cbec86416b67bec1cff60fa087b20b8eccabf9f413294e179dff9fed2d1dfe7ab45c0
|
7
|
+
data.tar.gz: f62bfe664a071c7f5c86490f708b483da6b5e837530cfc843f388c50938b7c0e61bd99f76f7fc2acb1f595acb292158d12ff0277e3a9fe410240a19fce2be064
|
data/README.md
CHANGED
@@ -4,17 +4,20 @@
|
|
4
4
|
|
5
5
|

|
6
6
|
```rb
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
require 'rubype'
|
8
|
+
class MyClass
|
9
|
+
# Assert first arg has method #to_i, second arg and return value are instance of Numeric.
|
10
|
+
def sum(x, y)
|
11
|
+
x.to_i + y
|
12
|
+
end
|
13
|
+
typesig :sum, [:to_i, Numeric] => Numeric
|
10
14
|
end
|
11
|
-
typesig :sum, [Numeric, Numeric] => String
|
12
15
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
16
|
+
MyClass.new.sum(:has_no_to_i, 2)
|
17
|
+
#=> Rubype::ArgumentTypeError: for MyClass#sum's 1st argument
|
18
|
+
# Expected: respond to :to_i,
|
19
|
+
# Actual: :has_no_to_i
|
20
|
+
# ...(stack trace)
|
18
21
|
```
|
19
22
|
|
20
23
|
|
@@ -31,6 +34,13 @@ This gem brings you advantage of type without changing existing code's behavior.
|
|
31
34
|
* There is no static analysis.
|
32
35
|
|
33
36
|
# Feature
|
37
|
+
### Super clean!!!
|
38
|
+
I know it's terrible to run your important code with such a hacked gem.
|
39
|
+
But this contract is implemented by less than 80 lines source code!
|
40
|
+
(It is not too little, works well enough)
|
41
|
+
https://github.com/gogotanaka/Rubype/blob/develop/lib/rubype.rb#L2-L79
|
42
|
+
|
43
|
+
You can read this over easily and even you can implement by yourself !(Don't need to use this gem, just take idea)
|
34
44
|
|
35
45
|
### Advantage of type
|
36
46
|
* Meaningful error
|
@@ -57,10 +67,16 @@ MyClass.new.sum(1, 2)
|
|
57
67
|
#=> 3
|
58
68
|
|
59
69
|
MyClass.new.sum(1, 'string')
|
60
|
-
#=> Rubype::ArgumentTypeError:
|
70
|
+
#=> Rubype::ArgumentTypeError: for MyClass#sum's 2nd argument
|
71
|
+
# Expected: Numeric,
|
72
|
+
# Actual: "string"
|
73
|
+
# ...(stack trace)
|
61
74
|
|
62
75
|
MyClass.new.wrong_sum(1, 2)
|
63
|
-
#=> Rubype::ReturnTypeError:
|
76
|
+
#=> Rubype::ReturnTypeError: for MyClass#wrong_sum's return
|
77
|
+
# Expected: Numeric,
|
78
|
+
# Actual: "string"
|
79
|
+
# ...(stack trace)
|
64
80
|
|
65
81
|
|
66
82
|
# ex2: Assert object has specified method
|
@@ -75,7 +91,10 @@ MyClass.new.sum('1', 2)
|
|
75
91
|
#=> 3
|
76
92
|
|
77
93
|
MyClass.new.sum(:has_no_to_i, 2)
|
78
|
-
#=> Rubype::ArgumentTypeError:
|
94
|
+
#=> Rubype::ArgumentTypeError: for MyClass#sum's 1st argument
|
95
|
+
# Expected: respond to :to_i,
|
96
|
+
# Actual: :has_no_to_i
|
97
|
+
# ...(stack trace)
|
79
98
|
|
80
99
|
|
81
100
|
# ex3: You can use Any class, if you want
|
@@ -90,7 +109,10 @@ People.new.marry(People.new)
|
|
90
109
|
#=> no error
|
91
110
|
|
92
111
|
People.new.marry('non people')
|
93
|
-
#=> Rubype::ArgumentTypeError:
|
112
|
+
#=> Rubype::ArgumentTypeError: for People#marry's 1st argument
|
113
|
+
# Expected: People,
|
114
|
+
# Actual: "non people"
|
115
|
+
# ...(stack trace)
|
94
116
|
|
95
117
|
```
|
96
118
|
|
@@ -154,7 +176,6 @@ MyClass.new.method(:sum).arg_types
|
|
154
176
|
|
155
177
|
MyClass.new.method(:sum).return_type
|
156
178
|
# => Numeric
|
157
|
-
|
158
179
|
```
|
159
180
|
|
160
181
|
## Benchmarks
|
data/lib/rubype.rb
CHANGED
@@ -1,122 +1,104 @@
|
|
1
1
|
require 'rubype/ordinal'
|
2
|
-
|
3
|
-
# Builtin Contracts
|
4
|
-
class Any; end
|
5
|
-
module Boolean; end
|
6
|
-
TrueClass.send(:include, Boolean)
|
7
|
-
FalseClass.send(:include, Boolean)
|
8
|
-
|
2
|
+
# === Rubype core === #
|
9
3
|
class Module
|
10
4
|
private
|
11
|
-
# @return [Module]
|
12
5
|
def __rubype__
|
13
6
|
prepend (@__rubype__ = Module.new) unless @__rubype__
|
14
7
|
@__rubype__
|
15
8
|
end
|
9
|
+
# typesig :__rubype__, [] => Rubype
|
16
10
|
|
17
|
-
# @param meth [Symbol]
|
18
|
-
# @param type_info_hash [Hash] { [ArgInfo_1, ArgInfo_2, ... ArgInfo_n] => RtnInfo }
|
19
|
-
# @return self
|
20
11
|
def typesig(meth, type_info_hash)
|
21
|
-
::Rubype.
|
12
|
+
::Rubype.define_typed_method(self, meth, type_info_hash, __rubype__)
|
22
13
|
self
|
23
14
|
end
|
24
|
-
|
25
|
-
|
26
|
-
class Method
|
27
|
-
# @return [Hash]: { [ArgInfo_1, ArgInfo_2, ... ArgInfo_n] => RtnInfo }
|
28
|
-
def type_info
|
29
|
-
if methods_hash = Rubype.typed_method_info[owner]
|
30
|
-
methods_hash[name]
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
# @return [Array<Class, Symbol>]: [ArgInfo_1, ArgInfo_2, ... ArgInfo_n]
|
35
|
-
def arg_types
|
36
|
-
type_info.first.first if type_info
|
37
|
-
end
|
38
|
-
|
39
|
-
# @return [Class, Symbol]: RtnInfo
|
40
|
-
def return_type
|
41
|
-
type_info.first.last if type_info
|
42
|
-
end
|
15
|
+
# typesig :typesig, [Symbol, Hash] => Module
|
43
16
|
end
|
44
17
|
|
45
18
|
module Rubype
|
19
|
+
@@typed_method_info = Hash.new({})
|
46
20
|
class ArgumentTypeError < ::TypeError; end
|
47
21
|
class ReturnTypeError < ::TypeError; end
|
48
|
-
@@typed_method_info = Hash.new({})
|
49
|
-
|
50
22
|
class << self
|
51
|
-
def
|
52
|
-
|
53
|
-
end
|
23
|
+
def define_typed_method(owner, meth, type_info_hash, __rubype__)
|
24
|
+
arg_types, rtn_type = *type_info_hash.first
|
54
25
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
@@typed_method_info[owner][meth] = {
|
62
|
-
arg_types => rtn_type
|
63
|
-
}
|
64
|
-
__rubype__.send(:define_method, meth) do |*args, &block|
|
65
|
-
::Rubype.send(:assert_arg_type, self, meth, args, arg_types)
|
66
|
-
rtn = super(*args, &block)
|
67
|
-
::Rubype.send(:assert_trn_type, self, meth, rtn, rtn_type)
|
68
|
-
rtn
|
26
|
+
@@typed_method_info[owner][meth] = { arg_types => rtn_type }
|
27
|
+
|
28
|
+
__rubype__.send(:define_method, meth) do |*args, &block|
|
29
|
+
::Rubype.assert_arg_type(self, meth, args, arg_types, caller)
|
30
|
+
super(*args, &block).tap do |rtn|
|
31
|
+
::Rubype.assert_rtn_type(self, meth, rtn, rtn_type, caller)
|
69
32
|
end
|
70
33
|
end
|
34
|
+
end
|
71
35
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
36
|
+
def assert_arg_type(meth_caller, meth, args, type_infos, caller_trace)
|
37
|
+
args.zip(type_infos).each.with_index(1) do |(arg, type_info), i|
|
38
|
+
unless match_type?(arg, type_info)
|
39
|
+
raise ArgumentTypeError,
|
40
|
+
error_mes("#{meth_caller.class}##{meth}'s #{i}#{ordinal(i)} argument", type_info, arg, caller_trace)
|
41
|
+
end
|
77
42
|
end
|
43
|
+
end
|
78
44
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
def assert_arg_type(meth_caller, meth, args, type_infos)
|
84
|
-
args.zip(type_infos).each.with_index(1) do |(arg, type_info), i|
|
85
|
-
case type_check(arg, type_info)
|
86
|
-
when :need_correct_class
|
87
|
-
raise ArgumentTypeError,
|
88
|
-
"Expected #{meth_caller.class}##{meth}'s #{i}#{ordinal(i)} argument to be #{type_info} but got #{arg.inspect} instead"
|
89
|
-
when :need_correct_method
|
90
|
-
raise ArgumentTypeError,
|
91
|
-
"Expected #{meth_caller.class}##{meth}'s #{i}#{ordinal(i)} argument to have method ##{type_info} but got #{arg.inspect} instead"
|
92
|
-
end
|
93
|
-
end
|
45
|
+
def assert_rtn_type(meth_caller, meth, rtn, type_info, caller_trace)
|
46
|
+
unless match_type?(rtn, type_info)
|
47
|
+
raise ReturnTypeError,
|
48
|
+
error_mes("#{meth_caller.class}##{meth}'s return", type_info, rtn, caller_trace)
|
94
49
|
end
|
50
|
+
end
|
95
51
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
when
|
105
|
-
raise ReturnTypeError,
|
106
|
-
"Expected #{meth_caller.class}##{meth} to return object which has method ##{type_info} but got #{rtn.inspect} instead"
|
52
|
+
def typed_method_info
|
53
|
+
@@typed_method_info
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
def match_type?(obj, type_info)
|
58
|
+
case type_info
|
59
|
+
when Module; (obj.is_a?(type_info) || type_info == Any)
|
60
|
+
when Symbol; (obj.respond_to?(type_info))
|
107
61
|
end
|
108
62
|
end
|
109
63
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
case type_info
|
115
|
-
when Module
|
116
|
-
:need_correct_class unless (obj.is_a?(type_info) || type_info == Any)
|
117
|
-
when Symbol
|
118
|
-
:need_correct_method unless (obj.respond_to?(type_info))
|
64
|
+
def error_mes(target, expected, actual, caller_trace)
|
65
|
+
expected_mes = case expected
|
66
|
+
when Module; expected
|
67
|
+
when Symbol; "respond to :#{expected}"
|
119
68
|
end
|
69
|
+
<<-ERROR_MES
|
70
|
+
for #{target}
|
71
|
+
Expected: #{expected_mes},
|
72
|
+
Actual: #{actual.inspect}
|
73
|
+
|
74
|
+
#{caller_trace.join("\n")}
|
75
|
+
ERROR_MES
|
120
76
|
end
|
121
77
|
end
|
122
78
|
end
|
79
|
+
# === end (only 79 lines :D)=== #
|
80
|
+
|
81
|
+
# Builtin Contracts
|
82
|
+
class Any; end
|
83
|
+
module Boolean; end
|
84
|
+
TrueClass.send(:include, Boolean)
|
85
|
+
FalseClass.send(:include, Boolean)
|
86
|
+
|
87
|
+
class Method
|
88
|
+
def type_info
|
89
|
+
if methods_hash = Rubype.typed_method_info[owner]
|
90
|
+
methods_hash[name]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
typesig :type_info, [] => Hash
|
94
|
+
|
95
|
+
def arg_types
|
96
|
+
type_info.first.first if type_info
|
97
|
+
end
|
98
|
+
typesig :arg_types, [] => Array
|
99
|
+
|
100
|
+
def return_type
|
101
|
+
type_info.first.last if type_info
|
102
|
+
end
|
103
|
+
typesig :arg_types, [] => Any
|
104
|
+
end
|
data/lib/rubype/version.rb
CHANGED
data/test/test_rubype.rb
CHANGED
@@ -103,10 +103,10 @@ class TestRubype < MiniTest::Unit::TestCase
|
|
103
103
|
assert_equal meth.return_type, String
|
104
104
|
|
105
105
|
err = assert_raises(Rubype::ReturnTypeError) { meth.(1,2) }
|
106
|
-
assert_equal err.message, %|Expected MyClass#test_mth to return String but got nil instead|
|
106
|
+
#assert_equal err.message, %|Expected MyClass#test_mth to return String but got nil instead|
|
107
107
|
|
108
108
|
err = assert_raises(Rubype::ArgumentTypeError) { meth.(1,'2') }
|
109
|
-
assert_equal err.message, %|Expected MyClass#test_mth's 2nd argument to be Numeric but got "2" instead|
|
109
|
+
#assert_equal err.message, %|Expected MyClass#test_mth's 2nd argument to be Numeric but got "2" instead|
|
110
110
|
end
|
111
111
|
|
112
112
|
private
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rubype
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- gogotanaka
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-04-
|
11
|
+
date: 2015-04-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|