repl_type_completor 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Gemfile +26 -0
- data/LICENSE.txt +21 -0
- data/README.md +62 -0
- data/Rakefile +37 -0
- data/lib/repl_type_completor/methods.rb +11 -0
- data/lib/repl_type_completor/require_paths.rb +75 -0
- data/lib/repl_type_completor/result.rb +145 -0
- data/lib/repl_type_completor/scope.rb +409 -0
- data/lib/repl_type_completor/type_analyzer.rb +1179 -0
- data/lib/repl_type_completor/types.rb +428 -0
- data/lib/repl_type_completor/version.rb +5 -0
- data/lib/repl_type_completor.rb +146 -0
- data/sig/repl_type_completor.rbs +19 -0
- metadata +87 -0
@@ -0,0 +1,409 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
require_relative 'types'
|
5
|
+
|
6
|
+
module ReplTypeCompletor
|
7
|
+
class RootScope
|
8
|
+
attr_reader :module_nesting, :self_object
|
9
|
+
|
10
|
+
def initialize(binding, self_object, local_variables)
|
11
|
+
@binding = binding
|
12
|
+
@self_object = self_object
|
13
|
+
@cache = {}
|
14
|
+
modules = [*binding.eval('::Module.nesting'), Object]
|
15
|
+
@module_nesting = modules.map { [_1, []] }
|
16
|
+
binding_local_variables = binding.local_variables
|
17
|
+
uninitialized_locals = local_variables - binding_local_variables
|
18
|
+
uninitialized_locals.each { @cache[_1] = Types::NIL }
|
19
|
+
@local_variables = (local_variables | binding_local_variables).map(&:to_s).to_set
|
20
|
+
@global_variables = Kernel.global_variables.map(&:to_s).to_set
|
21
|
+
@owned_constants_cache = {}
|
22
|
+
end
|
23
|
+
|
24
|
+
def level() = 0
|
25
|
+
|
26
|
+
def level_of(_name, _var_type) = 0
|
27
|
+
|
28
|
+
def mutable?() = false
|
29
|
+
|
30
|
+
def module_own_constant?(mod, name)
|
31
|
+
set = (@owned_constants_cache[mod] ||= Set.new(mod.constants.map(&:to_s)))
|
32
|
+
set.include? name
|
33
|
+
end
|
34
|
+
|
35
|
+
def get_const(nesting, path, _key = nil)
|
36
|
+
return unless nesting
|
37
|
+
|
38
|
+
result = path.reduce nesting do |mod, name|
|
39
|
+
return nil unless mod.is_a?(Module) && module_own_constant?(mod, name)
|
40
|
+
mod.const_get name
|
41
|
+
end
|
42
|
+
Types.type_from_object result
|
43
|
+
end
|
44
|
+
|
45
|
+
def get_cvar(nesting, path, name, _key = nil)
|
46
|
+
return Types::NIL unless nesting
|
47
|
+
|
48
|
+
result = path.reduce nesting do |mod, n|
|
49
|
+
return Types::NIL unless mod.is_a?(Module) && module_own_constant?(mod, n)
|
50
|
+
mod.const_get n
|
51
|
+
end
|
52
|
+
value = result.class_variable_get name if result.is_a?(Module) && name.size >= 3 && result.class_variable_defined?(name)
|
53
|
+
Types.type_from_object value
|
54
|
+
end
|
55
|
+
|
56
|
+
def [](name)
|
57
|
+
@cache[name] ||= (
|
58
|
+
value = case RootScope.type_by_name name
|
59
|
+
when :ivar
|
60
|
+
begin
|
61
|
+
Methods::OBJECT_INSTANCE_VARIABLE_GET_METHOD.bind_call(@self_object, name)
|
62
|
+
rescue NameError
|
63
|
+
end
|
64
|
+
when :lvar
|
65
|
+
begin
|
66
|
+
@binding.local_variable_get(name)
|
67
|
+
rescue NameError
|
68
|
+
end
|
69
|
+
when :gvar
|
70
|
+
@binding.eval name if @global_variables.include? name
|
71
|
+
end
|
72
|
+
Types.type_from_object(value)
|
73
|
+
)
|
74
|
+
end
|
75
|
+
|
76
|
+
def self_type
|
77
|
+
Types.type_from_object @self_object
|
78
|
+
end
|
79
|
+
|
80
|
+
def local_variables() = @local_variables.to_a
|
81
|
+
|
82
|
+
def global_variables() = @global_variables.to_a
|
83
|
+
|
84
|
+
def self.type_by_name(name)
|
85
|
+
if name.start_with? '@@'
|
86
|
+
# "@@cvar" or "@@cvar::[module_id]::[module_path]"
|
87
|
+
:cvar
|
88
|
+
elsif name.start_with? '@'
|
89
|
+
:ivar
|
90
|
+
elsif name.start_with? '$'
|
91
|
+
:gvar
|
92
|
+
elsif name.start_with? '%'
|
93
|
+
:internal
|
94
|
+
elsif name[0].downcase != name[0] || name[0].match?(/\d/)
|
95
|
+
# "ConstName" or "[module_id]::[const_path]"
|
96
|
+
:const
|
97
|
+
else
|
98
|
+
:lvar
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
class Scope
|
104
|
+
BREAK_RESULT = '%break'
|
105
|
+
NEXT_RESULT = '%next'
|
106
|
+
RETURN_RESULT = '%return'
|
107
|
+
PATTERNMATCH_BREAK = '%match'
|
108
|
+
|
109
|
+
attr_reader :parent, :mergeable_changes, :level, :module_nesting
|
110
|
+
|
111
|
+
def self.from_binding(binding, locals) = new(RootScope.new(binding, binding.receiver, locals))
|
112
|
+
|
113
|
+
def initialize(parent, table = {}, trace_ivar: true, trace_lvar: true, self_type: nil, nesting: nil)
|
114
|
+
@parent = parent
|
115
|
+
@level = parent.level + 1
|
116
|
+
@trace_ivar = trace_ivar
|
117
|
+
@trace_lvar = trace_lvar
|
118
|
+
@module_nesting = nesting ? [nesting, *parent.module_nesting] : parent.module_nesting
|
119
|
+
@self_type = self_type
|
120
|
+
@terminated = false
|
121
|
+
@jump_branches = []
|
122
|
+
@mergeable_changes = @table = table.transform_values { [level, _1] }
|
123
|
+
end
|
124
|
+
|
125
|
+
def mutable? = true
|
126
|
+
|
127
|
+
def terminated?
|
128
|
+
@terminated
|
129
|
+
end
|
130
|
+
|
131
|
+
def terminate_with(type, value)
|
132
|
+
return if terminated?
|
133
|
+
store_jump type, value, @mergeable_changes
|
134
|
+
terminate
|
135
|
+
end
|
136
|
+
|
137
|
+
def store_jump(type, value, changes)
|
138
|
+
return if terminated?
|
139
|
+
if has_own?(type)
|
140
|
+
changes[type] = [level, value]
|
141
|
+
@jump_branches << changes
|
142
|
+
elsif @parent.mutable?
|
143
|
+
@parent.store_jump(type, value, changes)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def terminate
|
148
|
+
return if terminated?
|
149
|
+
@terminated = true
|
150
|
+
@table = @mergeable_changes.dup
|
151
|
+
end
|
152
|
+
|
153
|
+
def trace?(name)
|
154
|
+
return false unless @parent
|
155
|
+
type = RootScope.type_by_name(name)
|
156
|
+
type == :ivar ? @trace_ivar : type == :lvar ? @trace_lvar : true
|
157
|
+
end
|
158
|
+
|
159
|
+
def level_of(name, var_type)
|
160
|
+
case var_type
|
161
|
+
when :ivar
|
162
|
+
return level unless @trace_ivar
|
163
|
+
when :gvar
|
164
|
+
return 0
|
165
|
+
end
|
166
|
+
variable_level, = @table[name]
|
167
|
+
variable_level || parent.level_of(name, var_type)
|
168
|
+
end
|
169
|
+
|
170
|
+
def get_const(nesting, path, key = nil)
|
171
|
+
key ||= [nesting.__id__, path].join('::')
|
172
|
+
_l, value = @table[key]
|
173
|
+
value || @parent.get_const(nesting, path, key)
|
174
|
+
end
|
175
|
+
|
176
|
+
def get_cvar(nesting, path, name, key = nil)
|
177
|
+
key ||= [name, nesting.__id__, path].join('::')
|
178
|
+
_l, value = @table[key]
|
179
|
+
value || @parent.get_cvar(nesting, path, name, key)
|
180
|
+
end
|
181
|
+
|
182
|
+
def [](name)
|
183
|
+
type = RootScope.type_by_name(name)
|
184
|
+
if type == :const
|
185
|
+
return get_const(nil, nil, name) || Types::NIL if name.include?('::')
|
186
|
+
|
187
|
+
module_nesting.each do |(nesting, path)|
|
188
|
+
value = get_const nesting, [*path, name]
|
189
|
+
return value if value
|
190
|
+
end
|
191
|
+
return Types::NIL
|
192
|
+
elsif type == :cvar
|
193
|
+
return get_cvar(nil, nil, nil, name) if name.include?('::')
|
194
|
+
|
195
|
+
nesting, path = module_nesting.first
|
196
|
+
return get_cvar(nesting, path, name)
|
197
|
+
end
|
198
|
+
level, value = @table[name]
|
199
|
+
if level
|
200
|
+
value
|
201
|
+
elsif trace? name
|
202
|
+
@parent[name]
|
203
|
+
elsif type == :ivar
|
204
|
+
self_instance_variable_get name
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def set_const(nesting, path, value)
|
209
|
+
key = [nesting.__id__, path].join('::')
|
210
|
+
@table[key] = [0, value]
|
211
|
+
end
|
212
|
+
|
213
|
+
def set_cvar(nesting, path, name, value)
|
214
|
+
key = [name, nesting.__id__, path].join('::')
|
215
|
+
@table[key] = [0, value]
|
216
|
+
end
|
217
|
+
|
218
|
+
def []=(name, value)
|
219
|
+
type = RootScope.type_by_name(name)
|
220
|
+
if type == :const
|
221
|
+
if name.include?('::')
|
222
|
+
@table[name] = [0, value]
|
223
|
+
else
|
224
|
+
parent_module, parent_path = module_nesting.first
|
225
|
+
set_const parent_module, [*parent_path, name], value
|
226
|
+
end
|
227
|
+
return
|
228
|
+
elsif type == :cvar
|
229
|
+
if name.include?('::')
|
230
|
+
@table[name] = [0, value]
|
231
|
+
else
|
232
|
+
parent_module, parent_path = module_nesting.first
|
233
|
+
set_cvar parent_module, parent_path, name, value
|
234
|
+
end
|
235
|
+
return
|
236
|
+
end
|
237
|
+
variable_level = level_of name, type
|
238
|
+
@table[name] = [variable_level, value] if variable_level
|
239
|
+
end
|
240
|
+
|
241
|
+
def self_type
|
242
|
+
@self_type || @parent.self_type
|
243
|
+
end
|
244
|
+
|
245
|
+
def global_variables
|
246
|
+
gvar_keys = @table.keys.select do |name|
|
247
|
+
RootScope.type_by_name(name) == :gvar
|
248
|
+
end
|
249
|
+
gvar_keys | @parent.global_variables
|
250
|
+
end
|
251
|
+
|
252
|
+
def local_variables
|
253
|
+
lvar_keys = @table.keys.select do |name|
|
254
|
+
RootScope.type_by_name(name) == :lvar
|
255
|
+
end
|
256
|
+
lvar_keys |= @parent.local_variables if @trace_lvar
|
257
|
+
lvar_keys
|
258
|
+
end
|
259
|
+
|
260
|
+
def table_constants
|
261
|
+
constants = module_nesting.flat_map do |mod, path|
|
262
|
+
prefix = [mod.__id__, *path].join('::') + '::'
|
263
|
+
@table.keys.select { _1.start_with? prefix }.map { _1.delete_prefix(prefix).split('::').first }
|
264
|
+
end.uniq
|
265
|
+
constants |= @parent.table_constants if @parent.mutable?
|
266
|
+
constants
|
267
|
+
end
|
268
|
+
|
269
|
+
def table_module_constants(mod)
|
270
|
+
prefix = "#{mod.__id__}::"
|
271
|
+
constants = @table.keys.select { _1.start_with? prefix }.map { _1.delete_prefix(prefix).split('::').first }
|
272
|
+
constants |= @parent.table_constants if @parent.mutable?
|
273
|
+
constants
|
274
|
+
end
|
275
|
+
|
276
|
+
def base_scope
|
277
|
+
@parent.mutable? ? @parent.base_scope : @parent
|
278
|
+
end
|
279
|
+
|
280
|
+
def table_instance_variables
|
281
|
+
ivars = @table.keys.select { RootScope.type_by_name(_1) == :ivar }
|
282
|
+
ivars |= @parent.table_instance_variables if @parent.mutable? && @trace_ivar
|
283
|
+
ivars
|
284
|
+
end
|
285
|
+
|
286
|
+
def instance_variables
|
287
|
+
self_singleton_types = self_type.types.grep(Types::SingletonType)
|
288
|
+
singleton_classes = self_type.types.grep(Types::InstanceType).map(&:klass).select(&:singleton_class?)
|
289
|
+
base_self = base_scope.self_object
|
290
|
+
self_instance_variables = singleton_classes.flat_map do |singleton_class|
|
291
|
+
if singleton_class.respond_to? :attached_object
|
292
|
+
Methods::OBJECT_INSTANCE_VARIABLES_METHOD.bind_call(singleton_class.attached_object).map(&:to_s)
|
293
|
+
elsif singleton_class == Methods::OBJECT_SINGLETON_CLASS_METHOD.bind_call(base_self)
|
294
|
+
Methods::OBJECT_INSTANCE_VARIABLES_METHOD.bind_call(base_self).map(&:to_s)
|
295
|
+
else
|
296
|
+
[]
|
297
|
+
end
|
298
|
+
end
|
299
|
+
[
|
300
|
+
self_singleton_types.flat_map { _1.module_or_class.instance_variables.map(&:to_s) },
|
301
|
+
self_instance_variables || [],
|
302
|
+
table_instance_variables
|
303
|
+
].inject(:|)
|
304
|
+
end
|
305
|
+
|
306
|
+
def self_instance_variable_get(name)
|
307
|
+
self_objects = self_type.types.grep(Types::SingletonType).map(&:module_or_class)
|
308
|
+
singleton_classes = self_type.types.grep(Types::InstanceType).map(&:klass).select(&:singleton_class?)
|
309
|
+
base_self = base_scope.self_object
|
310
|
+
singleton_classes.each do |singleton_class|
|
311
|
+
if singleton_class.respond_to? :attached_object
|
312
|
+
self_objects << singleton_class.attached_object
|
313
|
+
elsif singleton_class == base_self.singleton_class
|
314
|
+
self_objects << base_self
|
315
|
+
end
|
316
|
+
end
|
317
|
+
types = self_objects.map do |object|
|
318
|
+
value = begin
|
319
|
+
Methods::OBJECT_INSTANCE_VARIABLE_GET_METHOD.bind_call(object, name)
|
320
|
+
rescue NameError
|
321
|
+
end
|
322
|
+
Types.type_from_object value
|
323
|
+
end
|
324
|
+
Types::UnionType[*types]
|
325
|
+
end
|
326
|
+
|
327
|
+
def table_class_variables
|
328
|
+
cvars = @table.keys.filter_map { _1.split('::', 2).first if RootScope.type_by_name(_1) == :cvar }
|
329
|
+
cvars |= @parent.table_class_variables if @parent.mutable?
|
330
|
+
cvars
|
331
|
+
end
|
332
|
+
|
333
|
+
def class_variables
|
334
|
+
cvars = table_class_variables
|
335
|
+
m, = module_nesting.first
|
336
|
+
cvars |= m.class_variables.map(&:to_s) if m.is_a? Module
|
337
|
+
cvars
|
338
|
+
end
|
339
|
+
|
340
|
+
def constants
|
341
|
+
module_nesting.flat_map do |nest,|
|
342
|
+
nest.constants
|
343
|
+
end.map(&:to_s) | table_constants
|
344
|
+
end
|
345
|
+
|
346
|
+
def merge_jumps
|
347
|
+
if terminated?
|
348
|
+
@terminated = false
|
349
|
+
@table = @mergeable_changes
|
350
|
+
merge @jump_branches
|
351
|
+
@terminated = true
|
352
|
+
else
|
353
|
+
merge [*@jump_branches, {}]
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
def conditional(&block)
|
358
|
+
run_branches(block, ->(_s) {}).first || Types::NIL
|
359
|
+
end
|
360
|
+
|
361
|
+
def never(&block)
|
362
|
+
block.call Scope.new(self, { BREAK_RESULT => nil, NEXT_RESULT => nil, PATTERNMATCH_BREAK => nil, RETURN_RESULT => nil })
|
363
|
+
end
|
364
|
+
|
365
|
+
def run_branches(*blocks)
|
366
|
+
results = []
|
367
|
+
branches = []
|
368
|
+
blocks.each do |block|
|
369
|
+
scope = Scope.new self
|
370
|
+
result = block.call scope
|
371
|
+
next if scope.terminated?
|
372
|
+
results << result
|
373
|
+
branches << scope.mergeable_changes
|
374
|
+
end
|
375
|
+
terminate if branches.empty?
|
376
|
+
merge branches
|
377
|
+
results
|
378
|
+
end
|
379
|
+
|
380
|
+
def has_own?(name)
|
381
|
+
@table.key? name
|
382
|
+
end
|
383
|
+
|
384
|
+
def update(child_scope)
|
385
|
+
current_level = level
|
386
|
+
child_scope.mergeable_changes.each do |name, (level, value)|
|
387
|
+
self[name] = value if level <= current_level
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
391
|
+
protected
|
392
|
+
|
393
|
+
def merge(branches)
|
394
|
+
current_level = level
|
395
|
+
merge = {}
|
396
|
+
branches.each do |changes|
|
397
|
+
changes.each do |name, (level, value)|
|
398
|
+
next if current_level < level
|
399
|
+
(merge[name] ||= []) << value
|
400
|
+
end
|
401
|
+
end
|
402
|
+
merge.each do |name, values|
|
403
|
+
values << self[name] unless values.size == branches.size
|
404
|
+
values.compact!
|
405
|
+
self[name] = Types::UnionType[*values.compact] unless values.empty?
|
406
|
+
end
|
407
|
+
end
|
408
|
+
end
|
409
|
+
end
|