raap 0.2.0 → 0.4.0

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.
@@ -2,24 +2,19 @@
2
2
 
3
3
  module RaaP
4
4
  class MethodProperty
5
- class Stats < Struct.new(:success, :skip, :exception)
6
- def initialize(success: 0, skip: 0, exception: 0)
5
+ class Stats < Struct.new(:success, :skip, :exception, :break)
6
+ def initialize(success: 0, skip: 0, exception: 0, break: false)
7
7
  super
8
8
  end
9
9
  end
10
10
 
11
- attr_reader :receiver_type
12
- attr_reader :method_name
13
- attr_reader :method_type
14
- attr_reader :size_step
15
- attr_reader :timeout
16
-
17
- def initialize(receiver_type:, method_name:, method_type:, size_step:, timeout:)
11
+ def initialize(receiver_type:, method_name:, method_type:, size_step:, timeout:, allow_private: false)
18
12
  @receiver_type = receiver_type
19
13
  @method_name = method_name
20
14
  @method_type = method_type
21
15
  @size_step = size_step
22
16
  @timeout = timeout
17
+ @allow_private = allow_private
23
18
  end
24
19
 
25
20
  def run
@@ -28,12 +23,26 @@ module RaaP
28
23
  Timeout.timeout(@timeout) do
29
24
  catch(:break) do
30
25
  @size_step.each do |size|
31
- yield call(size: size, stats: stats)
26
+ call(size:, stats:).tap do |ret|
27
+ case ret
28
+ when Result::Success
29
+ stats.success += 1
30
+ when Result::Failure
31
+ # no count
32
+ when Result::Skip
33
+ stats.skip += 1
34
+ when Result::Exception
35
+ stats.exception += 1
36
+ end
37
+
38
+ yield ret
39
+ end
32
40
  end
33
41
  end
34
42
  end
35
43
  rescue Timeout::Error => exception
36
- RaaP.logger.warn "Timeout: #{exception}"
44
+ stats.break = true
45
+ RaaP.logger.info "Timeout: #{exception}"
37
46
  end
38
47
  stats
39
48
  end
@@ -41,60 +50,75 @@ module RaaP
41
50
  private
42
51
 
43
52
  def call(size:, stats:)
44
- receiver_value = receiver_type.pick(size: size, eval: false)
45
- arguments = method_type.pick_arguments(size: size, eval: false)
46
- method_value = MethodValue.new(receiver_value:, arguments:, method_name:, size:)
47
- symbolic_call = method_value.to_symbolic_call
53
+ if @method_type.rbs.each_type.find { |t| t.instance_of?(::RBS::Types::Bases::Any) }
54
+ RaaP.logger.info { "Skip type check since `#{@method_type.rbs}` includes `untyped`" }
55
+ stats.break = true
56
+ throw :break
57
+ end
58
+ receiver_value = @receiver_type.to_symbolic_call(size:)
59
+ args, kwargs, block = @method_type.arguments_to_symbolic_call(size:)
60
+ # @type var symbolic_call: symbolic_call
61
+ symbolic_call = [:call, receiver_value, @method_name, args, kwargs, block]
62
+ symbolic_caller = SymbolicCaller.new(symbolic_call, allow_private: @allow_private)
48
63
  begin
49
- # ensure method_value
50
- check = false
64
+ # ensure symbolic_call
65
+ check = [:failure]
51
66
  if return_type.instance_of?(::RBS::Types::Bases::Bottom)
52
67
  begin
53
- return_value = SymbolicCaller.new(symbolic_call).eval
54
- rescue => e
55
- check = true
68
+ return_value = symbolic_caller.eval
69
+ rescue StandardError, NotImplementedError
70
+ check = [:success]
56
71
  return_value = Value::Bottom.new
72
+ rescue Timeout::ExitException
73
+ raise
74
+ rescue Exception => e # rubocop:disable Lint/RescueException
75
+ RaaP.logger.error("[#{e.class}] class is not supported to check `bot` type")
76
+ raise
57
77
  end
58
78
  else
59
- return_value = SymbolicCaller.new(symbolic_call).eval
60
- check = check_return(receiver_value:, return_value:, method_type:)
79
+ return_value = symbolic_caller.eval
80
+ check = check_return(receiver_value:, return_value:)
61
81
  end
