raap 0.6.0 → 0.8.0

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
  SHA256:
3
- metadata.gz: 5780226ede8755c1ef7f82e18b7e8d96a63d2ab6eb253bc8964f5745776f993f
4
- data.tar.gz: 6e9dd39a302f555cac12ecb6232a7c2f39d206911fcee7a30650d5fe4a9cbc63
3
+ metadata.gz: f119356b79f6beb968f26d019dda506e8eada4a48e178bd029e7e97d187f2d2c
4
+ data.tar.gz: f72f9d682ff47f11d51ebcf547a7f991c75b9be731eabc018fbb5b5a727dc358
5
5
  SHA512:
6
- metadata.gz: 2841e75bc0b03742cc0d9268f9ec2ae69f2f9c6e1f579ec6e794db5e9b8f80d763fac4ce28abd7361e19ada8f6289e594b6a9a3cdc63b1806181a69a9f49bb6a
7
- data.tar.gz: a3057811c5d13ac4f891fc2c2082b91f33a1a8d35d3c9b6e1ccb1871c268127811caa6f2708c878ef35ae4cc25143db62c4e5801ded32cfad9a1db50387753a6
6
+ metadata.gz: 7d3fbbfd916eb8526d9021e94408f4af402d80455c89b822c74557a76a31b8fc93a0d7acd8e47efdca32a8b1a19fb37a2b8f997e3c7ad6083e2f61d72052973a
7
+ data.tar.gz: cfbeb8a13d53e16a6e51701c30354ea1537c8b6b7a7ef46665b7e0efe9db43a4ce769370f38f558e4a384c80a039960faa65acbc8648c8f36e3725a53c2c1075
data/.rubocop.yml CHANGED
@@ -8,6 +8,8 @@ Lint:
8
8
  Enabled: true
9
9
  Lint/EmptyBlock:
10
10
  Enabled: false
11
+ Lint/SymbolConversion:
12
+ Enabled: false
11
13
 
12
14
  Style:
13
15
  Enabled: false
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # RaaP
2
2
 
3
- <img src="https://raw.githubusercontent.com/ksss/raap/main/public/jacket.webp" width=400/>
3
+ <img src="https://raw.githubusercontent.com/ksss/raap/main/public/jacket.webp" width=500/>
4
4
 
5
5
  ## RBS as a Property
6
6
 
@@ -14,6 +14,10 @@ The return value of the method is checked to see if it matches the type, if not,
14
14
 
15
15
  If you write an RBS, it becomes a test case.
16
16
 
17
+ ## Example
18
+
19
+ <img src="https://raw.githubusercontent.com/ksss/raap/main/public/example.webp" width=700/>
20
+
17
21
  ## Concept
18
22
 
19
23
  If you has next signature.
@@ -53,6 +57,10 @@ Then, you can start loop again.
53
57
 
54
58
  Finally, you get the perfect RBS!
55
59
 
60
+ ## Slide
61
+
62
+ https://speakerdeck.com/ksss/raap
63
+
56
64
  ## Installation
57
65
 
58
66
  Install the gem and add to the application's Gemfile by executing:
@@ -88,6 +96,20 @@ $ raap $(cat test/raap.txt) # You can manage the methods to be tested in a fil
88
96
  - info: A somewhat visualized representation of the execution state.
89
97
  - debug: All information including stack traces.
90
98
 
99
+ ## Coverage
100
+
101
+ RaaP randomly selects a type and generates a value for optional and union types.
102
+
103
+ By default, displays the coverage of the type that actually produced the value.
104
+
105
+ The coverage display can help you determine if a correction is needed.
106
+
107
+ ### Meaning of colored types
108
+
109
+ - 🔴 Red: A type that has never been used to generate or validate a value.
110
+ - 🟡 Yellow: It is systematically difficult to determine if the type was used or not. (FIXME)
111
+ - 🟢 Green: The type used to generate and validate the value.
112
+
91
113
  ## Size
92
114
 
93
115
  Random values are determined based on size.
@@ -150,6 +172,42 @@ You can specify size of step like `Integer#step: (to: Integer, by: Integer)`.
150
172
  By default, raap only validates public methods.
151
173
  However, by setting this option, it is possible to intentionally validate private methods in the RBS.
152
174
 
175
+ ### `--preload path`
176
+
177
+ Simply call `Kernel.load`.
178
+ However, this simplifies the preliminary preparation.
179
+
180
+ #### Preload examples
181
+
182
+ 1) Check signature of aws-sdk-s3
183
+
184
+ ```rb
185
+ $ cat preload.rb
186
+ require 'aws-sdk-s3'
187
+
188
+ # Register initialize of `Aws::S3::Client` class.
189
+ RaaP::Type.register("Aws::S3::Client") do
190
+ [:call, Aws::S3::Client, :new, [], { stub_responses: true }, nil]
191
+ end
192
+
193
+ $ raap --preload ./preload.rb 'Aws::S3::Client#get_object'
194
+ ```
195
+
196
+ 2) Output ruby coverage by simplecov
197
+
198
+ ```rb
199
+ $ cat preload.rb
200
+ require 'simplecov'
201
+
202
+ SimpleCov.command_name 'raap'
203
+ SimpleCov.start do
204
+ enable_coverage :branch
205
+ add_filter "/test/"
206
+ end
207
+
208
+ $ bundle exec raap --preload ./preload.rb -r my_class 'MyClass'
209
+ ```
210
+
153
211
  ## First support is CLI
154
212
 
155
213
  In RaaP, usage through the CLI is the primary support focus, and efforts are made to maintain compatibility. The use of the library's code (such as `RaaP::Type`) does not guarantee compatibility.
data/lib/raap/cli.rb CHANGED
@@ -12,6 +12,7 @@ module RaaP
12
12
  :size_to,
13
13
  :size_by,
14
14
  :allow_private,
15
+ :coverage,
15
16
  keyword_init: true
16
17
  )
17
18
 
@@ -37,13 +38,11 @@ module RaaP
37
38
  def initialize(argv)
