rubype 0.2.5 → 0.2.6

Sign up to get free protection for your applications and to get access to all the features.
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