62
- if check
63
- stats.success += 1
64
- Result::Success.new(method_value:, return_value:)
65
- else
66
- Result::Failure.new(method_value:, return_value:, symbolic_call:)
82
+ case check
83
+ in [:success]
84
+ Result::Success.new(symbolic_call:, return_value:)
85
+ in [:failure]
86
+ Result::Failure.new(symbolic_call:, return_value:)
87
+ in [:exception, exception]
88
+ Result::Exception.new(symbolic_call:, exception:)
67
89
  end
68
90
  rescue TypeError => exception
69
- Result::Failure.new(method_value:, return_value:, symbolic_call:)
91
+ Result::Failure.new(symbolic_call:, return_value:, exception:)
70
92
  end
71
93
 
72
- # not ensure method_value
73
- rescue NoMethodError => exception
74
- stats.skip += 1
75
- Result::Skip.new(method_value:, exception:)
94
+ # not ensure symbolic_call
95
+ rescue NoMethodError, NotImplementedError => exception
96
+ Result::Skip.new(symbolic_call:, exception:)
76
97
  rescue NameError => e
98
+ RaaP.logger.error("[#{e.class}] #{e.detailed_message}")
77
99
  msg = e.name.nil? ? '' : "for `#{BindCall.to_s(e.receiver)}::#{e.name}`"
78
- RaaP.logger.error("Implementation is not found #{msg} maybe.")
100
+ RaaP.logger.warn("Implementation is not found #{msg} maybe.")
101
+ RaaP.logger.debug(e.backtrace&.join("\n"))
102
+ stats.break = true
79
103
  throw :break
80
- rescue NotImplementedError => exception
81
- stats.skip += 1
82
- Result::Skip.new(method_value:, exception:)
83
104
  rescue SystemStackError => exception
84
- stats.skip += 1
85
- RaaP.logger.warn "Found recursive type definition."
86
- Result::Skip.new(method_value: nil, exception:)
105
+ RaaP.logger.info "Found recursive type definition."
106
+ Result::Skip.new(symbolic_call:, exception:)
87
107
  rescue => exception
88
- stats.exception += 1
89
- Result::Exception.new(method_value:, exception:)
108
+ Result::Exception.new(symbolic_call:, exception:)
90
109
  end
91
110
 
92
- def check_return(receiver_value:, return_value:, method_type:)
111
+ def check_return(receiver_value:, return_value:)
93
112
  if BindCall.is_a?(receiver_value, Module)
94
113
  if BindCall.is_a?(return_type, ::RBS::Types::ClassSingleton)
95
114
  # ::RBS::Test::TypeCheck cannot support to check singleton class
96
- return receiver_value == return_value
115
+ if receiver_value == return_value
116
+ [:success]
117
+ else
118
+ [:failure]
119
+ end
97
120
  end
121
+
98
122
  self_class = receiver_value
99
123
  instance_class = receiver_value
100
124
  else
@@ -102,23 +126,27 @@ module RaaP
102
126
  instance_class = BindCall.class(receiver_value)
103
127
  end
104
128
  type_check = ::RBS::Test::TypeCheck.new(
105
- self_class: self_class,
106
- instance_class: instance_class,
129
+ self_class:,
130
+ instance_class:,
107
131
  class_class: Module,
108
132
  builder: RBS.builder,
109
133
  sample_size: 100,
110
134
  unchecked_classes: []
111
135
  )
112
136
  begin
113
- type_check.value(return_value, return_type)
137
+ if type_check.value(return_value, return_type)
138
+ [:success]
139
+ else
140
+ [:failure]
141
+ end
114
142
  rescue => e
115
- $stderr.puts "Type check fail by `(#{e.class}) #{e.message}`"
116
- false
143
+ RaaP.logger.debug("Type check fail by `(#{e.class}) #{e.message}`")
144
+ [:exception, e]
117
145
  end
118
146
  end
119
147
 
120
148
  def return_type
121
- method_type.rbs.type.return_type
149
+ @method_type.rbs.type.return_type
122
150
  end
123
151
  end
