raap 0.1.0

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