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.
@@ -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