124
152
  end
@@ -16,25 +16,32 @@ module RaaP
16
16
  else
17
17
  raise "bad method #{method}"
18
18
  end
19
- ts = TypeSubstitution.new(type_params_decl + rbs.type_params, type_args)
19
+
20
+ params = (type_params_decl + rbs.type_params).uniq
21
+ ts = TypeSubstitution.new(params, type_args)
20
22
 
21
23
  @rbs = ts.method_type_sub(rbs, self_type:, instance_type:, class_type:)
22
24
  @fun_type = FunctionType.new(@rbs.type)
23
25
  end
24
26
 
25
- def pick_arguments(size: 10, eval: true)
26
- args, kwargs = @fun_type.pick_arguments(size: size, eval:)
27
- block = pick_block(size: size, eval:)
27
+ def pick_arguments(size: 10)
28
+ SymbolicCaller.new(arguments_to_symbolic_call(size:)).eval
29
+ end
30
+
31
+ def arguments_to_symbolic_call(size: 10)
32
+ args, kwargs = @fun_type.arguments_to_symbolic_call(size:)
33
+ block = pick_block(size:)
28
34
 
29
35
  [args, kwargs, block]
30
36
  end
31
37
 
32
- def pick_block(size: 10, eval: true)
38
+ def pick_block(size: 10)
33
39
  block = @rbs.block
34
40
  return nil if block.nil?
35
41
  return nil if (block.required == false) && [true, false].sample
36
42
 
37
- Proc.new { Type.new(block.type.return_type).pick(size:, eval:) }
43
+ fixed_return_value = Type.new(block.type.return_type).pick(size:)
44
+ Proc.new { fixed_return_value }
38
45
  end
39
46
 
40
47
  def check_return(return_value)
data/lib/raap/minitest.rb CHANGED
@@ -11,9 +11,13 @@ module RaaP
11
11
  method_type = RaaP::MethodType.new(type)
12
12
  size_step.each do |size|
13
13
  # TODO assert_send_type
14
- args, kwargs, _block = method_type.pick_arguments(size: size)
14
+ args, kwargs, _block = method_type.pick_arguments(size:)
15
15
  return_value = yield(*args, **kwargs)
16
- assert method_type.check_return(return_value), "return value: #{BindCall.inspect(return_value)}[#{BindCall.class(return_value)}] is not match with `#{method_type.rbs.type.return_type}`"
16
+ i = BindCall.inspect(return_value)
17
+ c = BindCall.class(return_value)
18
+ r = method_type.rbs.type.return_type
19
+ msg = "return value: #{i}[#{c}] is not match with `#{r}`"
20
+ assert method_type.check_return(return_value), msg
17
21
  end
18
22
  else
19
23
  # forall("Integer", "String") { |int, str| Foo.new.int_str(int, str) }
@@ -24,7 +28,7 @@ module RaaP
24
28
  end
25
29
  end
26
30
  size_step.each do |size|
27
- values = types.map { |type| type.pick(size: size) }
31
+ values = types.map { |t| t.pick(size:) }
28
32
  assert yield(*values)
29
33
  end
30
34
  end
data/lib/raap/rbs.rb CHANGED
@@ -19,5 +19,24 @@ module RaaP
19
19
 
20
20
  ::RBS::Parser.parse_type(type, require_eof: true) or raise
21
21
  end
22
+
23
+ def self.parse_method_type(method_type)
24
+ raise ArgumentError, "empty method type" if method_type == ""
25
+
26
+ ::RBS::Parser.parse_method_type(method_type, require_eof: true) or raise
27
+ end
28
+
29
+ def self.find_alias_decl(type_name, method_name)
30
+ env.class_decls[type_name].decls.each do |d|
31
+ d.decl.members.each do |member|
32
+ case member
33
+ when ::RBS::AST::Members::Alias
34
+ return member if member.new_name == method_name
35
+ end
36
+ end
37
+ end
38
+
39
+ nil
40
+ end
22
41
  end
23
42
  end
data/lib/raap/result.rb CHANGED
@@ -2,17 +2,98 @@
2
2
 
3
3
  module RaaP
4
4
  module Result