38
39
  # defaults
39
40
  @option = Option.new(
40
- dirs: [],
41
- requires: [],
42
- libraries: [],
43
41
  timeout: 3,
44
42
  size_from: 0,
45
43
  size_to: 99,
46
44
  size_by: 1,
45
+ coverage: true,
47
46
  allow_private: false,
48
47
  )
49
48
  @argv = argv
@@ -54,13 +53,13 @@ module RaaP
54
53
  def load
55
54
  OptionParser.new do |o|
56
55
  o.on('-I', '--include PATH') do |path|
57
- @option.dirs << path
56
+ RaaP::RBS.loader.add(path: Pathname(path))
58
57
  end
59
58
  o.on('--library lib', 'load rbs library') do |lib|
60
- @option.libraries << lib
59
+ RaaP::RBS.loader.add(library: lib, version: nil)
61
60
  end
62
61
  o.on('--require lib', 'require ruby library') do |lib|
63
- @option.requires << lib
62
+ require lib
64
63
  end
65
64
  o.on('--log-level level', "default: info") do |arg|
66
65
  RaaP.logger.level = arg
@@ -80,18 +79,14 @@ module RaaP
80
79
  o.on('--allow-private', "default: #{@option.allow_private}") do
81
80
  @option.allow_private = true
82
81
  end
82
+ o.on('--preload path', 'Kernel.load path') do |path|
83
+ Kernel.load path
84
+ end
85
+ o.on('--[no-]coverage', "Show coverage for RBS (default: #{@option.coverage})") do |arg|
86
+ @option.coverage = arg
87
+ end
83
88
  end.parse!(@argv)
84
89
 
85
- @option.dirs.each do |dir|
86
- RaaP::RBS.loader.add(path: Pathname(dir))
87
- end
88
- @option.libraries.each do |lib|
89
- RaaP::RBS.loader.add(library: lib, version: nil)
90
- end
91
- @option.requires.each do |lib|
92
- require lib
93
- end
94
-
95
90
  self
96
91
  end
97
92
 
@@ -300,6 +295,7 @@ module RaaP
300
295
  timeout: @option.timeout,
301
296
  allow_private: @option.allow_private,
302
297
  )
298
+ RaaP::Coverage.start(method_type) if @option.coverage
303
299
  start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
304
300
  stats = prop.run do |called|
305
301
  case called
@@ -337,6 +333,9 @@ module RaaP
337
333
  end
338
334
  end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
339
335
  puts
336
+ RaaP::Coverage.show($stdout) if @option.coverage
337
+ puts
338
+
340
339
  time_diff = end_time - start_time
341
340
  time = ", time: #{(time_diff * 1000).round}ms"
342
341
  stats_log = "success: #{stats.success}, skip: #{stats.skip}, exception: #{stats.exception}#{time}"
