repl_type_completor 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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