5
- module CalledStr
5
+ module ReturnValueWithType
6
+ def return_value_with_type
7
+ return_type = return_value_to_type(return_value)
8
+ type = if return_type.empty? || return_type == 'nil'
9
+ ''
10
+ else
11
+ "[#{return_type}]"
12
+ end
13
+ "#{BindCall.inspect(return_value)}#{type}"
14
+ end
15
+
16
+ private
17
+
18
+ def return_value_to_type(val)
19
+ case val
20
+ when nil
21
+ 'nil'
22
+ when true, false
23
+ "bool"
24
+ when Array
25
+ elem = if val.empty?
26
+ 'untyped'
27
+ else
28
+ return_value_to_type(val.first)
29
+ end
30
+ "Array[#{elem}]"
31
+ when Hash
32
+ key = val.empty? ? 'untyped' : return_value_to_type(val.keys.first)
33
+ value = val.empty? ? 'untyped' : return_value_to_type(val.values.first)
34
+ "Hash[#{key}, #{value}]"
35
+ when Enumerator
36
+ elem =
37
+ begin
38
+ return_value_to_type(val.peek)
39
+ rescue StandardError
40
+ 'untyped'
41
+ end
42
+ ret =
43
+ if val.size == Float::INFINITY
44
+ 'bot'
45
+ else
46
+ begin
47
+ val.each {}
48
+ .tap { val.rewind }
49
+ .then { return_value_to_type(_1) }
50
+ rescue StandardError
51
+ 'untyped'
52
+ end
53
+ end
54
+ "Enumerator[#{elem}, #{ret}]"
55
+ else
56
+ "#{BindCall.class(val)}"
57
+ end
58
+ rescue StandardError => e
59
+ RaaP.logger.debug("[#{e.class}] #{e.exception.detailed_message}")
60
+ RaaP.logger.debug(e.exception.backtrace&.join("\n"))
61
+ "raised #{e.class} with check return_type"
62
+ end
63
+ end
64
+
65
+ class Success < Data.define(:symbolic_call, :return_value)
66
+ include ReturnValueWithType
67
+
6
68
  def called_str
7
- "#{method_value.call_str} -> #{return_value.inspect}[#{return_value.class}]"
69
+ scr = SymbolicCaller.new(symbolic_call)
70
+ "#{scr.call_str} -> #{return_value_with_type}"
8
71
  end
9
72
  end
10
73
 
11
- Success = Data.define(:method_value, :return_value)
12
- Success.include CalledStr
13
- Failure = Data.define(:method_value, :return_value, :symbolic_call)
14
- Failure.include CalledStr
15
- Skip = Data.define(:method_value, :exception)
16
- Exception = Data.define(:method_value, :exception)
74
+ class Failure < Data.define(:symbolic_call, :return_value, :exception)
75
+ include ReturnValueWithType
76
+
77
+ def initialize(exception: nil, **)
78
+ super
79
+ end
80
+
81
+ def called_str
82
+ scr = SymbolicCaller.new(symbolic_call)
83
+ return_type =
84
+ if exception
85
+ "raised #{exception.class}"
86
+ else
87
+ return_value_with_type
88
+ end
89
+ "#{scr.call_str} -> #{return_type}"
90
+ end
91
+ end
92
+
93
+ class Skip < Data.define(:symbolic_call, :exception)
94
+ end
95
+
96
+ class Exception < Data.define(:symbolic_call, :exception)
97
+ end
17
98
  end
18
99
  end
data/lib/raap/sized.rb CHANGED
@@ -4,6 +4,7 @@ module RaaP
4
4
  class Sized
5
5
  def initialize(&block)
6
6
  raise LocalJumpError, "no block given" unless block
7
+
7
8
  @block = block
8
9
  @such_that = nil
9
10
  end
@@ -28,6 +29,7 @@ module RaaP
28
29
  picked = yield(skip)
29
30
  such_that = @such_that
30
31
  return picked if such_that.nil? || such_that.call(picked)
32
+
31
33
  skip += 1
32
34
  raise "too many skips" unless skip < 100
33
35
  end
@@ -18,6 +18,7 @@ module RaaP
18
18
  class SymbolicCaller