@@ -0,0 +1,180 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RaaP
4
+ module Coverage
5
+ class Writer
6
+ def initialize(method_type, cov)
7
+ @method_type = method_type
8
+ @cov = cov
9
+ @cur = 0
10
+ end
11
+
12
+ def write(io)
13
+ RaaP.logger.debug { "Coverage: #{@cov}" }
14
+ ml = @method_type.location
15
+ unless ml
16
+ RaaP.logger.warn("No location information for `#{@method_type}`")
17
+ return
18
+ end
19
+ if ml.key?(:keyword)
20
+ # attr_{reader,writer,accessor}
21
+ phantom_member = RBS.parse_member(ml.source)
22
+ case phantom_member
23
+ when ::RBS::AST::Members::Attribute
24
+ unless phantom_member.location
25
+ RaaP.logger.warn("No location information for `#{phantom_member}`")
26
+ return
27
+ end
28
+ write_type(io, "return", phantom_member.type)
29
+ io.write(slice(@cur, @cur...phantom_member.location.end_pos))
30
+ else
31
+ RaaP.logger.error("#{phantom_member.class} is not supported")
32
+ return
33
+ end
34
+ else
35
+ # def name: () -> type
36
+ phantom_method_type = RBS.parse_method_type(ml.source)
37
+ phantom_method_type.type.yield_self do |fun|
38
+ case fun
39
+ when ::RBS::Types::Function
40
+ fun.required_positionals.each_with_index { |param, i| write_param(io, "req_#{i}", param) }
41
+ fun.optional_positionals.each_with_index { |param, i| write_param(io, "opt_#{i}", param) }
42
+ fun.rest_positionals&.yield_self { |param| write_param(io, "rest", param) }
43
+ fun.trailing_positionals.each_with_index { |param, i| write_param(io, "trail_#{i}", param) }
44
+ fun.required_keywords.each { |key, param| write_param(io, "keyreq_#{key}", param) }
45
+ fun.optional_keywords.each { |key, param| write_param(io, "key_#{key}", param) }
46
+ fun.rest_keywords&.yield_self { |param| write_param(io, "keyrest", param) }
47
+ # when ::RBS::Types::UntypedFunction
48
+ end
49
+ end
50
+
51
+ phantom_method_type.block&.yield_self do |b|
52
+ b.type.each_param.with_index { |param, i| write_param(io, "block_param_#{i}", param) }
53
+ write_type(io, "block_return", b.type.return_type)
54
+ end
55
+ write_type(io, "return", phantom_method_type.type.return_type)
56
+ raise unless phantom_method_type.location
57
+
58
+ io.write(slice(@cur, @cur...phantom_method_type.location.end_pos))
59
+ end
60
+
61
+ io.puts
62
+ end
63
+
64
+ private
65
+
66
+ def slice(start, range)
67
+ ml = @method_type.location
68
+ raise unless ml
69
+
70
+ ml.source[start, range.end - range.begin] or raise
71
+ end
72
+
73
+ def write_param(io, position, param)
74
+ write_type(io, position, param.type)
75
+ end
76
+
77
+ def write_type(io, position, type)
78
+ unless type.location
79
+ RaaP.logger.warn("No location information for `#{type}`")
80
+ return
81
+ end
82
+ io.write(slice(@cur, @cur...type.location.start_pos))
83
+ @cur = type.location.start_pos
84
+ case type
85
+ when ::RBS::Types::Tuple, ::RBS::Types::Union
86
+ name = type.class.name.split('::').last.downcase
87
+ type.types.each_with_index do |t, i|
88
+ t.location or raise
89
+ io.write(slice(@cur, @cur...t.location.start_pos)) # ( or [
90
+ @cur = t.location.start_pos
91
+ write_type(io, "#{position}_#{name}_#{i}", t)
92
+ end
93
+ when ::RBS::Types::Optional
94
+ raise unless type.location
95
+
96
+ write_type(io, "#{position}_optional_left", type.type)
97
+ io.write(slice(@cur, @cur...(type.location.end_pos - 1)))
98
+ @cur = type.location.end_pos - 1
99
+ if @cov.include?("#{position}_optional_right".to_sym)
100
+ io.write(green('?'))
101
+ else
102
+ io.write(red('?'))
103
+ end
104
+ raise unless type.location
105
+
106
+ @cur = type.location.end_pos
107
+ else
108
+ raise unless type.location
109
+
110
+ if @cov.include?(position.to_sym)
111
+ io.write(green(type.location.source))
112
+ else
113
+ io.write(red(type.location.source))
114
+ end
115
+ @cur = type.location.end_pos
116
+ end
117
+ end
118
+
119
+ def green(str) = "\e[32m#{str}\e[0m"
120
+ def red(str) = "\e[1;4;41m#{str}\e[0m"
121
+ end
122
+
123
+ class << self
124
+ def start(method_type)
125
+ @cov = Set.new
126
+ @method_type = method_type
127
+ end
128
+
129
+ def running?
130
+ !!@cov
131
+ end
132
+
133
+ def log(position)
134
+ return unless running?
135
+
136
+ cov << position.to_sym
137
+ end
138
+
139
+ def cov
140
+ @cov or raise("Coverage is not started")
141
+ end
142
+
143
+ def show(io)
144
+ return unless running?
145
+
146
+ writer = Writer.new(@method_type, cov)
147
+ writer.write(io)
148
+ end
149
+
150
+ def new_type_with_log(position, type)
151
+ log_with_type(position, type) do |t|
152
+ Type.new(t)
153
+ end
154
+ end
155
+
156
+ def log_with_type(position, type, &block)
157
+ case type
158
+ when ::RBS::Types::Tuple
159
+ # FIXME: Support Union in Tuple
160
+ type.types.each_with_index do |_t, i|
161
+ log("#{position}_tuple_#{i}")
162
+ end
163
+ block&.call(type)
164
+ when ::RBS::Types::Union
165
+ i = Random.rand(type.types.length)
166
+ log_with_type("#{position}_union_#{i}", type.types[i], &block)
167
+ when ::RBS::Types::Optional
168
+ if Random.rand(2).zero?
169
+ log_with_type("#{position}_optional_left", type.type, &block)
170
+ else
171
+ log_with_type("#{position}_optional_right", ::RBS::Types::Bases::Nil.new(location: nil), &block)
172
+ end
173
+ else
174
+ log(position)
175
+ block&.call(type)
176
+ end
177
+ end
178
+ end
179
+ end
180
+ end
@@ -2,8 +2,9 @@
2
2
 
3
3
  module RaaP
4
4
  class FunctionType
5
- def initialize(fun)
5
+ def initialize(fun, coverage: true)
6
6
  @fun = fun
7
+ @coverage = coverage
7
8
  end
8
9
 
9
10
  def pick_arguments(size: 10)
@@ -26,42 +27,66 @@ module RaaP
26
27
  when type.respond_to?(:each_pair)
27
28
  type.each_pair.to_h { |k, v| [k, to_symbolic_call_recursive(v, size: size)] }
28
29
  when type.respond_to?(:each)
29
- type.each.map { |v| to_symbolic_call_recursive(v, size: size) }
30
+ type.map { |v| to_symbolic_call_recursive(v, size: size) }
30
31
  else
31
32
  type.to_symbolic_call(size: size)
32
33
  end
33
34
  end
34
35
 
35
36
  def build_args_type
36
- reqs = @fun.required_positionals.map { |param| Type.new(param.type) }
37
- tras = @fun.trailing_positionals.map { |param| Type.new(param.type) }
38
- sampled_optional_positionals = @fun.optional_positionals.take(Random.rand(@fun.optional_positionals.length + 1))
39
- opts = sampled_optional_positionals.map { |param| Type.new(param.type) }
37
+ reqs = @fun.required_positionals.map.with_index do |param, i|
38
+ build_type_with_coverage("req_#{i}", param)
39
+ end
40
+
41
+ take_num = Random.rand(@fun.optional_positionals.length + 1)
42
+ opts = @fun.optional_positionals.take(take_num).map.each_with_index do |param, i|
43
+ build_type_with_coverage("opt_#{i}", param)
44
+ end
45
+
40
46
  rest = []
41
- if (param = @fun.rest_positionals)
42
- rest = Array.new(Random.rand(0..3)) { Type.new(param.type) }
47
+ if (rest_param = @fun.rest_positionals)
48
+ rest = Array.new(Random.rand(4)) do
49
+ build_type_with_coverage("rest", rest_param)
50
+ end
51
+ end
52
+
53
+ tras = @fun.trailing_positionals.map.with_index do |param, i|
54
+ build_type_with_coverage("trail_#{i}", param)
43
55
  end
56
+
44
57
  [reqs, opts, rest, tras].flatten
45
58
  end
46
59
 
47
60
  def build_kwargs_type
48
- reqs = @fun.required_keywords.transform_values { |param| Type.new(param.type) }
61
+ reqs = @fun.required_keywords.keys.to_h do |key|
62
+ [key, build_type_with_coverage("keyreq_#{key}", @fun.required_keywords[key])]
63
+ end
49
64
  rand = Random.rand(@fun.optional_keywords.length + 1)
