rbs-dynamic 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rbs"
4
+ require_relative "../builder.rb"
5
+ require_relative "../refine/each_called_method.rb"
6
+ require_relative "./called_method_to_sigunature.rb"
7
+ require_relative "./called_method_to_with_interface.rb"
8
+
9
+ module RBS module Dynamic module Converter
10
+ class TraceToRBS
11
+ using RBS::Dynamic::Refine::EachCalledMethod
12
+ using RBS::Dynamic::Refine::TracePoint::ToRBSType
13
+ using CalledMethodToMethodSigunature
14
+ using CalledMethodToWithInterface
15
+
16
+ using Module.new {
17
+ refine Module do
18
+ def constants_wit_rbs_type(...)
19
+ constants(...)
20
+ # Except to ARGF.class
21
+ .filter_map { |name| [name, const_get(name).to_rbs_type.except_value] if const_get(name).class.name =~ /^\w+$/ }
22
+ .to_h
23
+ end
24
+
25
+ def include_prepend_modules
26
+ ancestors = Class === self ? self.ancestors.take_while { _1 != self.superclass } : self.ancestors
27
+ [ancestors.take_while { _1 != self }.reverse, ancestors.drop_while { _1 != self }.drop(1).reverse]
28
+ end
29
+
30
+ # M1::M2::X => [M1, M1::M2, M1::M2::X]
31
+ def namespace_paths
32
+ name.split("::").inject([]) { |result, scope|
33
+ result << Object.const_get("#{result.last}::#{scope}")
34
+ }
35
+ end
36
+ end
37
+ }
38
+
39
+ attr_reader :called_methods
40
+
41
+ def initialize(called_methods)
42
+ @called_methods = called_methods
43
+ end
44
+
45
+ def convert(
46
+ root_path: nil,
47
+ except_build_members: [],
48
+ include_method_location: false,
49
+ method_defined_calssses: %i(defined_class receiver_class),
50
+ use_literal_type: false,
51
+ with_literal_type: false,
52
+ use_interface: false,
53
+ use_interface_method_argument: false,
54
+ target_classname_pattern: /.*/,
55
+ ignore_classname_pattern: nil
56
+ )
57
+ called_methods_ = called_methods.each_called_method.reject { _1[:block?] }
58
+ klass_with_called_methods = [
59
+ if method_defined_calssses.include? :defined_class
60
+ called_methods_.group_by { _1[:receiver_defined_class] }
61
+ end,
62
+ if method_defined_calssses.include? :receiver_class
63
+ called_methods_.group_by { _1[:receiver_class] }
64
+ end
65
+ ]
66
+ .compact
67
+ .inject { |result, it| result.merge(it) { |key, a, b| a + b } }
68
+ .select { |klass, _| Module === klass }.reject { |klass, _| klass.nil? || klass.singleton_class? }
69
+ .to_h { |klass, called_methods|
70
+ [klass, called_methods.uniq { [_1[:method_id], _1[:arguments], _1[:return_value_class], _1[:called_path], _1[:called_lineno]] }.group_by { _1[:method_id] }]
71
+ }
72
+ klasses = klass_with_called_methods.keys
73
+
74
+ # Modules / classes that are defined but methods are not called are also defined in RBS
75
+ non_called_klass = klass_with_called_methods.keys.map { _1.namespace_paths }.flatten \
76
+ + klasses.filter_map { Class === _1 && Object != _1.superclass && BasicObject != _1.superclass && _1.superclass } \
77
+ - klasses
78
+
79
+ # non_called_klass += non_called_klass.map { _1.include_prepend_modules }.flatten
80
+ klass_with_called_methods = non_called_klass.to_h { [_1, {}] }.merge(klass_with_called_methods)
81
+ # klass_with_called_methods = klass_with_called_methods.merge(non_called_klass.to_h { [_1, {}] })
82
+
83
+ # klass_with_called_methods.sort_by { |klass, _| klass.name }.to_h { |klass, name_with_called_methods|
84
+ klass_with_called_methods
85
+ .select { |klass, _| target_classname_pattern && target_classname_pattern =~ klass.name }
86
+ .reject { |klass, _| ignore_classname_pattern && ignore_classname_pattern =~ klass.name }
87
+ .to_h { |klass, name_with_called_methods|
88
+ builder = Class === klass ? RBS::Dynamic::Builder::Class.new(klass) : RBS::Dynamic::Builder::Module.new(klass)
89
+
90
+ # Add prepend / include
91
+ # MEMO: Add only the traced module to RBS
92
+ # TODO: Predefined RBS modules (e.g. core module) other than Trace are not mixin
93
+ klass.include_prepend_modules.tap { |prepended, included|
94
+ # prepend
95
+ prepended.each { |mod|
96
+ builder.add_prepended_module(mod) if klasses.include? mod
97
+ } unless except_build_members.include? :prepended_modules
98
+
99
+ # include
100
+ included.each { |mod|
101
+ builder.add_inclued_module(mod) if klasses.include? mod
102
+ } unless except_build_members.include? :inclued_modules
103
+ }
104
+
105
+ # Add extend
106
+ klass.singleton_class.include_prepend_modules.flatten.each { |mod|
107
+ builder.add_extended_module(mod) if klasses.include? mod
108
+ } unless except_build_members.include? :extended_modules
109
+
110
+ # Add constant variable
111
+ klass.constants_wit_rbs_type(false).each { |name, type|
112
+ builder.add_constant_variable(name, type) if name == name.upcase
113
+ } unless except_build_members.include? :constant_variables
114
+
115
+ # Add instance variable
116
+ name_with_called_methods.map { |_, called_methods| called_methods.map { _1[:instance_variables_rbs_type] } }.flatten.each { |instance_variables_rbs_type|
117
+ instance_variables_rbs_type.each { |name, type|
118
+ # TODO: Support use_literal_type and with_literal_type
119
+ builder.add_instance_variable(name, type.except_value)
120
+ }
121
+ } unless except_build_members.include? :instance_variables
122
+
123
+ # Add method and singleton method
124
+ name_with_called_methods.each.with_index(1) { |(name, called_methods), i|
125
+ called_methods.each.with_index(1) { |called_method, j|
126
+ if use_interface_method_argument
127
+ called_method, sub_interfaces = called_method.to_with_interface(defined_interfaces: builder.interface_members)
128
+ sub_interfaces.map { builder.add_interface_members(_1) }
129
+ end
130
+
131
+ signature = called_method.method_sigunature(root_path: root_path, include_location: include_method_location, use_literal_type: use_literal_type, with_literal_type: with_literal_type)
132
+ if called_method[:singleton_method?]
133
+ builder.add_singleton_method(name, **signature) unless except_build_members.include? :singleton_methods
134
+ else
135
+ builder.add_method(name, **signature) unless except_build_members.include? :methods
136
+ end
137
+ }
138
+ }
139
+
140
+ [klass, builder.build]
141
+ }
142
+ end
143
+ end
144
+ end end end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RBS module Dynamic module Refine
4
+ module BasicObjectWithKernel
5
+ refine BasicObject do
6
+ def class(...)
7
+ ::Kernel.instance_method(:class).bind(self).call(...)
8
+ end
9
+ end
10
+ end
11
+ end end end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RBS module Dynamic module Refine
4
+ module EachCalledMethod
5
+ refine Hash do
6
+ def each_called_method(&block)
7
+ return Enumerator.new { |y|
8
+ each_called_method { |it| y << it }
9
+ } unless block
10
+
11
+ self[:called_methods].each { |called_method|
12
+ block.call(called_method)
13
+ if Hash === called_method
14
+ called_method.each_called_method(&block)
15
+ end
16
+ }
17
+ end
18
+ end
19
+
20
+ refine Array do
21
+ using EachCalledMethod
22
+
23
+ def each_called_method(&block)
24
+ return Enumerator.new { |y|
25
+ each_called_method { |it| y << it }
26
+ } unless block
27
+ each { |called_method|
28
+ block.call(called_method)
29
+ called_method.each_called_method(&block)
30
+ }
31
+ end
32
+ end
33
+ end
34
+ end end end
@@ -0,0 +1,175 @@
1
+ # frozen_string_literal: true
2
+
3
+
4
+ require_relative "../builder/types.rb"
5
+
6
+ module RBS module Dynamic module Refine
7
+ module SignatureMerger
8
+ module AsA
9
+ refine Object do
10
+ def as_a
11
+ [self]
12
+ end
13
+ end
14
+
15
+ refine Array do
16
+ def as_a
17
+ self
18
+ end
19
+ end
20
+ end
21
+ using AsA
22
+
23
+ using Module.new {
24
+ using RBS::Dynamic::Builder::Types::ToRBSType
25
+
26
+ refine Array do
27
+ # p [1, 2].zip([3, 4]) # => [[1, 3], [2, 4]]
28
+ # p [1, 2].zip([3, 4, 5, 6]) # => [[1, 3], [2, 4]]
29
+ # p [1, 2].zip_fill([3, 4, 5, 6]) # => [[1, 3], [2, 4], [nil, 5], [nil, 6]]
30
+ def zip_fill(other, val = nil)
31
+ if count < other.count
32
+ fill(val, count...other.count).zip(other)
33
+ else
34
+ zip(other)
35
+ end
36
+ end
37
+
38
+ def union_params_rbs_type
39
+ map { |a, b|
40
+ a ||= {}
41
+ b ||= {}
42
+ type = a.fetch(:type, []).as_a + b.fetch(:type, []).as_a
43
+ { name: (a[:name] || b[:name]), type: type.map { _1.to_rbs_type }.uniq }
44
+ }
45
+ end
46
+
47
+ def equal_type(other)
48
+ count == Array(other).count && zip(Array(other)).all? { |a, b| a.equal_type(b) }
49
+ end
50
+ end
51
+
52
+ refine Hash do
53
+ def equal_type(other)
54
+ rhs_type = Integer === self[:type] ? [Integer]
55
+ : String === self[:type] ? [String]
56
+ : Symbol === self[:type] ? [Symbol]
57
+ : Array == self[:type] ? [Array] # except args
58
+ : Hash == self[:type] ? [Hash] # except args
59
+ : [self[:type], self[:args]]
60
+
61
+ lhs_type = Integer === other[:type] ? [Integer]
62
+ : String === other[:type] ? [String]
63
+ : Symbol === other[:type] ? [Symbol]
64
+ : Array == other[:type] ? [Array] # except args
65
+ : Hash == other[:type] ? [Hash] # except args
66
+ : [other[:type], other[:args]]
67
+
68
+ rhs_type == lhs_type
69
+ end
70
+
71
+ def params
72
+ slice(:required_positionals, :optional_positionals, :rest_positionals, :required_keywords, :optional_keywords)
73
+ end
74
+
75
+ def params_with_types
76
+ params.to_h { |k, sigs| [k, sigs&.map { _1[:type].to_rbs_type }] }
77
+ end
78
+
79
+ def param_names_with_num
80
+ params.to_h { |key, val| [key, val.count] }
81
+ end
82
+
83
+ def return_type
84
+ fetch(:return_type, []).as_a.map { _1.to_rbs_type }
85
+ end
86
+
87
+ def param_mergeable?(other)
88
+ param_names_with_num == other.param_names_with_num &&
89
+ return_type == other.return_type
90
+ end
91
+
92
+ def return_type_mergeable?(other)
93
+ params_with_types == other.params_with_types
94
+ end
95
+
96
+ def merge_params!(other)
97
+ other.params.merge(self) { |key, other, current|
98
+ current.zip_fill(other, {}).union_params_rbs_type
99
+ }
100
+ end
101
+
102
+ def merge_params(other)
103
+ return unless param_mergeable?(other)
104
+ merge_params!(other)
105
+ end
106
+
107
+ def merge_return_type!(other)
108
+ merge(return_type: (return_type + other.return_type).uniq)
109
+ end
110
+
111
+ def merge_return_type(other)
112
+ return unless return_type_mergeable?(other)
113
+ merge_return_type!(other)
114
+ end
115
+ end
116
+ }
117
+
118
+ refine Object do
119
+ def merge_sig(*)
120
+ nil
121
+ end
122
+ end
123
+
124
+ refine Array do
125
+ def merge_sig(other)
126
+ (self + other).zip_sig
127
+ end
128
+ end
129
+
130
+ refine Hash do
131
+ def merge_sig(other)
132
+ return unless Hash === other
133
+
134
+ block_merged = self[:block]&.merge_sig(other[:block])
135
+
136
+ if merged = merge_return_type(other)
137
+ merged.merge(block: block_merged)
138
+ elsif merged = merge_params(other)
139
+ merged.merge(block: block_merged)
140
+ end
141
+ end
142
+
143
+ def merge_sig!(other)
144
+ return unless Hash === other
145
+
146
+ merge_params!(other).merge_return_type!(other)
147
+ end
148
+ end
149
+
150
+ refine Array do
151
+ def push_merge_sig(other)
152
+ each.with_index { |sig, i|
153
+ merged = sig.merge_sig(other)
154
+ if merged
155
+ self[i] = merged
156
+ return self
157
+ end
158
+ }
159
+ push(other)
160
+ end
161
+
162
+ def zip_sig
163
+ inject([]) { |result, sig|
164
+ result.push_merge_sig(sig)
165
+ }
166
+ end
167
+
168
+ def zip_sig!
169
+ drop(1).inject(first) { |result, sig|
170
+ result.merge_sig!(sig)
171
+ }
172
+ end
173
+ end
174
+ end
175
+ end end end
@@ -0,0 +1,211 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "./basic_object_with_kernel.rb"
4
+
5
+ module RBS module Dynamic module Refine
6
+ module TracePoint
7
+ using RBS::Dynamic::Refine::BasicObjectWithKernel
8
+
9
+ module ToRBSType
10
+ refine Symbol do
11
+ def to_rbs_value
12
+ self
13
+ end
14
+ end
15
+
16
+ refine Integer do
17
+ def to_rbs_value
18
+ self
19
+ end
20
+ end
21
+
22
+ refine String do
23
+ # No supported literal-string
24
+ # def to_rbs_value
25
+ # self
26
+ # end
27
+ end
28
+
29
+ refine BasicObject do
30
+ def to_rbs_value
31
+ nil
32
+ end
33
+
34
+ def to_rbs_type(cache: [])
35
+ { type: Kernel.instance_method(:class).bind(self).call, value: to_rbs_value, args: [] }
36
+ end
37
+ end
38
+
39
+ refine Array do
40
+ def to_rbs_type(cache: [])
41
+ return { type: Array, value: nil, args: [[]] } if cache.include? __id__
42
+ { type: Array, value: nil, args: (empty? ? [[]] : [filter_map { _1.to_rbs_type(cache: cache + [__id__]) }.uniq]) }
43
+ end
44
+ end
45
+
46
+ refine Hash do
47
+ def to_rbs_type(cache: [])
48
+ return { type: Hash, value: nil, args: [[], []] } if cache.include? __id__
49
+ args = empty? ? [[], []] : [keys.filter_map { _1.to_rbs_type(cache: cache + [__id__]) }.uniq, values.map { _1.to_rbs_type(cache: cache + [__id__]) }]
50
+ { type: Hash, value: nil, args: args }
51
+ end
52
+
53
+ def except_value
54
+ { type: self[:type], args: self[:args]&.map { |args| args.map { _1.except_value } } || [] }
55
+ end
56
+ end
57
+
58
+ refine Range do
59
+ def to_rbs_type(*)
60
+ return { type: Range, value: nil, args: [[self.begin.to_rbs_type, self.end.to_rbs_type]] }
61
+ end
62
+ end
63
+ end
64
+
65
+ refine ::TracePoint do
66
+ using ToRBSType
67
+ using Module.new {
68
+ refine Module do
69
+ def original_classname
70
+ if singleton_class?
71
+ self.superclass.name
72
+ else
73
+ self.name
74
+ end
75
+ end
76
+ end
77
+
78
+ refine Hash do
79
+ def to_arg_s
80
+ if self[:op] == :key
81
+ "#{self[:name]}: #{self[:value].inspect}"
82
+ else
83
+ "#{self[:name]} = #{self[:value].inspect}"
84
+ end
85
+ end
86
+ end
87
+ }
88
+
89
+ def receiver
90
+ self.self
91
+ end
92
+
93
+ def original_classname
94
+ if singleton_class?
95
+ if result = self.inspect[/#<Class:([^#].*)>/, 1]
96
+ result
97
+ else
98
+ self.ancestors.superclass
99
+ end
100
+ else
101
+ self.name
102
+ end
103
+ end
104
+
105
+ def block?
106
+ method_id.nil? || defined_class.nil?
107
+ end
108
+
109
+ def singleton_method?
110
+ defined_class&.singleton_class? || (Module === receiver)
111
+ end
112
+
113
+ def receiver_class
114
+ singleton_method? ? receiver : receiver.class
115
+ end
116
+
117
+ def receiver_defined_class
118
+ return defined_class unless singleton_method?
119
+ return defined_class unless (Module === receiver)
120
+ receiver.ancestors.find { _1.methods(false).include?(method_id) || _1.private_methods(false).include?(method_id) } || defined_class
121
+ end
122
+
123
+ def argument_param(name)
124
+ value = binding.local_variable_get(name)
125
+ { name: name, type: value.class, rbs_type: value.to_rbs_type, value_object_id: value.__id__ }
126
+ end
127
+
128
+ def arguments
129
+ parameters.map { |op, name|
130
+ if op == :opt && name.nil?
131
+ { op: op, type: NilClass, rbs_type: { type: NilClass } }
132
+ elsif op == :rest && name.nil? || name == :*
133
+ { op: op, type: Array, rbs_type: [].to_rbs_type }
134
+ elsif op == :keyrest && name.nil? || name == :**
135
+ { op: op, type: Hash, rbs_type: {}.to_rbs_type }
136
+ elsif op == :block && name.nil? || name == :&
137
+ { op: op, type: Proc, rbs_type: { type: Proc } }
138
+ elsif name.nil?
139
+ { op: op, type: NilClass, rbs_type: { type: NilClass } }
140
+ else
141
+ { op: op, name: name }.merge(argument_param(name))
142
+ end
143
+ }
144
+ end
145
+
146
+ def arguments_with_value
147
+ parameters.map { |op, name|
148
+ { op: op, name: name, value: binding.local_variable_get(name) }
149
+ }
150
+ end
151
+
152
+ def visibility
153
+ return nil if block?
154
+
155
+ if singleton_method?
156
+ if receiver.singleton_class.private_method_defined?(method_id)
157
+ :private
158
+ elsif receiver.singleton_class.protected_method_defined?(method_id)
159
+ :protected
160
+ else
161
+ :public
162
+ end
163
+ else
164
+ if receiver.class.private_method_defined?(method_id)
165
+ :private
166
+ elsif receiver.class.protected_method_defined?(method_id)
167
+ :protected
168
+ else
169
+ :public
170
+ end
171
+ end
172
+ end
173
+
174
+ def instance_variables_class
175
+ binding.eval("::Kernel.instance_method(:instance_variables).bind(self).call.to_h { [_1, ::Kernel.instance_method(:instance_variable_get).bind(self).call(_1).class] }")
176
+ end
177
+
178
+ def instance_variables_rbs_type
179
+ binding.eval("::Kernel.instance_method(:instance_variables).bind(self).call.to_h { [_1, ::Kernel.instance_method(:instance_variable_get).bind(self).call(_1)] }").to_h { [_1, _2.to_rbs_type] }
180
+ end
181
+
182
+ def meta
183
+ {
184
+ method_id: method_id,
185
+ defined_class: defined_class,
186
+ receiver_class: receiver_class,
187
+ receiver_defined_class: receiver_defined_class,
188
+ receiver_object_id: receiver.__id__,
189
+ callee_id: callee_id,
190
+ arguments: arguments,
191
+ lineno: lineno,
192
+ path: path,
193
+ visibility: visibility,
194
+ singleton_method?: singleton_method?,
195
+ }
196
+ end
197
+
198
+ def method_sigunature
199
+ if defined_class.nil?
200
+ "-> (#{arguments_with_value.map(&:to_arg_s).join(", ")}) {}"
201
+ elsif defined_class.singleton_class?
202
+ "#{defined_class.original_classname}.#{method_id}(#{arguments_with_value.map(&:to_arg_s).join(", ")})"
203
+ elsif defined_class == Object
204
+ "#{method_id}(#{arguments_with_value.map(&:to_arg_s).join(", ")})"
205
+ else
206
+ "#{defined_class.original_classname}##{method_id}(#{arguments_with_value.map(&:to_arg_s).join(", ")})"
207
+ end
208
+ end
209
+ end
210
+ end
211
+ end end end
@@ -0,0 +1,30 @@
1
+ require "rbs"
2
+ require_relative "../dynamic.rb"
3
+
4
+ $rbs_dynamic_tracer = RBS::Dynamic::Tracer::CalledMethod.new
5
+ $rbs_dynamic_tracer.enable
6
+
7
+ END {
8
+ $rbs_dynamic_tracer.ignore_rbs_dynamic_trace { $rbs_dynamic_tracer.disable }
9
+ called_methods = $rbs_dynamic_tracer.called_methods
10
+
11
+ puts "# RBS dynamic trace #{RBS::Dynamic::VERSION}"
12
+ puts
13
+
14
+ config = RBS::Dynamic::Config.new($rbs_dynamic_option)
15
+ decls = RBS::Dynamic::Converter::TraceToRBS.new(called_methods).convert(
16
+ root_path: config.root_path,
17
+ except_build_members: config.except_build_members,
18
+ method_defined_calssses: config.method_defined_calssses,
19
+ include_method_location: config.show_method_location?,
20
+ use_literal_type: config.use_literal_type?,
21
+ with_literal_type: config.with_literal_type?,
22
+ use_interface_method_argument: config.use_interface_method_argument?,
23
+ target_classname_pattern: config.target_classname_pattern,
24
+ ignore_classname_pattern: config.ignore_classname_pattern
25
+ ).values
26
+ stdout = StringIO.new
27
+ writer = RBS::Writer.new(out: stdout)
28
+ writer.write(decls)
29
+ puts stdout.string
30
+ }