19
19
  class Var
20
20
  attr_reader :name
21
+
21
22
  def initialize(name)
22
23
  @name = name
23
24
  end
@@ -32,9 +33,11 @@ module RaaP
32
33
  end
33
34
 
34
35
  attr_reader :symbolic_call
36
+ attr_reader :allow_private
35
37
 
36
- def initialize(symbolic_call)
38
+ def initialize(symbolic_call, allow_private: false)
37
39
  @symbolic_call = symbolic_call
40
+ @allow_private = allow_private
38
41
  end
39
42
 
40
43
  def eval
@@ -43,13 +46,23 @@ module RaaP
43
46
  end
44
47
  end
45
48
 
46
- def walk(&)
47
- _walk(@symbolic_call, &)
49
+ def call_str
50
+ symbolic_call => [:call, receiver, Symbol => method_name, Array => args, Hash => kwargs, block]
51
+ receiver = try_eval(receiver)
52
+ args, kwargs, block = try_eval([args, kwargs, block])
53
+
54
+ a = []
55
+ a << args.map(&BindCall.method(:inspect)).join(', ') if !args.empty?
56
+ a << kwargs.map { |k, v| "#{k}: #{BindCall.inspect(v)}" }.join(', ') if !kwargs.empty?
57
+ argument_str = a.join(', ')
58
+ block_str = block ? "{ }" : nil
59
+
60
+ "#{BindCall.inspect(receiver)}.#{method_name}(#{argument_str})#{block_str}"
48
61
  end
49
62
 
50
63
  def to_lines
51
64
  [].tap do |lines|
52
- walk do |symbolic_call|
65
+ walk do |symbolic_call, is_last|
53
66
  symbolic_call => [:call, receiver_value, method_name, args, kwargs, block]
54
67
 
55
68
  is_mod = receiver_value.is_a?(Module)
@@ -60,7 +73,7 @@ module RaaP
60
73
  var_eq = "#{var} = "
61
74
  receiver = ''
62
75
  when BindCall.instance_of?(receiver_value, Var)
63
- var_eq = ""
76
+ var_eq = "#{receiver_value} = "
64
77
  var = Var.new(receiver_value.name)
65
78
  receiver = var + '.'
66
79
  when is_mod
@@ -68,22 +81,32 @@ module RaaP
68
81
  var_eq = "#{var} = "
69
82
  receiver = receiver_value.name + '.'
70
83
  else
71
- var_eq = ""
72
- receiver = if printable?(receiver_value)
73
- var = Var.new(printable(receiver_value))
74
- var + '.'
75
- else
76
- var = Var.new(var_name(receiver_value.class))
77
- var + '.'
78
- end
84
+ var_eq = ''
85
+ receiver = Var.new(printable(receiver_value)) + '.'
79
86
  end
80
87
 
88
+ var_eq = '' if is_last
89
+
81
90
  arguments = []
82
- arguments << args.map { |a| printable(a) } if !args.empty?
83
- arguments << kwargs.map{|k,v| "#{k}: #{printable(v)}" }.join(', ') if !kwargs.empty?
91
+ if !args.empty?
92
+ # The reproduction code is kept short and executable.
93
+ if [Value::Interface, Value::Intersection].include?(receiver_value)
94
+ if args.all? { |a| a.respond_to?(:free_variables) } && !args.all? { |a| a.free_variables.empty? }
95
+ # FIXME: Type arguments are not yet supported.
96
+ args.each do |a|
97
+ lines << "# Free variables: #{a.free_variables.to_a.join(', ')}"
98
+ end
99
+ end
100
+ arguments << args.map { |a| printable(a.to_s) }
101
+ else
102
+ arguments << args.map { |a| printable(a) }
103
+ end
104
+ end
105
+ arguments << kwargs.map { |k, v| "#{k}: #{printable(v)}" }.join(', ') if !kwargs.empty?
84
106
  block_str = block ? " { }" : ""
85
107
 
86
- lines << "#{var_eq}#{receiver}#{method_name}(#{arguments.join(', ')})#{block_str}"
108
+ line = "#{var_eq}#{receiver}#{method_name}(#{arguments.join(', ')})#{block_str}"
109
+ lines << line
87
110
 