50
- opts = @fun.optional_keywords.to_a.sample(rand).to_h { |name, param| [name, Type.new(param.type)] }
65
+ opts = @fun.optional_keywords.to_a.sample(rand).to_h do |key, param|
66
+ [key, build_type_with_coverage("key_#{key}", param)]
67
+ end
51
68
  kwargs = reqs.to_h.merge(opts)
52
69
  if (param = @fun.rest_keywords)
53
- keys = Array.new(Random.rand(0..3)) do
70
+ keys = Array.new(Random.rand(4)) do
54
71
  random_key = nil
55
72
  loop do
56
73
  # @type var random_key: Symbol
57
74
  random_key = Type.new("Symbol").pick(size: 6)
58
75
  break unless kwargs.key?(random_key)
59
76
  end
60
- [random_key, Type.new(param.type)]
77
+ [random_key, build_type_with_coverage("keyrest", param)]
61
78
  end
62
79
  kwargs.merge!(keys.to_h)
63
80
  end
64
81
  kwargs
65
82
  end
83
+
84
+ def build_type_with_coverage(position, param)
85
+ if @coverage
86
+ Coverage.new_type_with_log(position, param.type)
87
+ else
88
+ Type.new(param.type)
89
+ end
90
+ end
66
91
  end
67
92
  end
@@ -50,7 +50,7 @@ module RaaP
50
50
  private
51
51
 
52
52
  def call(size:, stats:)
53
- if @method_type.rbs.type.each_type.find { |t| t.instance_of?(::RBS::Types::Bases::Any) }
53
+ if @method_type.rbs.type.each_param.find { |param| param.type.each_type.find { |t| t.instance_of?(::RBS::Types::Bases::Any) } }
54
54
  RaaP.logger.info { "Skip type check since `#{@method_type.rbs}` includes `untyped`" }
55
55
  stats.break = true
56
56
  throw :break
@@ -67,8 +67,9 @@ module RaaP
67
67
  begin
68
68
  return_value = symbolic_caller.eval
69
69
  rescue StandardError, NotImplementedError
70
- check = [:success]
71
70
  return_value = Value::Bottom.new
71
+ coverage("return", return_value, return_type)
72
+ check = [:success]
72
73
  rescue Timeout::ExitException
73
74
  raise
74
75
  rescue Exception => e # rubocop:disable Lint/RescueException
@@ -113,6 +114,7 @@ module RaaP
113
114
  if BindCall.is_a?(return_type, ::RBS::Types::ClassSingleton)
114
115
  # ::RBS::Test::TypeCheck cannot support to check singleton class
115
116
  if receiver_value == return_value
117
+ coverage("return", return_value, return_type)
116
118
  [:success]
117
119
  else
118
120
  [:failure]
@@ -135,6 +137,7 @@ module RaaP
135
137
  )
136
138
  begin
137
139
  if type_check.value(return_value, return_type)
140
+ coverage("return", return_value, return_type, type_check)
138
141
  [:success]
139
142
  else
140
143
  [:failure]
@@ -148,5 +151,33 @@ module RaaP
148
151
  def return_type
149
152
  @method_type.rbs.type.return_type
150
153
  end
154
+
155
+ def coverage(position, return_value, return_type, type_check = nil)
156
+ return unless Coverage.running?
157
+
158
+ case return_type
159
+ when ::RBS::Types::Tuple
160
+ return_type.types.zip(return_value).each_with_index do |(type, value), i|
161
+ if type_check&.value(value, type)
162
+ coverage("#{position}_tuple_#{i}", value, type, type_check)
163
+ end
164
+ end
165
+ when ::RBS::Types::Union
166
+ return_type.types.each_with_index do |type, i|
167
+ if type_check&.value(return_value, type)
168
+ coverage("#{position}_union_#{i}", return_value, type, type_check)
169
+ break
170
+ end
171
+ end
172
+ when ::RBS::Types::Optional
173
+ if return_value.nil?
174
+ Coverage.log("#{position}_optional_right")
175
+ else
176
+ coverage("#{position}_optional_left", return_value, return_type.type, type_check)
177
+ end
178
+ else
179
+ Coverage.log(position)
180
+ end
181
+ end
151
182
  end
152
183
  end
@@ -19,9 +19,17 @@ module RaaP
19
19
 
20
20
  params = (type_params_decl + rbs.type_params).uniq
21
21
  ts = TypeSubstitution.new(params, type_args)
22
-
23
22
  @rbs = ts.method_type_sub(rbs, self_type: self_type, instance_type: instance_type, class_type: class_type)
24
- @fun_type = FunctionType.new(@rbs.type)
23
+ function_or_untypedfunction = __skip__ = @rbs.type
24
+ @fun_type = FunctionType.new(function_or_untypedfunction)
25
+ @type_check = ::RBS::Test::TypeCheck.new(
26
+ self_class: (_ = self_type),
27
+ instance_class: (_ = instance_type),
28
+ class_class: (_ = class_type),
29
+ builder: RBS.builder,
30
+ sample_size: 100,
31
+ unchecked_classes: []
32
+ )
25
33
  end
26
34
 
27
35
  def pick_arguments(size: 10)
@@ -40,21 +48,63 @@ module RaaP
40
48
  return nil if block.nil?
41
49
  return nil if (block.required == false) && [true, false].sample
42
50
 
51
+ args_name = []
52
+ args_source = []
53
+ resource = [*'a'..'z']
54
+ case fun = block.type
55
+ when ::RBS::Types::Function
56
+ # FIXME: Support keyword types
57
+ fun.required_positionals.each do
58
+ resource.shift.tap do |name|
59
+ args_name << name
60
+ args_source << name
61
+ end
62
+ end
63
+ fun.optional_positionals.each do |param|
64
+ resource.shift.tap do |name|
65
+ # FIXME: Support without literal type
66
+ default = Type.new(param.type).pick(size: size)
67
+ args_name << name
68
+ args_source << "#{name} = #{default}"
69
+ end
70
+ end
71
+ fun.rest_positionals&.yield_self do |_|
72
+ resource.shift.tap do |name|
73
+ args_name << "*#{name}"
74
+ args_source << "*#{name}"
75
+ end
76
+ end
77
+ fun.trailing_positionals.each do
78
+ resource.shift.tap do |name|
79
+ args_name << name
80
+ args_source << name
81
+ end
82
+ end
83
+ end
84
+ # Hack: Use local variable in eval
43
85
  fixed_return_value = Type.new(block.type.return_type).pick(size: size)
