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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +7 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/LICENSE.txt +21 -0
- data/README.md +109 -0
- data/Rakefile +8 -0
- data/Steepfile +4 -0
- data/exe/raap +3 -0
- data/lib/raap/bind_call.rb +29 -0
- data/lib/raap/cli.rb +239 -0
- data/lib/raap/function_type.rb +63 -0
- data/lib/raap/method_property.rb +122 -0
- data/lib/raap/method_type.rb +40 -0
- data/lib/raap/method_value.rb +38 -0
- data/lib/raap/rbs.rb +23 -0
- data/lib/raap/result.rb +18 -0
- data/lib/raap/sized.rb +37 -0
- data/lib/raap/symbolic_caller.rb +128 -0
- data/lib/raap/type.rb +310 -0
- data/lib/raap/type_substitution.rb +53 -0
- data/lib/raap/value/bottom.rb +8 -0
- data/lib/raap/value/interface.rb +50 -0
- data/lib/raap/value/intersection.rb +35 -0
- data/lib/raap/value/module.rb +17 -0
- data/lib/raap/value/top.rb +8 -0
- data/lib/raap/value/variable.rb +14 -0
- data/lib/raap/value/void.rb +8 -0
- data/lib/raap/value.rb +13 -0
- data/lib/raap/version.rb +5 -0
- data/lib/raap.rb +34 -0
- data/rbs_collection.lock.yaml +68 -0
- data/rbs_collection.yaml +20 -0
- data/sig/raap.rbs +313 -0
- metadata +107 -0
@@ -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
|
data/lib/raap/result.rb
ADDED
@@ -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
|