argtrace 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rubocop.yml +10 -0
- data/Gemfile +15 -0
- data/LICENSE.txt +21 -0
- data/README.md +53 -0
- data/Rakefile +16 -0
- data/argtrace.gemspec +37 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/lib/argtrace.rb +12 -0
- data/lib/argtrace/autorun.rb +3 -0
- data/lib/argtrace/default.rb +32 -0
- data/lib/argtrace/signature.rb +233 -0
- data/lib/argtrace/tracer.rb +467 -0
- data/lib/argtrace/typelib.rb +230 -0
- data/lib/argtrace/version.rb +5 -0
- metadata +62 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7398f53bb9b94105fc990221979803eaceebffa073e7487b16b558fc65755af6
|
4
|
+
data.tar.gz: 0744ce1ba76244daf8d442465e6ac7828cdf07b51f486b4b661c619f333419cb
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 274187675f07b8141b2ddbf05be0d344d5ce885cfc945c0d76912b2b77acfcba447eedaa1cc0dba0b671d1dee8b1dbe79fc4772b1747a8f946952a98749ce5f4
|
7
|
+
data.tar.gz: 3e05fa0e9067ec243aca02b4e94a016e9e193cd26d0bd321295dd1c8159926f7a48274eaee52d85121ca60eeb9d5b3dcfef7505f4ddc13fba74c6cd894c0f991
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
# Specify your gem's dependencies in argtrace.gemspec
|
6
|
+
gemspec
|
7
|
+
|
8
|
+
gem "rake", "~> 13.0"
|
9
|
+
|
10
|
+
gem "minitest", "~> 5.0"
|
11
|
+
|
12
|
+
gem "rubocop", "~> 0.80"
|
13
|
+
|
14
|
+
gem "ruby-debug-ide"
|
15
|
+
gem "debase"
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2021 Riki Ishikawa
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# Argtrace
|
2
|
+
|
3
|
+
Argtrace is a Ruby MRI method type analyser.
|
4
|
+
|
5
|
+
Argtrace uses TracePoint and traces all of method calling,
|
6
|
+
peeks actual type of parameters and return value,
|
7
|
+
and finally summarize them into RBS format.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
gem 'argtrace'
|
15
|
+
```
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
$ bundle install
|
20
|
+
|
21
|
+
Or install it yourself as:
|
22
|
+
|
23
|
+
$ gem install argtrace
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
### 1. Explicit trace
|
28
|
+
```ruby
|
29
|
+
require 'argtrace'
|
30
|
+
Argtrace::Default.main(rbs_path: "sig.rbs")
|
31
|
+
|
32
|
+
... (YOUR PROGRAM HERE) ...
|
33
|
+
```
|
34
|
+
RBS file is saved as "sig.rbs" in current directory.
|
35
|
+
|
36
|
+
### 2. Implicit trace
|
37
|
+
```console
|
38
|
+
$ ruby -r argtrace/autorun YOUR_PROGRAM_HERE.rb
|
39
|
+
```
|
40
|
+
RBS file is saved as "sig.rbs" in current directory.
|
41
|
+
|
42
|
+
### Restriction
|
43
|
+
Argtrace cannot work with C-extension,
|
44
|
+
because TracePoint doesn't provide feature to access arguments of calls into C-extension.
|
45
|
+
|
46
|
+
|
47
|
+
## Contributing
|
48
|
+
|
49
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/jljse/argtrace.
|
50
|
+
|
51
|
+
## License
|
52
|
+
|
53
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
require "rake/testtask"
|
5
|
+
|
6
|
+
Rake::TestTask.new(:test) do |t|
|
7
|
+
t.libs << "test"
|
8
|
+
t.libs << "lib"
|
9
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
10
|
+
end
|
11
|
+
|
12
|
+
require "rubocop/rake_task"
|
13
|
+
|
14
|
+
RuboCop::RakeTask.new
|
15
|
+
|
16
|
+
task default: %i[test rubocop]
|
data/argtrace.gemspec
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/argtrace/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "argtrace"
|
7
|
+
spec.version = Argtrace::VERSION
|
8
|
+
spec.authors = ["Riki Ishikawa"]
|
9
|
+
spec.email = ["riki.ishikawa@gmail.com"]
|
10
|
+
|
11
|
+
spec.summary = "trace arguments of method calls or block calls."
|
12
|
+
spec.description = "library to trace arguments of method calls or block calls with TracePoint and do callback."
|
13
|
+
spec.homepage = "https://github.com/jljse/argtrace/"
|
14
|
+
spec.license = "MIT"
|
15
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.7.0")
|
16
|
+
|
17
|
+
# spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
|
18
|
+
|
19
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
20
|
+
spec.metadata["source_code_uri"] = "https://github.com/jljse/argtrace/"
|
21
|
+
# spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
|
22
|
+
|
23
|
+
# Specify which files should be added to the gem when it is released.
|
24
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
25
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
26
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
|
27
|
+
end
|
28
|
+
spec.bindir = "exe"
|
29
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
30
|
+
spec.require_paths = ["lib"]
|
31
|
+
|
32
|
+
# Uncomment to register a new dependency of your gem
|
33
|
+
# spec.add_dependency "example-gem", "~> 1.0"
|
34
|
+
|
35
|
+
# For more information and examples about making a new gem, checkout our
|
36
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
37
|
+
end
|
data/bin/console
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "argtrace"
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
require "irb"
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/lib/argtrace.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "argtrace/version"
|
4
|
+
require_relative "argtrace/signature.rb"
|
5
|
+
require_relative "argtrace/tracer.rb"
|
6
|
+
require_relative "argtrace/typelib.rb"
|
7
|
+
require_relative "argtrace/default.rb"
|
8
|
+
|
9
|
+
module Argtrace
|
10
|
+
class ArgtraceError < StandardError; end
|
11
|
+
end
|
12
|
+
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Argtrace
|
2
|
+
# Default tracing setting.
|
3
|
+
class Default
|
4
|
+
# Default tracing setting. Analyse only user sources, and output them into RBS file.
|
5
|
+
def self.main(rbs_path: "sig.rbs")
|
6
|
+
typelib = Argtrace::TypeLib.new
|
7
|
+
tracer = Argtrace::Tracer.new
|
8
|
+
|
9
|
+
tracer.set_filter do |tp|
|
10
|
+
if [:call, :return].include?(tp.event)
|
11
|
+
tracer.user_source?(tp.defined_class, tp.method_id)
|
12
|
+
else
|
13
|
+
true
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
tracer.set_notify do |ev, callinfo|
|
18
|
+
if ev == :return
|
19
|
+
typelib.learn(callinfo.signature)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
tracer.set_exit do
|
24
|
+
File.open(rbs_path, "w") do |f|
|
25
|
+
f.puts typelib.to_rbs
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
tracer.start_trace
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,233 @@
|
|
1
|
+
|
2
|
+
module Argtrace
|
3
|
+
|
4
|
+
# signature of method/block
|
5
|
+
class Signature
|
6
|
+
attr_accessor :defined_class, :method_id, :is_singleton_method, :params, :return_type
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@is_singleton_method = false
|
10
|
+
@params = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def merge(all_params, ret)
|
14
|
+
unless @params
|
15
|
+
@params = []
|
16
|
+
end
|
17
|
+
# all params (including optional / keyword etc)
|
18
|
+
for i in 0...all_params.size
|
19
|
+
if i == @params.size
|
20
|
+
@params << all_params[i] # TODO: dup
|
21
|
+
else
|
22
|
+
if @params[i].mode == all_params[i].mode &&
|
23
|
+
@params[i].name == all_params[i].name
|
24
|
+
if all_params[i].mode == :block
|
25
|
+
# TODO: buggy
|
26
|
+
# merging of block parameter type is quite tricky...
|
27
|
+
@params[i].type.merge(
|
28
|
+
all_params[i].type.params, nil)
|
29
|
+
else
|
30
|
+
@params[i].type.merge_union(all_params[i].type)
|
31
|
+
end
|
32
|
+
else
|
33
|
+
raise "signature change not supported"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
if ret
|
39
|
+
unless @return_type
|
40
|
+
@return_type = TypeUnion.new
|
41
|
+
end
|
42
|
+
@return_type.merge_union(ret)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def get_block_param
|
47
|
+
@params.find{|x| x.mode == :block}
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_s
|
51
|
+
"Signature(#{@defined_class}::#{@method_id}(" + @params.map{|x| x.to_s}.join(",") + ") => #{@return_type.to_s})"
|
52
|
+
end
|
53
|
+
|
54
|
+
def inspect
|
55
|
+
to_s
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# instance for one parameter
|
60
|
+
class Parameter
|
61
|
+
attr_accessor :mode, :name, :type
|
62
|
+
|
63
|
+
def hash
|
64
|
+
[@mode, @name, @type].hash
|
65
|
+
end
|
66
|
+
|
67
|
+
def to_s
|
68
|
+
"Parameter(#{@name}@#{@mode}:#{@type.to_s})"
|
69
|
+
end
|
70
|
+
|
71
|
+
def inspect
|
72
|
+
to_s
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Union of types (e.g. String | Integer)
|
77
|
+
class TypeUnion
|
78
|
+
attr_accessor :union;
|
79
|
+
|
80
|
+
def initialize
|
81
|
+
@union = []
|
82
|
+
end
|
83
|
+
|
84
|
+
def merge_union(other_union)
|
85
|
+
other_union.union.each do |type|
|
86
|
+
self.add(type)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def add(type)
|
91
|
+
for i in 0...@union.size
|
92
|
+
if @union[i] == type
|
93
|
+
# already in union
|
94
|
+
return
|
95
|
+
end
|
96
|
+
if @union[i].superclass_of?(type)
|
97
|
+
# already in union
|
98
|
+
return
|
99
|
+
end
|
100
|
+
if type.superclass_of?(@union[i])
|
101
|
+
# remove redundant element
|
102
|
+
@union[i] = nil
|
103
|
+
end
|
104
|
+
end
|
105
|
+
@union.compact!
|
106
|
+
@union << type
|
107
|
+
self
|
108
|
+
end
|
109
|
+
|
110
|
+
def to_s
|
111
|
+
if @union.empty?
|
112
|
+
"TypeUnion(None)"
|
113
|
+
else
|
114
|
+
"TypeUnion(" + @union.map{|x| x.to_s}.join("|") + ")"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def inspect
|
119
|
+
to_s
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# placeholder for TrueClass / FalseClass
|
124
|
+
class BooleanClass
|
125
|
+
end
|
126
|
+
|
127
|
+
# type in RBS manner
|
128
|
+
class Type
|
129
|
+
attr_accessor :data, :subdata
|
130
|
+
|
131
|
+
def initialize()
|
132
|
+
@data = nil
|
133
|
+
@subdata = nil
|
134
|
+
end
|
135
|
+
|
136
|
+
def self.new_with_type(actual_type)
|
137
|
+
ret = Type.new
|
138
|
+
if actual_type == TrueClass || actual_type == FalseClass
|
139
|
+
ret.data = BooleanClass
|
140
|
+
else
|
141
|
+
ret.data = actual_type
|
142
|
+
end
|
143
|
+
return ret
|
144
|
+
end
|
145
|
+
|
146
|
+
def self.new_with_value(actual_value)
|
147
|
+
ret = Type.new
|
148
|
+
if actual_value.is_a?(Symbol)
|
149
|
+
# use symbol as type
|
150
|
+
ret.data = actual_value
|
151
|
+
elsif true == actual_value || false == actual_value
|
152
|
+
# warn: operands of == must in this order, because of override.
|
153
|
+
# treat true and false as boolean
|
154
|
+
ret.data = BooleanClass
|
155
|
+
elsif actual_value.class == Array
|
156
|
+
# TODO: multi type array
|
157
|
+
ret.data = Array
|
158
|
+
unless actual_value.empty?
|
159
|
+
if true == actual_value.first || false == actual_value.first
|
160
|
+
ret.subdata = BooleanClass
|
161
|
+
else
|
162
|
+
ret.subdata = actual_value.first.class
|
163
|
+
end
|
164
|
+
end
|
165
|
+
else
|
166
|
+
ret.data = actual_value.class
|
167
|
+
end
|
168
|
+
return ret
|
169
|
+
end
|
170
|
+
|
171
|
+
def hash
|
172
|
+
@data.hash
|
173
|
+
end
|
174
|
+
|
175
|
+
def ==(other)
|
176
|
+
if other.class != Type
|
177
|
+
return false
|
178
|
+
end
|
179
|
+
return @data == other.data && @subdata == other.subdata
|
180
|
+
end
|
181
|
+
|
182
|
+
def eql?(other)
|
183
|
+
self.==(other)
|
184
|
+
end
|
185
|
+
|
186
|
+
# true if self(Type) includes other(Type) as type declaration
|
187
|
+
# false if self and other is same Type.
|
188
|
+
def superclass_of?(other)
|
189
|
+
if other.class != Type
|
190
|
+
raise TypeError, "parameter must be Argtrace::Type"
|
191
|
+
end
|
192
|
+
if @data.is_a?(Symbol)
|
193
|
+
return false
|
194
|
+
elsif other.data.is_a?(Symbol)
|
195
|
+
return false
|
196
|
+
elsif @data == Array && other.data == Array
|
197
|
+
# TODO: merge for Array type like:
|
198
|
+
# Array[X] | Array[Y] => Array[X|Y]
|
199
|
+
if @subdata
|
200
|
+
if other.subdata
|
201
|
+
return other.subdata < @subdata
|
202
|
+
else
|
203
|
+
return true
|
204
|
+
end
|
205
|
+
else
|
206
|
+
# if self Array is untyped, cannot replace other as declaration.
|
207
|
+
return false
|
208
|
+
end
|
209
|
+
else
|
210
|
+
return other.data < @data
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def to_s
|
215
|
+
if @data.is_a?(Symbol)
|
216
|
+
@data.inspect
|
217
|
+
elsif @data == Array
|
218
|
+
if @subdata
|
219
|
+
"Array[#{@subdata}]"
|
220
|
+
else
|
221
|
+
@data
|
222
|
+
end
|
223
|
+
else
|
224
|
+
@data.to_s
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
def inspect
|
229
|
+
to_s
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
end
|
@@ -0,0 +1,467 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
module Argtrace
|
4
|
+
|
5
|
+
# instance per method/block call
|
6
|
+
class CallInfo
|
7
|
+
attr_accessor :signature
|
8
|
+
|
9
|
+
# actual block instance
|
10
|
+
attr_accessor :block_proc
|
11
|
+
end
|
12
|
+
|
13
|
+
# call stack of tracing targets
|
14
|
+
class CallStack
|
15
|
+
def initialize
|
16
|
+
# thread.object_id => stack
|
17
|
+
@stack = Hash.new{|h,k| h[k] = []}
|
18
|
+
end
|
19
|
+
|
20
|
+
def push_callstack(callinfo)
|
21
|
+
id = Thread.current.object_id
|
22
|
+
# DEBUG:
|
23
|
+
# p "[#{id}]>>" + " "*@stack[id].size*2 + callinfo.signature.to_s
|
24
|
+
|
25
|
+
@stack[id].push callinfo
|
26
|
+
end
|
27
|
+
|
28
|
+
def pop_callstack(tp)
|
29
|
+
id = Thread.current.object_id
|
30
|
+
ent = @stack[id].pop
|
31
|
+
if ent
|
32
|
+
# DEBUG:
|
33
|
+
# p "[#{id}]<<" + " "*@stack[id].size*2 + ent.signature.to_s
|
34
|
+
|
35
|
+
if tp.method_id != ent.signature.method_id
|
36
|
+
raise <<~EOF
|
37
|
+
callstack is broken
|
38
|
+
returning by tracepoint: #{tp.defined_class}::#{tp.method_id}
|
39
|
+
top of stack: #{ent.signature.to_s}
|
40
|
+
rest of stack:
|
41
|
+
#{@stack[id].map{|x| x.signature.to_s}.join("\n ")}
|
42
|
+
EOF
|
43
|
+
end
|
44
|
+
type = TypeUnion.new
|
45
|
+
type.add(Type.new_with_value(tp.return_value))
|
46
|
+
ent.signature.return_type = type
|
47
|
+
end
|
48
|
+
return ent
|
49
|
+
end
|
50
|
+
|
51
|
+
# find callinfo which use specific block
|
52
|
+
def find_by_block_location(path, lineno)
|
53
|
+
id = Thread.current.object_id
|
54
|
+
ret = []
|
55
|
+
@stack[id].each do |info|
|
56
|
+
if info.block_proc && info.block_proc.source_location == [path, lineno]
|
57
|
+
ret << info
|
58
|
+
end
|
59
|
+
end
|
60
|
+
return ret
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# class definition related operation and result caching.
|
65
|
+
class DefinitionResolver
|
66
|
+
def initialize
|
67
|
+
# TODO:
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Main class for tracing with TracePoint.
|
72
|
+
class Tracer
|
73
|
+
attr_accessor :is_dead
|
74
|
+
|
75
|
+
def initialize()
|
76
|
+
@notify_block = nil
|
77
|
+
@callstack = CallStack.new
|
78
|
+
@tp_holder = nil
|
79
|
+
@is_dead = false
|
80
|
+
|
81
|
+
# prune_event_count > 0 while no need to notify.
|
82
|
+
# This is used to avoid undesirable signature lerning caused by error test.
|
83
|
+
@prune_event_count = 0
|
84
|
+
|
85
|
+
# cache of singleton-class => basic-class
|
86
|
+
@singleton_class_map_cache = {}
|
87
|
+
|
88
|
+
# cache of method location (klass => method_id => source_path)
|
89
|
+
@method_location_cache = Hash.new{|h, klass| h[klass] = {}}
|
90
|
+
|
91
|
+
# cache of judge result whether method is library-defined or user-defined
|
92
|
+
@ignore_paths_cache = {}
|
93
|
+
end
|
94
|
+
|
95
|
+
# entry point of trace event
|
96
|
+
def trace(tp)
|
97
|
+
if ignore_event?(tp)
|
98
|
+
return
|
99
|
+
end
|
100
|
+
|
101
|
+
if [:b_call, :b_return].include?(tp.event)
|
102
|
+
trace_block_event(tp)
|
103
|
+
else
|
104
|
+
trace_method_event(tp)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# process block call/return event
|
109
|
+
def trace_block_event(tp)
|
110
|
+
# I cannot determine the called block instance directly, so use block's location.
|
111
|
+
callinfos_with_block = @callstack.find_by_block_location(tp.path, tp.lineno)
|
112
|
+
callinfos_with_block.each do |callinfo|
|
113
|
+
block_param = callinfo.signature.get_block_param
|
114
|
+
block_param_types = get_param_types(callinfo.block_proc.parameters, tp)
|
115
|
+
# TODO: return type (but maybe, there is no demand)
|
116
|
+
block_param.type.merge(block_param_types, nil)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# process method call/return event
|
121
|
+
def trace_method_event(tp)
|
122
|
+
if [:call, :c_call].include?(tp.event)
|
123
|
+
# I don't know why but tp.parameters is different from called_method.parameters
|
124
|
+
# and called_method.parameters not work.
|
125
|
+
# called_method = get_called_method(tp)
|
126
|
+
|
127
|
+
case check_event_filter(tp)
|
128
|
+
when :prune
|
129
|
+
@prune_event_count += 1
|
130
|
+
skip_flag = true
|
131
|
+
when false
|
132
|
+
skip_flag = true
|
133
|
+
end
|
134
|
+
|
135
|
+
callinfo = CallInfo.new
|
136
|
+
signature = Signature.new
|
137
|
+
signature.defined_class = non_singleton_class(tp.defined_class)
|
138
|
+
signature.method_id = tp.method_id
|
139
|
+
signature.is_singleton_method = tp.defined_class.singleton_class?
|
140
|
+
signature.params = get_param_types(tp.parameters, tp)
|
141
|
+
callinfo.signature = signature
|
142
|
+
callinfo.block_proc = get_block_param_value(tp.parameters, tp)
|
143
|
+
|
144
|
+
@callstack.push_callstack(callinfo)
|
145
|
+
|
146
|
+
if !skip_flag && @prune_event_count == 0
|
147
|
+
# skip if it's object specific method
|
148
|
+
@notify_block.call(tp.event, callinfo) if @notify_block
|
149
|
+
end
|
150
|
+
else
|
151
|
+
case check_event_filter(tp)
|
152
|
+
when :prune
|
153
|
+
@prune_event_count -= 1
|
154
|
+
skip_flag = true
|
155
|
+
when false
|
156
|
+
skip_flag = true
|
157
|
+
end
|
158
|
+
|
159
|
+
callinfo = @callstack.pop_callstack(tp)
|
160
|
+
if callinfo
|
161
|
+
if !skip_flag && @prune_event_count == 0
|
162
|
+
@notify_block.call(tp.event, callinfo) if @notify_block
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def standard_lib_root_path
|
169
|
+
# Search for standard lib path by some method location.
|
170
|
+
# I choose Pathname#parent here.
|
171
|
+
path = get_location(Pathname, :parent)
|
172
|
+
lib_dir = File.dirname(path)
|
173
|
+
return lib_dir
|
174
|
+
end
|
175
|
+
|
176
|
+
# true if method is defined in user source
|
177
|
+
def user_source?(klass, method_id)
|
178
|
+
path = get_location(klass, method_id)
|
179
|
+
return false unless path
|
180
|
+
|
181
|
+
unless @ignore_paths_cache.key?(path)
|
182
|
+
if path.start_with?("<internal:")
|
183
|
+
# skip all ruby internal method
|
184
|
+
@ignore_paths_cache[path] = true
|
185
|
+
elsif path == "(eval)"
|
186
|
+
# skip all eval
|
187
|
+
@ignore_paths_cache[path] = true
|
188
|
+
elsif path.start_with?(standard_lib_root_path())
|
189
|
+
# skip all standard lib
|
190
|
+
@ignore_paths_cache[path] = true
|
191
|
+
elsif Gem.path.any?{|x| path.start_with?(x)}
|
192
|
+
# skip all installed gem files
|
193
|
+
@ignore_paths_cache[path] = true
|
194
|
+
else
|
195
|
+
@ignore_paths_cache[path] = false
|
196
|
+
end
|
197
|
+
end
|
198
|
+
return ! @ignore_paths_cache[path]
|
199
|
+
end
|
200
|
+
|
201
|
+
def get_location(klass, method_id)
|
202
|
+
unless @method_location_cache[klass].key?(method_id)
|
203
|
+
path = nil
|
204
|
+
m = klass.instance_method(method_id)
|
205
|
+
if m and m.source_location
|
206
|
+
path = m.source_location[0]
|
207
|
+
end
|
208
|
+
@method_location_cache[klass][method_id] = path
|
209
|
+
end
|
210
|
+
|
211
|
+
return @method_location_cache[klass][method_id]
|
212
|
+
end
|
213
|
+
|
214
|
+
# true if klass is defined under Module
|
215
|
+
def under_module?(klass, mod)
|
216
|
+
ks = non_singleton_class(klass).to_s
|
217
|
+
ms = mod.to_s
|
218
|
+
return ks == ms || ks.start_with?(ms + "::")
|
219
|
+
end
|
220
|
+
|
221
|
+
# convert singleton class (like #<Class:Regexp>) to non singleton class (like Regexp)
|
222
|
+
def non_singleton_class(klass)
|
223
|
+
unless klass.singleton_class?
|
224
|
+
return klass
|
225
|
+
end
|
226
|
+
|
227
|
+
if /^#<Class:([A-Za-z0-9_:]+)>$/ =~ klass.inspect
|
228
|
+
# maybe normal name class
|
229
|
+
klass_name = Regexp.last_match[1]
|
230
|
+
begin
|
231
|
+
ret_klass = klass_name.split('::').inject(Kernel){|nm, sym| nm.const_get(sym)}
|
232
|
+
rescue => e
|
233
|
+
$stderr.puts "----- argtrace bug -----"
|
234
|
+
$stderr.puts "cannot convert class name #{klass} => #{klass_name}"
|
235
|
+
$stderr.puts e.full_message
|
236
|
+
$stderr.puts "------------------------"
|
237
|
+
raise
|
238
|
+
end
|
239
|
+
return ret_klass
|
240
|
+
end
|
241
|
+
|
242
|
+
# maybe this class is object's singleton class / special named class.
|
243
|
+
# I can't find efficient way, so cache the calculated result.
|
244
|
+
if @singleton_class_map_cache.key?(klass)
|
245
|
+
return @singleton_class_map_cache[klass]
|
246
|
+
end
|
247
|
+
begin
|
248
|
+
ret_klass = ObjectSpace.each_object(Module).find{|x| x.singleton_class == klass}
|
249
|
+
@singleton_class_map_cache[klass] = ret_klass
|
250
|
+
rescue => e
|
251
|
+
$stderr.puts "----- argtrace bug -----"
|
252
|
+
$stderr.puts "cannot convert class name #{klass} => #{klass_name}"
|
253
|
+
$stderr.puts e.full_message
|
254
|
+
$stderr.puts "------------------------"
|
255
|
+
raise
|
256
|
+
end
|
257
|
+
return ret_klass
|
258
|
+
end
|
259
|
+
|
260
|
+
# convert parameters to Parameter[]
|
261
|
+
def get_param_types(parameters, tp)
|
262
|
+
if tp.event == :c_call
|
263
|
+
# I cannot get parameter values of c_call ...
|
264
|
+
return []
|
265
|
+
else
|
266
|
+
return parameters.map{|param|
|
267
|
+
# param[0]=:req, param[1]=:x
|
268
|
+
p = Parameter.new
|
269
|
+
p.mode = param[0]
|
270
|
+
p.name = param[1]
|
271
|
+
if param[0] == :block
|
272
|
+
p.type = Signature.new
|
273
|
+
elsif param[1] == :* || param[1] == :&
|
274
|
+
# workaround for ActiveSupport gem.
|
275
|
+
# I don't know why this happen. just discard info about it.
|
276
|
+
type = TypeUnion.new
|
277
|
+
p.type = type
|
278
|
+
else
|
279
|
+
# TODO: this part is performance bottleneck caused by eval,
|
280
|
+
# but It's essential code
|
281
|
+
type = TypeUnion.new
|
282
|
+
begin
|
283
|
+
val = tp.binding.eval(param[1].to_s)
|
284
|
+
rescue => e
|
285
|
+
$stderr.puts "----- argtrace bug -----"
|
286
|
+
$stderr.puts parameters.inspect
|
287
|
+
$stderr.puts e.full_message
|
288
|
+
$stderr.puts "------------------------"
|
289
|
+
raise
|
290
|
+
end
|
291
|
+
type.add Type.new_with_value(val)
|
292
|
+
p.type = type
|
293
|
+
end
|
294
|
+
p
|
295
|
+
}
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
# pickup block parameter as proc if exists
|
300
|
+
def get_block_param_value(parameters, tp)
|
301
|
+
if tp.event == :c_call
|
302
|
+
# I cannot get parameter values of c_call ...
|
303
|
+
return nil
|
304
|
+
else
|
305
|
+
parameters.each do |param|
|
306
|
+
if param[0] == :block
|
307
|
+
if param[1] == :&
|
308
|
+
# workaround for ActiveSupport gem.
|
309
|
+
# I don't know why this happen. just discard info about it.
|
310
|
+
return nil
|
311
|
+
end
|
312
|
+
begin
|
313
|
+
val = tp.binding.eval(param[1].to_s)
|
314
|
+
rescue => e
|
315
|
+
$stderr.puts "----- argtrace bug -----"
|
316
|
+
$stderr.puts parameters.inspect
|
317
|
+
$stderr.puts e.full_message
|
318
|
+
$stderr.puts "------------------------"
|
319
|
+
raise
|
320
|
+
end
|
321
|
+
return val
|
322
|
+
end
|
323
|
+
end
|
324
|
+
return nil
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
# current called method object
|
329
|
+
def get_called_method(tp)
|
330
|
+
if tp.defined_class != tp.self.class
|
331
|
+
# I cannot identify all cases for this, so checks strictly.
|
332
|
+
|
333
|
+
if tp.defined_class.singleton_class?
|
334
|
+
# On class method call, "defined_class" becomes singleton(singular) class.
|
335
|
+
elsif tp.self.is_a?(tp.defined_class)
|
336
|
+
# On ancestor's method call, "defined_class" is different from self.class.
|
337
|
+
else
|
338
|
+
# This is unknown case.
|
339
|
+
raise "type inconsistent def:#{tp.defined_class} <=> self:#{tp.self.class} "
|
340
|
+
end
|
341
|
+
end
|
342
|
+
return tp.self.method(tp.method_id)
|
343
|
+
end
|
344
|
+
|
345
|
+
# true for the unhandleable events
|
346
|
+
def ignore_event?(tp)
|
347
|
+
if tp.defined_class.equal?(Class) and tp.method_id == :new
|
348
|
+
# On "Foo.new", I want "Foo" here,
|
349
|
+
# but "binding.receiver" equals to caller's "self" so I cannot get "Foo" from anywhere.
|
350
|
+
# Just ignore.
|
351
|
+
return true
|
352
|
+
end
|
353
|
+
|
354
|
+
if tp.defined_class.equal?(BasicObject) and tp.method_id == :initialize
|
355
|
+
# On "Foo#initialize", I want "Foo" here,
|
356
|
+
# but if "Foo" doesn't have explicit "initialize" method then no clue to get "Foo".
|
357
|
+
# Just ignore.
|
358
|
+
return true
|
359
|
+
end
|
360
|
+
|
361
|
+
if tp.defined_class.equal?(Class) and tp.method_id == :inherited
|
362
|
+
# I can't understand this.
|
363
|
+
# Just ignore.
|
364
|
+
return true
|
365
|
+
end
|
366
|
+
|
367
|
+
if tp.defined_class.equal?(Module) and tp.method_id == :method_added
|
368
|
+
# I can't understand this.
|
369
|
+
# Just ignore.
|
370
|
+
return true
|
371
|
+
end
|
372
|
+
|
373
|
+
return false
|
374
|
+
end
|
375
|
+
|
376
|
+
# check filter from set_filter
|
377
|
+
def check_event_filter(tp)
|
378
|
+
if @prune_event_filter
|
379
|
+
return @prune_event_filter.call(tp)
|
380
|
+
else
|
381
|
+
return true
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
# set event filter
|
386
|
+
# true = normal process
|
387
|
+
# false = skip notify
|
388
|
+
# :prune = skip notify and skip all nested events
|
389
|
+
def set_filter(&prune_event_filter)
|
390
|
+
@prune_event_filter = prune_event_filter
|
391
|
+
end
|
392
|
+
|
393
|
+
def set_exit(&exit_block)
|
394
|
+
@exit_block = exit_block
|
395
|
+
end
|
396
|
+
|
397
|
+
def set_notify(¬ify_block)
|
398
|
+
@notify_block = notify_block
|
399
|
+
end
|
400
|
+
|
401
|
+
def call_exit
|
402
|
+
@exit_block.call if @exit_block
|
403
|
+
end
|
404
|
+
|
405
|
+
def enable
|
406
|
+
@tp_holder.enable
|
407
|
+
end
|
408
|
+
|
409
|
+
def disable
|
410
|
+
@tp_holder.disable
|
411
|
+
end
|
412
|
+
|
413
|
+
def stop_trace()
|
414
|
+
self.disable
|
415
|
+
Tracer.remove_running_trace(self)
|
416
|
+
end
|
417
|
+
|
418
|
+
# start TracePoint with callback block
|
419
|
+
def start_trace()
|
420
|
+
tp = TracePoint.new(:c_call, :c_return, :call, :return, :b_call) do |tp|
|
421
|
+
begin
|
422
|
+
tp.disable
|
423
|
+
# DEBUG:
|
424
|
+
# p [tp.event, tp.defined_class, tp.method_id]
|
425
|
+
self.trace(tp)
|
426
|
+
rescue => e
|
427
|
+
$stderr.puts "----- argtrace catch exception -----"
|
428
|
+
$stderr.puts e.full_message
|
429
|
+
$stderr.puts "------------------------------------"
|
430
|
+
@is_dead = true
|
431
|
+
ensure
|
432
|
+
tp.enable unless @is_dead
|
433
|
+
end
|
434
|
+
end
|
435
|
+
@tp_holder = tp
|
436
|
+
|
437
|
+
# hold reference and register at_exit
|
438
|
+
Tracer.add_running_trace(self)
|
439
|
+
|
440
|
+
tp.enable
|
441
|
+
end
|
442
|
+
|
443
|
+
@@running_trace_first = true
|
444
|
+
@@running_trace = []
|
445
|
+
def self.add_running_trace(trace)
|
446
|
+
@@running_trace << trace
|
447
|
+
if @@running_trace_first
|
448
|
+
@@running_trace_first = false
|
449
|
+
at_exit do
|
450
|
+
@@running_trace.each do |trace|
|
451
|
+
trace.disable
|
452
|
+
end
|
453
|
+
@@running_trace.each do |trace|
|
454
|
+
trace.call_exit
|
455
|
+
end
|
456
|
+
@@running_trace.clear
|
457
|
+
end
|
458
|
+
end
|
459
|
+
end
|
460
|
+
|
461
|
+
def self.remove_running_trace(trace)
|
462
|
+
@@running_trace.delete(trace)
|
463
|
+
end
|
464
|
+
|
465
|
+
end
|
466
|
+
|
467
|
+
end
|
@@ -0,0 +1,230 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Argtrace
|
4
|
+
|
5
|
+
# Store of signatures
|
6
|
+
class TypeLib
|
7
|
+
# class => { method_id => [normal_method_signature, singleton_method_signature] }
|
8
|
+
def lib
|
9
|
+
@lib
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@lib = Hash.new{|hklass, klass|
|
14
|
+
hklass[klass] = Hash.new{|hmethod, method_id|
|
15
|
+
hmethod[method_id] = [nil, nil]
|
16
|
+
}
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
def ready_signature(signature)
|
21
|
+
pair = @lib[signature.defined_class][signature.method_id]
|
22
|
+
index = signature.is_singleton_method ? 1 : 0
|
23
|
+
unless pair[index]
|
24
|
+
sig = Signature.new
|
25
|
+
sig.defined_class = signature.defined_class
|
26
|
+
sig.method_id = signature.method_id
|
27
|
+
sig.is_singleton_method = signature.is_singleton_method
|
28
|
+
sig.return_type = nil
|
29
|
+
pair[index] = sig
|
30
|
+
end
|
31
|
+
return pair[index]
|
32
|
+
end
|
33
|
+
|
34
|
+
def learn(signature)
|
35
|
+
ready_signature(signature).merge(signature.params, signature.return_type)
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_rbs
|
39
|
+
# TODO: should I output class inheritance info ?
|
40
|
+
# TODO: private/public
|
41
|
+
# TODO: attr_reader/attr_writer/attr_accessor
|
42
|
+
mod_root = OutputModule.new
|
43
|
+
|
44
|
+
@lib.keys.sort_by{|x| x.to_s}.each do |klass|
|
45
|
+
klass_methods = @lib[klass]
|
46
|
+
|
47
|
+
# output instance method first, and then output singleton method.
|
48
|
+
[0, 1].each do |instance_or_singleton|
|
49
|
+
klass_methods.keys.sort.each do |method_id|
|
50
|
+
sig = klass_methods[method_id][instance_or_singleton]
|
51
|
+
next unless sig
|
52
|
+
|
53
|
+
mod_root.add_signature(sig)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
mod_root.to_rbs
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# helper to convert TypeLib into RBS. OutputMoudle acts like Module tree node.
|
63
|
+
class OutputModule
|
64
|
+
attr_accessor :actual_module, :name, :children, :signatures
|
65
|
+
|
66
|
+
def initialize
|
67
|
+
@children = {}
|
68
|
+
@signatures = []
|
69
|
+
end
|
70
|
+
|
71
|
+
def add_signature(signature)
|
72
|
+
# this is root node, so use Kernel as const resolve source.
|
73
|
+
@actual_module = Kernel
|
74
|
+
|
75
|
+
constname = class_const_name(signature.defined_class)
|
76
|
+
unless constname
|
77
|
+
# cannot handle this
|
78
|
+
return
|
79
|
+
end
|
80
|
+
|
81
|
+
add_signature_inner(constname, signature)
|
82
|
+
end
|
83
|
+
|
84
|
+
# split class name into consts (e.g. Argtrace::TypeLib to ["Argtrace", "TypeLib"])
|
85
|
+
def class_const_name(klass)
|
86
|
+
if /^[A-Za-z0-9_:]+$/ =~ klass.to_s
|
87
|
+
# this should be normal name
|
88
|
+
consts = klass.to_s.split("::")
|
89
|
+
|
90
|
+
# assertion
|
91
|
+
resolved_class = consts.inject(Kernel){|mod, const| mod.const_get(const)}
|
92
|
+
if klass != resolved_class
|
93
|
+
$stderr.puts "----- argtrace bug -----"
|
94
|
+
$stderr.puts "#{klass} => #{consts} => #{resolved_class}"
|
95
|
+
$stderr.puts "------------------------"
|
96
|
+
raise "Failed to resolve class by constant"
|
97
|
+
end
|
98
|
+
|
99
|
+
return consts
|
100
|
+
else
|
101
|
+
return nil
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def add_signature_inner(name_consts, signature)
|
106
|
+
if name_consts.empty?
|
107
|
+
@signatures << signature
|
108
|
+
else
|
109
|
+
unless @children.key?(name_consts.first)
|
110
|
+
mod = OutputModule.new
|
111
|
+
mod.name = name_consts.first
|
112
|
+
mod.actual_module = @actual_module.const_get(name_consts.first)
|
113
|
+
@children[name_consts.first] = mod
|
114
|
+
end
|
115
|
+
current_resolving_name = name_consts.shift
|
116
|
+
@children[current_resolving_name].add_signature_inner(name_consts, signature)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def to_rbs
|
121
|
+
# this is root node
|
122
|
+
lines = []
|
123
|
+
@children.keys.sort.each do |child_name|
|
124
|
+
lines << @children[child_name].to_rbs_inner(0)
|
125
|
+
lines << ""
|
126
|
+
end
|
127
|
+
return lines.join("\n")
|
128
|
+
end
|
129
|
+
|
130
|
+
def to_rbs_inner(indent_level)
|
131
|
+
indent = " " * indent_level
|
132
|
+
classmod_def = @actual_module.class == Class ? "class" : "module"
|
133
|
+
|
134
|
+
lines = []
|
135
|
+
lines << "#{indent}#{classmod_def} #{name}"
|
136
|
+
@children.keys.sort.each do |child_name|
|
137
|
+
lines << @children[child_name].to_rbs_inner(indent_level + 1)
|
138
|
+
lines << ""
|
139
|
+
end
|
140
|
+
@signatures.each do |sig|
|
141
|
+
lines << sig_to_rbs(indent_level + 1, sig)
|
142
|
+
end
|
143
|
+
lines << "#{indent}end"
|
144
|
+
return lines.join("\n")
|
145
|
+
end
|
146
|
+
|
147
|
+
def sig_to_rbs(indent_level, signature)
|
148
|
+
indent = " " * indent_level
|
149
|
+
sig_name = signature.is_singleton_method ? "self.#{signature.method_id}" : signature.method_id
|
150
|
+
params = signature.params
|
151
|
+
.filter{|p| p.mode != :block}
|
152
|
+
.map{|p| param_to_rbs(p)}
|
153
|
+
.compact
|
154
|
+
.join(", ")
|
155
|
+
rettype = type_union_to_rbs(signature.return_type)
|
156
|
+
blocktype = blocktype_to_rbs(signature.params.find{|p| p.mode == :block})
|
157
|
+
return "#{indent}def #{sig_name} : (#{params})#{blocktype} -> #{rettype}"
|
158
|
+
end
|
159
|
+
|
160
|
+
def blocktype_to_rbs(blockparam)
|
161
|
+
unless blockparam
|
162
|
+
return ""
|
163
|
+
end
|
164
|
+
params = blockparam.type.params
|
165
|
+
.map{|p| type_union_to_rbs(p.type)}
|
166
|
+
.join(", ")
|
167
|
+
return " { (#{params}) -> untyped }"
|
168
|
+
end
|
169
|
+
|
170
|
+
def param_to_rbs(param)
|
171
|
+
case param.mode
|
172
|
+
when :req
|
173
|
+
return "#{type_union_to_rbs(param.type)} #{param.name}"
|
174
|
+
when :opt
|
175
|
+
return "?#{type_union_to_rbs(param.type)} #{param.name}"
|
176
|
+
when :keyreq
|
177
|
+
return "#{param.name}: #{type_union_to_rbs(param.type)}"
|
178
|
+
when :key
|
179
|
+
return "?#{param.name}: #{type_union_to_rbs(param.type)}"
|
180
|
+
when :block
|
181
|
+
return nil
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def type_union_to_rbs(typeunion)
|
186
|
+
if typeunion.union.size == 0
|
187
|
+
return "untyped"
|
188
|
+
end
|
189
|
+
# TODO: ugly
|
190
|
+
if typeunion.union.size == 1 and NilClass == typeunion.union.first.data
|
191
|
+
# TODO: I can't distinguish nil and untyped.
|
192
|
+
return "untyped"
|
193
|
+
end
|
194
|
+
if typeunion.union.size == 2 and typeunion.union.any?{|x| NilClass == x.data}
|
195
|
+
# type is nil and sometype, so represent it as "sometype?"
|
196
|
+
sometype = typeunion.union.find{|x| NilClass != x.data}
|
197
|
+
return "#{type_to_rbs(sometype)}?"
|
198
|
+
end
|
199
|
+
|
200
|
+
ret = typeunion.union.map{|type| type_to_rbs(type)}.join("|")
|
201
|
+
return ret
|
202
|
+
end
|
203
|
+
|
204
|
+
def type_to_rbs(type)
|
205
|
+
if type.data.is_a?(Symbol)
|
206
|
+
return type.data.inspect
|
207
|
+
elsif true == type.data || false == type.data || BooleanClass == type.data
|
208
|
+
return "bool"
|
209
|
+
elsif nil == type.data || NilClass == type.data
|
210
|
+
return "nil"
|
211
|
+
elsif Array == type.data
|
212
|
+
if type.subdata
|
213
|
+
case type.subdata
|
214
|
+
when true, false, BooleanClass
|
215
|
+
elementtype = "bool"
|
216
|
+
else
|
217
|
+
elementtype = type.subdata.to_s
|
218
|
+
end
|
219
|
+
return "Array[#{elementtype}]"
|
220
|
+
else
|
221
|
+
return "Array"
|
222
|
+
end
|
223
|
+
else
|
224
|
+
return type.data.to_s
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
metadata
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: argtrace
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Riki Ishikawa
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-03-14 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: library to trace arguments of method calls or block calls with TracePoint
|
14
|
+
and do callback.
|
15
|
+
email:
|
16
|
+
- riki.ishikawa@gmail.com
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- ".gitignore"
|
22
|
+
- ".rubocop.yml"
|
23
|
+
- Gemfile
|
24
|
+
- LICENSE.txt
|
25
|
+
- README.md
|
26
|
+
- Rakefile
|
27
|
+
- argtrace.gemspec
|
28
|
+
- bin/console
|
29
|
+
- bin/setup
|
30
|
+
- lib/argtrace.rb
|
31
|
+
- lib/argtrace/autorun.rb
|
32
|
+
- lib/argtrace/default.rb
|
33
|
+
- lib/argtrace/signature.rb
|
34
|
+
- lib/argtrace/tracer.rb
|
35
|
+
- lib/argtrace/typelib.rb
|
36
|
+
- lib/argtrace/version.rb
|
37
|
+
homepage: https://github.com/jljse/argtrace/
|
38
|
+
licenses:
|
39
|
+
- MIT
|
40
|
+
metadata:
|
41
|
+
homepage_uri: https://github.com/jljse/argtrace/
|
42
|
+
source_code_uri: https://github.com/jljse/argtrace/
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options: []
|
45
|
+
require_paths:
|
46
|
+
- lib
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: 2.7.0
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
requirements: []
|
58
|
+
rubygems_version: 3.2.3
|
59
|
+
signing_key:
|
60
|
+
specification_version: 4
|
61
|
+
summary: trace arguments of method calls or block calls.
|
62
|
+
test_files: []
|