44
- Proc.new { fixed_return_value }
86
+ _ = fixed_return_value
87
+ type_check = @type_check
88
+ _ = type_check
89
+ eval(<<~RUBY) # rubocop:disable Security/Eval
90
+ -> (#{args_source.join(', ')}) do
91
+ i = 0
92
+ type_check.zip_args([#{args_name.join(', ')}], block.type) do |val, param|
93
+ unless type_check.value(val, param.type)
94
+ raise TypeError, "block argument type mismatch: expected `(\#{fun.param_to_s})`, got \#{BindCall.inspect([#{args_name.join(', ')}])}"
95
+ end
96
+
97
+ Coverage.log_with_type("block_param_\#{i}", param.type)
98
+ i += 1
99
+ end
100
+ Coverage.log_with_type("block_return", block.type.return_type)
101
+ fixed_return_value
102
+ end
103
+ RUBY
45
104
  end
46
105
 
47
106
  def check_return(return_value)
48
- untyped = __skip__ = nil
49
- type_check = ::RBS::Test::TypeCheck.new(
50
- self_class: untyped, # cannot support `self`
51
- instance_class: untyped, # cannot support `instance`
52
- class_class: untyped, # cannot support `class`
53
- builder: RBS.builder,
54
- sample_size: 100,
55
- unchecked_classes: []
56
- )
57
- type_check.value(return_value, rbs.type.return_type)
107
+ @type_check.value(return_value, rbs.type.return_type)
58
108
  end
59
109
  end
60
110
  end
data/lib/raap/rbs.rb CHANGED
@@ -26,6 +26,48 @@ module RaaP
26
26
  ::RBS::Parser.parse_method_type(method_type, require_eof: true) or raise
27
27
  end
28
28
 
29
+ def self.parse_member(member_type)
30
+ _, _, decls = ::RBS::Parser.parse_signature(<<~RBS)
31
+ module MemberScope
32
+ #{member_type}
33
+ end
34
+ RBS
35
+ decl = decls.first or raise
36
+ raise unless decl.is_a?(::RBS::AST::Declarations::Module)
37
+
38
+ member = decl.members.first or raise
39
+ raise unless member.is_a?(::RBS::AST::Members::Attribute)
40
+
41
+ member.tap do |m|
42
+ m = __skip__ = m
43
+ _shift_location(m.type, -m.location.start_pos)
44
+ _shift_location(m, -m.location.start_pos)
45
+ end
46
+ end
47
+
48
+ def self._shift_location(localable, shift)
49
+ return if localable.location.nil?
50
+
51
+ l = localable.instance_variable_get("@location")
52
+ localable.instance_variable_set(
53
+ "@location",
54
+ ::RBS::Location.new(
55
+ buffer: ::RBS::Buffer.new(
56
+ name: l.buffer.name,
57
+ content: l.buffer.content[-shift..l.end_pos],
58
+ ),
59
+ start_pos: l.start_pos + shift,
60
+ end_pos: l.end_pos + shift,
61
+ )
62
+ )
63
+ case localable
64
+ when ::RBS::Types::Union
65
+ localable.types.each { |t| _shift_location(t, shift) }
66
+ when ::RBS::Types::Optional
67
+ _shift_location(localable.type, shift)
68
+ end
69
+ end
70
+
29
71
  def self.find_alias_decl(type_name, method_name)
30
72
  env.class_decls[type_name].decls.each do |d|
31
73
  d.decl.members.each do |member|
data/lib/raap/type.rb CHANGED
@@ -24,6 +24,7 @@ module RaaP
24
24
  def self.register(type_name, &block)
25
25
  raise ArgumentError, "block is required" unless block
26
26
 
27
+ type_name = "::#{type_name}" if !type_name.start_with?("::")
27
28
  GENERATORS[type_name] = __skip__ = block
28
29
  end
29
30
 
@@ -69,11 +70,12 @@ module RaaP
69
70
  t = instance.args[0] ? Type.new(instance.args[0], range: range) : Type.random
70
71
  array(t)
71
72
  end
72
- register("::Binding") { sized { binding } }
73
+ register("::Binding") { binding }
73
74
  register("::Complex") { complex }
74
- register("::Data") { sized { Data.define } }
75
+ register("::Data") { Data.define }
75
76
  register("::Encoding") { encoding }
76
- register("::FalseClass") { sized { false } }
77
+ register("::FalseClass") { false }
78
+ register("::File") { File.open("/dev/null") }
77
79
  register("::Float") { float }
78
80
  register("::Hash") do
79
81
  instance = __skip__ = type
@@ -82,18 +84,18 @@ module RaaP
82
84
  dict(key, value)
83
85
  end
84
86
  register("::Integer") { integer }
85
- register("::IO") { sized { $stdout } }
86
- register("::Method") { sized { temp_method_object } }
87
- register("::NilClass") { sized { nil } }
88
- register("::Proc") { sized { Proc.new {} } }
87
+ register("::IO") { $stdout }
88
+ register("::Method") { temp_method_object }
89
+ register("::NilClass") { nil }
90
+ register("::Proc") { Proc.new {} }
89
91
  register("::Rational") { rational }
90
92
  register("::Regexp") { sized { |size| Regexp.new(string.pick(size: size)) } }
91
93
  register("::String") { string }
92
- register("::Struct") { sized { Struct.new(:foo, :bar).new } }
94
+ register("::Struct") { Struct.new(:foo, :bar).new }
93
95
  register("::Symbol") { symbol }
94
- register("::Time") { sized { [:call, Time, :now, [], {}, nil] } }
95
- register("::TrueClass") { sized { true } }
96
- register("::UnboundMethod") { sized { temp_method_object.unbind } }
96
+ register("::Time") { [:call, Time, :now, [], {}, nil] }
97
+ register("::TrueClass") { true }
98
+ register("::UnboundMethod") { temp_method_object.unbind }
97
99
 
98
100
  attr_reader :type
99
101
  attr_reader :range
@@ -176,7 +178,7 @@ module RaaP
176
178
  Object.const_get(type.name.to_s)
177
179
  when ::RBS::Types::ClassInstance
178
180
  case gen = GENERATORS[type.name.absolute!.to_s]
179
- in Proc then instance_exec(&gen).pick(size: size)
181
+ in Proc then pick_by_generator(gen, size: size)
180
182
  in nil then to_symbolic_call_from_initialize(type, size: size)
181
183
  end
182
184
  when ::RBS::Types::Record
@@ -186,7 +188,7 @@ module RaaP
186
188
  when ::RBS::Types::Literal
187
189
  type.literal
188
190
  when ::RBS::Types::Bases::Bool
189
- bool.pick(size: size)
191
+ bool
190
192
  when ::RBS::Types::Bases::Any
191
193
  Type.random.to_symbolic_call(size: size)
192
194
  when ::RBS::Types::Bases::Nil
@@ -198,6 +200,16 @@ module RaaP
198
200
 
199
201
  private
200
202
 
203
+ def pick_by_generator(gen, size:)
204
+ ret = instance_exec(&gen)
205
+ case ret
206
+ when Sized
207
+ ret.pick(size: size)
208
+ else
209
+ ret
210
+ end
211
+ end
212
+
201
213
  def to_symbolic_call_from_initialize(type, size:)
202
214
  type_name = type.name.absolute!
203
215
  const = Object.const_get(type_name.to_s)
@@ -301,16 +313,12 @@ module RaaP
301
313
  end
302
314
 
303
315
  def encoding
304
- sized do
305
- e = Encoding.list.sample or raise
306
- [:call, Encoding, :find, [e.name], {}, nil]
307
- end
316
+ e = Encoding.list.sample or raise
317
+ [:call, Encoding, :find, [e.name], {}, nil]
308
318
  end
309
319
 
310
320
  def bool
311
- sized do
312
- Random.rand(2) == 0
313
- end
321
+ Random.rand(2) == 0
314
322
  end
315
323
 
316
324
  def temp_method_object
@@ -25,6 +25,10 @@ module RaaP
25
25
  instance_type = instance_type.is_a?(::String) ? RBS.parse_type(instance_type) : instance_type
26
26
  class_type = class_type.is_a?(::String) ? RBS.parse_type(class_type) : class_type
27
27
  sub = build
28
+ if sub.empty? && self_type.nil? && instance_type.nil? && class_type.nil?
29
+ return method_type
30
+ end
31
+
28
32
  ::RBS::MethodType.new(
29
33
  type_params: [],
30
34
  type: method_type.type.sub(sub).then { |ty| sub(ty, self_type: self_type, instance_type: instance_type, class_type: class_type) },
@@ -2,9 +2,9 @@
2
2
 
3
3
  module RaaP
4
4
  module Value
5
- class Interface < BasicObject
5
+ class Interface
6
6
  class << self
7
- def define_method_from_interface(base_class, type, size: 3)
7
+ def define_method_from_interface(base_mod, type, size: 3)
8
8
  type = type.is_a?(::String) ? RBS.parse_type(type) : type
9
9
  unless type.instance_of?(::RBS::Types::Interface)
10
10
  ::Kernel.raise ::TypeError, "not an interface type: #{type}"
@@ -22,7 +22,7 @@ module RaaP
22
22
  ts = TypeSubstitution.new(type_params, type.args)
23
23
  subed_method_type = ts.method_type_sub(method_type, self_type: self_type, instance_type: instance_type, class_type: class_type)
24
24
 
25
- BindCall.define_method(base_class, name) do |*_, &b|
25
+ BindCall.define_method(base_mod, name) do |*_, &b|
26
26
  @fixed_return_value ||= {}
27
27
  @fixed_return_value[name] ||= if self_type == subed_method_type.type.return_type
28
28
  self
@@ -30,16 +30,13 @@ module RaaP
30
30
  Type.new(subed_method_type.type.return_type).pick(size: size)
31
31
  end
32
32
  # @type var b: Proc?
33
- if b
33
+ if b && subed_method_type.block && subed_method_type.block.type.is_a?(::RBS::Types::Function)
34
34
  @fixed_block_arguments ||= {}
35
- @fixed_block_arguments[name] ||= if subed_method_type.block
36
- size.times.map do
37
- FunctionType.new(subed_method_type.block.type)
38
- .pick_arguments(size: size)
39
- end
40
- else
41
- []
42
- end
35
+ @fixed_block_arguments[name] ||= size.times.map do
36
+ FunctionType.new(subed_method_type.block.type, coverage: false)
37
+ .pick_arguments(size: size)
38
+ end
39
+
43
40
  @fixed_block_arguments[name].each do |a, kw|
44
41
  b.call(*a, **kw)
45
42
  end
@@ -68,14 +65,6 @@ module RaaP
68
65
  @size = size
69
66
  end
70
67
 
71
- def respond_to?(name, _include_all = false)
72
- @definition.methods.has_key?(name.to_sym)
73
- end
74
-
75
- def class
76
- Interface
77
- end
78
-
79
68
  def inspect
80
69
  "#<interface @type=`#{@type}` @methods=#{@definition.methods.keys} @size=#{@size}>"
81
70
  end
@@ -17,19 +17,18 @@ module RaaP
17
17
  raise ArgumentError, "intersection type must have at least one class instance type in `#{instances}`"
18
18
  end
19
19
 
20
- base = instances.find { |c| c.is_a?(::Class) } || BasicObject
20
+ base = instances.find { |c| c.is_a?(::Class) } || Object
21
21
 
22
22
  c = Class.new(base) do
23
23
  instances.select { |i| !i.is_a?(::Class) }.each do |m|
24
24
  include(m)
25
25
  end
26
26
 
27
- interfaces = type.types.select do |t|
28
- t.instance_of?(::RBS::Types::Interface)
29
- end
30
-
31
- interfaces.each do |interface|
32
- Interface.define_method_from_interface(self, interface, size: size)
27
+ type.types.each do |t|
28
+ case t
29
+ when ::RBS::Types::Interface
30
+ Interface.define_method_from_interface(self, t, size: size)
31
+ end
33
32
  end
34
33
  end
35
34
  type = ::RBS::Types::ClassInstance.new(name: TypeName(base.name), args: [], location: nil)
@@ -2,7 +2,7 @@
2
2
 
3
3
  module RaaP
4
4
  module Value
5
- class Variable < BasicObject
5
+ class Variable
6
6
  attr_reader :type
7
7
 
8
8
  def initialize(type)
data/lib/raap/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RaaP
4
- VERSION = "0.6.0"
4
+ VERSION = "0.8.0"
5
5
  end
data/lib/raap.rb CHANGED
@@ -23,6 +23,7 @@ module RaaP
23
23
 
24
24
  autoload :BindCall, "raap/bind_call"
25
25
  autoload :CLI, "raap/cli"
26
+ autoload :Coverage, "raap/coverage"
26
27
  autoload :FunctionType, "raap/function_type"
27
28
  autoload :MethodProperty, "raap/method_property"
28
29
  autoload :MethodType, "raap/method_type"
Binary file
data/sig/raap.rbs CHANGED
@@ -19,21 +19,15 @@ module RaaP
19
19
 
20
20
  class CLI
21
21
  class Option < ::Struct[untyped]
22
- def self.new: (?dirs: ::Array[String], ?requires: ::Array[String], ?libraries: ::Array[String], ?timeout: (Integer | Float | nil), ?size_from: ::Integer, ?size_to: ::Integer, ?size_by: ::Integer, ?allow_private: bool) -> instance
22
+ def self.new: (?timeout: (Integer | Float | nil), ?size_from: ::Integer, ?size_to: ::Integer, ?size_by: ::Integer, ?allow_private: bool, ?coverage: bool) -> instance
23
23
 
24
- def self.[]: (?dirs: ::Array[String], ?requires: ::Array[String], ?libraries: ::Array[String], ?timeout: (Integer | Float | nil), ?size_from: ::Integer, ?size_to: ::Integer, ?size_by: ::Integer, ?allow_private: bool) -> instance
24
+ def self.[]: (?timeout: (Integer | Float | nil), ?size_from: ::Integer, ?size_to: ::Integer, ?size_by: ::Integer, ?allow_private: bool, ?coverage: bool) -> instance
25
25
 
26
26
  def self.keyword_init?: () -> true
27
27
 
28
- def self.members: () -> [ :dirs, :requires, :library, :timeout, :size_from, :size_to, :size_by, :allow_private]
28
+ def self.members: () -> [ :timeout, :size_from, :size_to, :size_by, :allow_private, :coverage]
29
29
 
30
- def members: () -> [ :dirs, :requires, :library, :timeout, :size_from, :size_to, :size_by, :allow_private]
31
-
32
- attr_accessor dirs: ::Array[String]
33
-
34
- attr_accessor requires: ::Array[String]
35
-
36
- attr_accessor libraries: ::Array[String]
30
+ def members: () -> [ :timeout, :size_from, :size_to, :size_by, :allow_private, :coverage]
37
31
 
38
32
  attr_accessor timeout: (Integer | Float | nil)
39
33
 
@@ -44,6 +38,8 @@ module RaaP
44
38
  attr_accessor size_by: ::Integer
45
39
 
46
40
  attr_accessor allow_private: bool
41
+
42
+ attr_accessor coverage: bool
47
43
  end
48
44
 
49
45
  type property_result = [Integer, Symbol, ::RBS::MethodType, StringIO?]
@@ -71,10 +67,44 @@ module RaaP
71
67
  def report: () -> Integer
72
68
  end
73
69
 
70
+ module Coverage
71
+ type locs = [::RBS::Buffer::loc, ::RBS::Buffer::loc]
72
+ class Writer
73
+ @method_type: ::RBS::MethodType
74
+ @cov: ::Set[Symbol]
75
+ @cur: Integer
76
+
77
+ def initialize: (::RBS::MethodType, Set[Symbol]) -> void
78
+ def write: (IO) -> void
79
+
80
+ private
81
+
82
+ def method_type_location: () -> ::RBS::Location[untyped, untyped]
83
+ def slice: (Integer, Range[Integer]) -> String
84
+ def write_param: (IO, String, ::RBS::Types::Function::Param) -> void
85
+ def write_type: (IO, String, ::RBS::Types::t) -> void
86
+ def green: (String) -> String
87
+ def red: (String) -> String
88
+ end
89
+
90
+ self.@cov: Set[Symbol]?
91
+ self.@method_type: ::RBS::MethodType
92
+
93
+ def self.start: (::RBS::MethodType) -> void
94
+ def self.running?: () -> bool
95
+ def self.log: (String | Symbol) -> void
96
+ def self.cov: () -> Set[Symbol]
97
+ def self.show: (IO) -> void
98
+ def self.new_type_with_log: (String, ::RBS::Types::t) -> Type
99
+ def self.log_with_type: (String, ::RBS::Types::t) -> nil
100
+ | (String, ::RBS::Types::t) ?{ (::RBS::Types::t) -> Type } -> Type
101
+ end
102
+
74
103
  class FunctionType
75
104
  @fun: ::RBS::Types::Function
105
+ @coverage: boolish
76
106
 
77
- def initialize: (::RBS::Types::Function) -> void
107
+ def initialize: (::RBS::Types::Function, ?coverage: boolish) -> void
78
108
  def pick_arguments: (?size: Integer) -> [Array[untyped], Hash[Symbol, untyped]]
79
109
  def arguments_to_symbolic_call: (?size: Integer) -> [Array[untyped], Hash[Symbol, untyped]]
80
110
 
@@ -83,6 +113,7 @@ module RaaP
83
113
  def to_symbolic_call_recursive: (untyped, size: Integer) -> untyped
84
114
  def build_args_type: () -> Array[Type]
85
115
  def build_kwargs_type: () -> Hash[Symbol, Type]
116
+ def build_type_with_coverage: (String, ::RBS::Types::Function::Param) -> Type
86
117
  end
87
118
 
88
119
  class MethodProperty
@@ -108,11 +139,14 @@ module RaaP
108
139
  def call: (size: Integer, stats: Stats) -> (Result::Success | Result::Failure | Result::Skip | Result::Exception)
109
140
  def check_return: (receiver_value: untyped, return_value: untyped) -> ([Symbol] | [Symbol, Exception])
110
141
  def return_type: () -> RBS::Types::t
142
+ def original_return_type: () -> RBS::Types::t
143
+ def coverage: (String, untyped, RBS::Types::t, ?RBS::Test::TypeCheck?) -> void
111
144
  end
112
145
 
113
146
  class MethodType
114
147
  attr_reader rbs: ::RBS::MethodType
115
148
  @fun_type: FunctionType
149
+ @type_check: ::RBS::Test::TypeCheck
116
150
 
117
151
  def initialize: (::RBS::MethodType | String method, ?type_params_decl: Array[untyped], ?type_args: Array[untyped], ?self_type: ::RBS::Types::ClassInstance?, ?instance_type: ::RBS::Types::ClassInstance?, ?class_type: ::RBS::Types::ClassSingleton?) -> void
118
152
  def pick_arguments: (?size: Integer) -> [Array[untyped], Hash[Symbol, untyped], ::Proc?]
@@ -134,6 +168,8 @@ module RaaP
134
168
  def self.loader: () -> ::RBS::EnvironmentLoader
135
169
  def self.parse_type: (String) -> ::RBS::Types::t
136
170
  def self.parse_method_type: (String) -> ::RBS::MethodType
171
+ def self.parse_member: (String) -> ::RBS::AST::Members::Attribute
172
+ def self._shift_location: (untyped, Integer) -> void
137
173
  def self.find_alias_decl: (::RBS::TypeName, Symbol) -> ::RBS::AST::Members::Alias?
138
174
  end
139
175
 
@@ -242,7 +278,7 @@ module RaaP
242
278
  GENERATORS: Hash[String, ^() -> Sized[untyped]]
243
279
  SIMPLE_SOURCE: Array[String]
244
280
 
245
- def self.register: (String) { () [self: instance] -> Sized[untyped] } -> void
281
+ def self.register: (String) { () [self: instance] -> untyped } -> void
246
282
  def self.random: () -> Type
247
283
  def self.random_without_basic_object: () -> Type
248
284
  def self.call_new_from: (Module, ::RBS::Types::ClassInstance, size: Integer) -> symbolic_call
@@ -278,8 +314,8 @@ module RaaP
278
314
  def symbol: () -> Sized[Symbol]
279
315
  def array: (Type) -> Sized[Array[untyped]]
280
316
  def dict: (Type, Type) -> Sized[Hash[untyped, untyped]]
281
- def encoding: () -> Sized[symbolic_call]
282
- def bool: () -> Sized[bool]
317
+ def encoding: () -> symbolic_call
318
+ def bool: () -> bool
283
319
  def temp_method_object: () -> ::Method
284
320
  end
285
321
 
@@ -289,19 +325,19 @@ module RaaP
289
325
  def class: () -> class
290
326
  end
291
327
 
292
- class Interface < BasicObject
328
+ class Interface
293
329
  @type: ::RBS::Types::Interface
294
330
  @size: Integer
295
331
  @definition: ::RBS::Definition
296
332
 
297
- def self.define_method_from_interface: (Class base_class, String | ::RBS::Types::Interface type, ?size: Integer) -> void
333
+ def self.define_method_from_interface: (::Module base_class, String | ::RBS::Types::Interface type, ?size: Integer) -> void
298
334
  def initialize: (String | ::RBS::Types::Interface | String, ?size: Integer) -> void
299
335
  def respond_to?: (Symbol, ?boolish) -> bool
300
336
  def inspect: () -> String
301
337
  def class: () -> class
302
338
  end
303
339
 
304
- class Intersection < BasicObject
340
+ module Intersection
305
341
  @type: ::RBS::Types::Intersection
306
342
  @children: Array[Type]
307
343
  @size: Integer
@@ -328,7 +364,7 @@ module RaaP
328
364
  def class: () -> class
329
365
  end
330
366
 
331
- class Variable < BasicObject
367
+ class Variable
332
368
  attr_reader type: ::RBS::Types::Variable
333
369
 
334
370
  def initialize: (::RBS::Types::Variable | String | Symbol) -> void
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: raap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ksss
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-04-25 00:00:00.000000000 Z
11
+ date: 2024-06-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rbs
@@ -57,6 +57,7 @@ files:
57
57
  - lib/raap.rb
58
58
  - lib/raap/bind_call.rb
59
59
  - lib/raap/cli.rb
60
+ - lib/raap/coverage.rb
60
61
  - lib/raap/function_type.rb
61
62
  - lib/raap/method_property.rb
62
63
  - lib/raap/method_type.rb
@@ -77,6 +78,7 @@ files:
77
78
  - lib/raap/value/void.rb
78
79
  - lib/raap/version.rb
79
80
  - lib/shims.rb
81
+ - public/example.webp
80
82
  - public/jacket.webp
81
83
  - rbs_collection.lock.yaml
82
84
  - rbs_collection.yaml
@@ -102,7 +104,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
102
104
  - !ruby/object:Gem::Version
103
105
  version: '0'
104
106
  requirements: []
105
- rubygems_version: 3.2.33
107
+ rubygems_version: 3.5.9
106
108
  signing_key:
107
109
  specification_version: 4
108
110
  summary: RBS as a Property