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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2acef96be19b3b93d4403a8ac88be41dfa2d45a9
4
- data.tar.gz: 573fd9e52d7bbcec0e178964633ccb3e48e40342
3
+ metadata.gz: 942f53bc7020fb73e9af0158cd6c5d75fa7476f7
4
+ data.tar.gz: e1fe176b85235b86007b967c040d5971a3851093
5
5
  SHA512:
6
- metadata.gz: a1e45168f79837035f25ba88da2fc53472026fc10d37ce2be1a61e65542692d3ddf365634343a6c7da23be7f47becccf6c034366617d99a970d7ad9eb2617625
7
- data.tar.gz: 506debee9973cd93b74eb2f4248253cbc5328dc3c618d340543bd8ada6119bc305d3ca11c0e46715b55c4ce911c38c86febc899705e513ad03270298b06a5ddd
6
+ metadata.gz: 5c60d752bd40eedcb3a85163d3c92d091f2a2e5e0098dd35afd61412a42cbec86416b67bec1cff60fa087b20b8eccabf9f413294e179dff9fed2d1dfe7ab45c0
7
+ data.tar.gz: f62bfe664a071c7f5c86490f708b483da6b5e837530cfc843f388c50938b7c0e61bd99f76f7fc2acb1f595acb292158d12ff0277e3a9fe410240a19fce2be064
data/README.md CHANGED
@@ -4,17 +4,20 @@
4
4
 
5
5
  ![210414.png](https://qiita-image-store.s3.amazonaws.com/0/30440/0aafba03-1a4c-4676-5377-75f906aaeab9.png)
6
6
  ```rb
7
- # Assert class of both args is Numeric and class of return is String
8
- def sum(x, y)
9
- (x + y).to_s
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
- # Assert first arg has method #to_i
14
- def sum(x, y)
15
- x.to_i + y
16
- end
17
- typesig :sum, [:to_i, Numeric] => Numeric
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: Expected MyClass#sum's 2nd argument to be Numeric but got "string" instead
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: Expected MyClass#wrong_sum to return Numeric but got "string" instead
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: Expected MyClass#sum's 1st argument to have method #to_i but got :has_no_to_i instead
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: Expected People#marry's 1st argument to be People but got "non people" instead
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.send(:define_typed_method, self, meth, type_info_hash, __rubype__)
12
+ ::Rubype.define_typed_method(self, meth, type_info_hash, __rubype__)
22
13
  self
23
14
  end
24
- end
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 typed_method_info
52
- @@typed_method_info
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
- private
56
- # @param caller [Object]
57
- # @param type_info_hash [Hash] { [ArgInfo_1, ArgInfo_2, ... ArgInfo_n] => RtnInfo }
58
- # @param module [Module]
59
- def define_typed_method(owner, meth, type_info_hash, __rubype__)
60
- arg_types, rtn_type = *strip_type_info(type_info_hash)
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
- # @param type_info_hash [Hash] { [ArgInfo_1, ArgInfo_2, ... ArgInfo_n] => RtnInfo }
73
- # @return arg_types [Array<Class, Symbol>], rtn_type [Class, Symbol]
74
- def strip_type_info(type_info_hash)
75
- arg_types, rtn_type = type_info_hash.first
76
- [arg_types, rtn_type]
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
- # @param caller [Module]
80
- # @param meth [Symbol]
81
- # @param args [Array<Object>]
82
- # @param type_infos [Array<Class, Symbol>]
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
- # @param caller [Module]
97
- # @param rtn [Object]
98
- # @param type_info [Class, Symbol]
99
- def assert_trn_type(meth_caller, meth, rtn, type_info)
100
- case type_check(rtn, type_info)
101
- when :need_correct_class
102
- raise ReturnTypeError,
103
- "Expected #{meth_caller.class}##{meth} to return #{type_info} but got #{rtn.inspect} instead"
104
- when :need_correct_method
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
- # @param obj [Object]
111
- # @param type_info [Class, Symbol]
112
- # @return [Symbol]
113
- def type_check(obj, type_info)
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
@@ -1,3 +1,3 @@
1
1
  module Rubype
2
- VERSION = "0.2.5"
2
+ VERSION = "0.2.6"
3
3
  end
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.5
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-09 00:00:00.000000000 Z
11
+ date: 2015-04-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler