argtrace 0.1.1
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/.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: []
|