88
111
  var
89
112
  end
@@ -92,22 +115,31 @@ module RaaP
92
115
 
93
116
  private
94
117
 
95
- def _walk(symbolic_call, &block)
118
+ def try_eval(symbolic_call)
119
+ SymbolicCaller.new(symbolic_call).eval
120
+ rescue RuntimeError, NotImplementedError
121
+ symbolic_call
122
+ end
123
+
124
+ def walk(&)
125
+ _walk(@symbolic_call, true, &)
126
+ end
127
+
128
+ def _walk(symbolic_call, is_last, &block)
96
129
  return symbolic_call if BindCall::instance_of?(symbolic_call, BasicObject)
97
130
  return symbolic_call if !BindCall.respond_to?(symbolic_call, :deconstruct) && !BindCall.respond_to?(symbolic_call, :deconstruct_keys)
98
131
 
99
132
  case symbolic_call
100
133
  in [:call, receiver, Symbol => method_name, Array => args, Hash => kwargs, b]
101
- receiver = _walk(receiver, &block)
102
- args = _walk(args, &block) if !args.empty?
103
- kwargs = _walk(kwargs, &block) if !kwargs.empty?
104
- block.call [:call, receiver, method_name, args, kwargs, b]
105
- in Var
106
- symbolic_call.name
134
+ receiver = _walk(receiver, false, &block)
135
+ args = _walk(args, false, &block) if !args.empty?
136
+ kwargs = _walk(kwargs, false, &block) if !kwargs.empty?
137
+ block.call [:call, receiver, method_name, args, kwargs, b], is_last
107
138
  in Array
108
- symbolic_call.map { |sc| _walk(sc, &block) }
139
+ symbolic_call.map { |sc| _walk(sc, false, &block) }
109
140
  in Hash
110
- symbolic_call.transform_values { |sc| _walk(sc, &block) }
141
+ symbolic_call.transform_keys { |k| _walk(k, false, &block) }
142
+ .transform_values! { |v| _walk(v, false, &block) }
111
143
  else
112
144
  symbolic_call
113
145
  end
@@ -115,35 +147,39 @@ module RaaP
115
147
 
116
148
  def eval_one(symbolic_call)
117
149
  symbolic_call => [:call, receiver_value, method_name, args, kwargs, block]
118
- BindCall.public_send(receiver_value, method_name, *args, **kwargs, &block)
150
+ if @allow_private
151
+ receiver_value.__send__(method_name, *args, **kwargs, &block)
152
+ else
153
+ BindCall.public_send(receiver_value, method_name, *args, **kwargs, &block)
154
+ end
119
155
  end
120
156
 
121
157
  def var_name(mod)
122
158
  printable(mod).gsub('::', '_').downcase
123
159
  end
124
160
 
125
- def printable?(obj)
126
- case obj
127
- when Symbol, Integer, Float, Regexp, nil, true, false, String, Module, Var
128
- true
129
- else
130
- false
131
- end
132
- end
133
-
134
161
  def printable(obj)
135
162
  case obj
136
163
  when Var
137
164
  obj.name
138
165
  # Object from which it can get strings that can be eval with `#inspect`
139
- when Symbol, Integer, Float, Regexp, nil, true, false
166
+ when Symbol, Integer, Float, Regexp, Range, nil, true, false
140
167
  obj.inspect
141
168
  when String
142
169
  obj.inspect.gsub('"', "'") or raise
170
+ when Hash
171
+ hash_body = obj.map do |k, v|
172
+ ks = printable(k)
173
+ vs = printable(v)
174
+ ks.start_with?(':') ? "#{ks[1..-1]}: #{vs}" : "#{ks} => #{vs}"
175
+ end
176
+ "{#{hash_body.join(', ')}}"
177
+ when Array
178
+ "[#{obj.map { |o| printable(o) }.join(', ')}]"
143
179
  when Module
144
- BindCall.name(obj) or raise
180
+ BindCall.name(obj) or raise "`#class` method returns nil"
145
181
  else
146
- var_name(obj.class)
182
+ var_name(BindCall.class(obj))
147
183
  end
148
184
  end
149
185
  end