raap 0.1.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.
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RaaP
4
+ class MethodProperty
5
+ class Stats < Struct.new(:success, :skip, :exception)
6
+ def initialize(success: 0, skip: 0, exception: 0)
7
+ super
8
+ end
9
+ end
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:)
18
+ @receiver_type = receiver_type
19
+ @method_name = method_name
20
+ @method_type = method_type
21
+ @size_step = size_step
22
+ @timeout = timeout
23
+ end
24
+
25
+ def run
26
+ stats = Stats.new
27
+ if receiver_type.type.to_s == 'String' && method_name == :initialize
28
+ return stats
29
+ end
30
+ begin
31
+ Timeout.timeout(@timeout) do
32
+ catch(:break) do
33
+ @size_step.each do |size|
34
+ yield call(size: size, stats: stats)
35
+ end
36
+ end
37
+ end
38
+ rescue Timeout::Error => exception
39
+ RaaP.logger.warn "Timeout: #{exception}"
40
+ end
41
+ stats
42
+ end
43
+
44
+ private
45
+
46
+ def call(size:, stats:)
47
+ receiver_value = receiver_type.pick(size: size, eval: false)
48
+ arguments = method_type.pick_arguments(size: size, eval: false)
49
+ method_value = MethodValue.new(receiver_value:, arguments:, method_name:, size:)
50
+ symbolic_call = method_value.to_symbolic_call
51
+ check = false
52
+ if return_type.instance_of?(::RBS::Types::Bases::Bottom)
53
+ begin
54
+ return_value = SymbolicCaller.new(symbolic_call).eval
55
+ rescue => e
56
+ check = true
57
+ return_value = Value::Bottom.new
58
+ end
59
+ else
60
+ return_value = SymbolicCaller.new(symbolic_call).eval
61
+ check = check_return(receiver_value:, return_value:, method_type:)
62
+ end
63
+ if check
64
+ stats.success += 1
65
+ Result::Success.new(method_value:, return_value:)
66
+ else
67
+ Result::Failure.new(method_value:, return_value:, symbolic_call:)
68
+ end
69
+ rescue NoMethodError => exception
70
+ stats.skip += 1
71
+ Result::Skip.new(method_value:, exception:)
72
+ rescue NameError => e
73
+ msg = e.name.nil? ? '' : "for `#{BindCall.to_s(e.receiver)}::#{e.name}`"
74
+ RaaP.logger.error("Implementation is not found #{msg} maybe.")
75
+ throw :break
76
+ rescue NotImplementedError => exception
77
+ stats.skip += 1
78
+ Result::Skip.new(method_value:, exception:)
79
+ rescue SystemStackError => exception
80
+ stats.skip += 1
81
+ RaaP.logger.warn "Found recursive type definition."
82
+ Result::Skip.new(method_value: nil, exception:)
83
+ rescue TypeError => exception
84
+ Result::Failure.new(method_value:, return_value:, symbolic_call:)
85
+ rescue => exception
86
+ stats.exception += 1
87
+ Result::Exception.new(method_value:, exception:)
88
+ end
89
+
90
+ def check_return(receiver_value:, return_value:, method_type:)
91
+ if BindCall.is_a?(receiver_value, Module)
92
+ if BindCall.is_a?(return_type, ::RBS::Types::ClassSingleton)
93
+ # ::RBS::Test::TypeCheck cannot support to check singleton class
94
+ return receiver_value == return_value
95
+ end
96
+ self_class = receiver_value
97
+ instance_class = receiver_value
98
+ else
99
+ self_class = BindCall.class(receiver_value)
100
+ instance_class = BindCall.class(receiver_value)
101
+ end
102
+ type_check = ::RBS::Test::TypeCheck.new(
103
+ self_class: self_class,
104
+ instance_class: instance_class,
105
+ class_class: Module,
106
+ builder: RBS.builder,
107
+ sample_size: 1,
108
+ unchecked_classes: []
109
+ )
110
+ begin
111
+ type_check.value(return_value, return_type)
112
+ rescue => e
113
+ $stderr.puts "Type check fail by `(#{e.class}) #{e.message}`"
114
+ false
115
+ end
116
+ end
117
+
118
+ def return_type
119
+ method_type.rbs.type.return_type
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RaaP
4
+ class MethodType
5
+ attr_reader :rbs
6
+
7
+ def initialize(method, type_params_decl: [], type_args: [], self_type: nil, instance_type: nil, class_type: nil)
8
+ rbs =
9
+ case method
10
+ when ""
11
+ raise ArgumentError, "method type is empty"
12
+ when String
13
+ ::RBS::Parser.parse_method_type(method, require_eof: true) or raise
14
+ when ::RBS::MethodType
15
+ method
16
+ else
17
+ raise "bad method #{method}"
18
+ end
19
+ ts = TypeSubstitution.new(type_params_decl + rbs.type_params, type_args)
20
+
21
+ @rbs = ts.method_type_sub(rbs, self_type:, instance_type:, class_type:)
22
+ @fun_type = FunctionType.new(@rbs.type)
23
+ end
24
+
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:)
28
+
29
+ [args, kwargs, block]
30
+ end
31
+
32
+ def pick_block(size: 10, eval: true)
33
+ block = @rbs.block
34
+ return nil if block.nil?
35
+ return nil if (block.required == false) && [true, false].sample
36
+
37
+ Proc.new { Type.new(block.type.return_type).pick(size:, eval:) }
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RaaP
4
+ class MethodValue < Data.define(
5
+ :receiver_value,
6
+ :arguments,
7
+ :method_name,
8
+ :size
9
+ )
10
+ def to_symbolic_call
11
+ args, kwargs, block = arguments
12
+ [:call, receiver_value, method_name, args, kwargs, block]
13
+ end
14
+
15
+ def call_str
16
+ r = SymbolicCaller.new(receiver_value).eval
17
+ "#{BindCall.inspect(r)}.#{method_name}(#{argument_str})#{block_str}"
18
+ end
19
+
20
+ private
21
+
22
+ def argument_str
23
+ args, kwargs, _ = SymbolicCaller.new(arguments).eval
24
+
25
+ r = []
26
+ r << args.map(&:inspect).join(', ') if !args.empty?
27
+ r << kwargs.map { |k ,v| "#{k}: #{BindCall.inspect(v)}" }.join(', ') if !kwargs.empty?
28
+ r.join(', ')
29
+ end
30
+
31
+ def block_str
32
+ _, _, block = SymbolicCaller.new(arguments).eval
33
+ if block
34
+ "{ }"
35
+ end
36
+ end
37
+ end
38
+ end
data/lib/raap/rbs.rb ADDED
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RaaP
4
+ module RBS
5
+ def self.builder
6
+ @builder ||= ::RBS::DefinitionBuilder.new(env: env.resolve_type_names)
7
+ end
8
+
9
+ def self.env
10
+ @env ||= ::RBS::Environment.from_loader(loader)
11
+ end
12
+
13
+ def self.loader
14
+ @loader ||= ::RBS::CLI::LibraryOptions.new.loader
15
+ end
16
+
17
+ def self.parse_type(type)
18
+ raise ArgumentError, "empty type" if type == ""
19
+
20
+ ::RBS::Parser.parse_type(type, require_eof: true) or raise
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RaaP
4
+ module Result
5
+ module CalledStr
6
+ def called_str
7
+ "#{method_value.call_str} -> #{return_value.inspect}[#{return_value.class}]"
8
+ end
9
+ end
10
+
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)
17
+ end
18
+ end
data/lib/raap/sized.rb ADDED
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RaaP
4
+ class Sized
5
+ def initialize(&block)
6
+ raise LocalJumpError, "no block given" unless block
7
+ @block = block
8
+ @such_that = nil
9
+ end
10
+
11
+ def pick(size:)
12
+ such_that_loop do |skip|
13
+ @block.call(size + skip)
14
+ end
15
+ end
16
+
17
+ def such_that(&block)
18
+ @such_that = block
19
+ self
20
+ end
21
+ alias when such_that
22
+
23
+ private
24
+
25
+ def such_that_loop
26
+ skip = 0
27
+ while skip < 100
28
+ picked = yield(skip)
29
+ such_that = @such_that
30
+ return picked if such_that.nil? || such_that.call(picked)
31
+ skip += 1
32
+ raise "too many skips" unless skip < 100
33
+ end
34
+ raise "never reached"
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RaaP
4
+ # sc = SymbolicCaller.new(
5
+ # [:call,
6
+ # [:call, C, :new, [], {
7
+ # a: [:call, A, :new, [], {}, nil],
8
+ # b: [:call, B, :new, [], {}, nil] }, nil],
9
+ # :run, [], {}, nil]
10
+ # sc.eval #=> 123
11
+ #
12
+ # sc.to_lines
13
+ # ↓
14
+ # a = A.new(1)
15
+ # b = B.new(b: 'bbb')
16
+ # c = C.new(a: a, b: b)
17
+ # c.run() { }
18
+ class SymbolicCaller
19
+ attr_reader :symbolic_call
20
+
21
+ def initialize(symbolic_call)
22
+ @symbolic_call = symbolic_call
23
+ end
24
+
25
+ def eval
26
+ walk do |symbolic_call|
27
+ eval_one(symbolic_call)
28
+ end
29
+ end
30
+
31
+ def walk(&)
32
+ _walk(@symbolic_call, &)
33
+ end
34
+
35
+ def to_lines
36
+ [].tap do |lines|
37
+ walk do |symbolic_call|
38
+ symbolic_call => [:call, receiver_value, method_name, args, kwargs, block]
39
+
40
+ is_mod = receiver_value.is_a?(Module)
41
+
42
+ if receiver_value == Kernel
43
+ var = "#{var_name(method_name)} = "
44
+ receiver = ''
45
+ elsif is_mod
46
+ var = "#{var_name(receiver_value)} = "
47
+ receiver = receiver_value.name + '.'
48
+ else
49
+ var = ""
50
+ receiver = if printable?(receiver_value)
51
+ printable(receiver_value) + '.'
52
+ else
53
+ var_name(receiver_value.class) + '.'
54
+ end
55
+ end
56
+
57
+ arguments = []
58
+ arguments << args.map { |a| printable(a) } if !args.empty?
59
+ arguments << kwargs.map{|k,v| "#{k}: #{printable(v)}" }.join(', ') if !kwargs.empty?
60
+ block_str = block ? " { }" : ""
61
+
62
+ lines << "#{var}#{receiver}#{method_name}(#{arguments.join(', ')})#{block_str}"
63
+
64
+ eval_one(symbolic_call)
65
+ end
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ def _walk(symbolic_call, &block)
72
+ return symbolic_call if BindCall::instance_of?(symbolic_call, BasicObject)
73
+ return symbolic_call if !BindCall.respond_to?(symbolic_call, :deconstruct) && !BindCall.respond_to?(symbolic_call, :deconstruct_keys)
74
+
75
+ case symbolic_call
76
+ in [:call, receiver, Symbol => method_name, Array => args, Hash => kwargs, b]
77
+ receiver = _walk(receiver, &block)
78
+ args = _walk(args, &block) if !args.empty?
79
+ kwargs = _walk(kwargs, &block) if !kwargs.empty?
80
+ block.call [:call, receiver, method_name, args, kwargs, b]
81
+ in Array
82
+ symbolic_call.map { |sc| _walk(sc, &block) }
83
+ in Hash
84
+ symbolic_call.transform_values { |sc| _walk(sc, &block) }
85
+ else
86
+ symbolic_call
87
+ end
88
+ end
89
+
90
+ def eval_one(symbolic_call)
91
+ symbolic_call => [:call, receiver_value, method_name, args, kwargs, block]
92
+
93
+ begin
94
+ receiver_value.__send__(method_name, *args, **kwargs, &block)
95
+ rescue => e
96
+ RaaP.logger.error("Cannot eval symbolic call #{symbolic_call} with #{e.class}")
97
+ raise
98
+ end
99
+ end
100
+
101
+ def var_name(mod)
102
+ printable(mod).gsub('::', '_').downcase
103
+ end
104
+
105
+ def printable?(obj)
106
+ case obj
107
+ when Symbol, Integer, Float, Regexp, nil, true, false, String, Module
108
+ true
109
+ else
110
+ false
111
+ end
112
+ end
113
+
114
+ def printable(obj)
115
+ case obj
116
+ # Object from which it can get strings that can be eval with `#inspect`
117
+ when Symbol, Integer, Float, Regexp, nil, true, false
118
+ obj.inspect
119
+ when String
120
+ obj.inspect.gsub('"', "'") or raise
121
+ when Module
122
+ BindCall.name(obj) or raise
123
+ else
124
+ var_name(obj.class)
125
+ end
126
+ end
127
+ end
128
+ end