raap 0.2.0 → 0.4.0

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