Skip to content

Commit a8d1547

Browse files
committed
Add TypeCompletor (YARP(0.11.0)+RBS completion)
1 parent bb385c8 commit a8d1547

File tree

5 files changed

+2022
-3
lines changed

5 files changed

+2022
-3
lines changed

lib/irb/completion.rb

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,27 @@ def self.retrieve_files_to_require_relative_from_current_dir
6464
end
6565

6666
singleton_class.attr_accessor :completion_analyze_result
67+
singleton_class.attr_reader :completion_method, :completor
6768

68-
def self.completor
69-
require_relative 'completion/regexp_completor'
70-
RegexpCompletor
69+
def self.completion_method=(completion_method)
70+
case completion_method
71+
when :regexp
72+
require_relative 'completion/regexp_completor'
73+
@completor = RegexpCompletor
74+
when :type
75+
require_relative 'completion/type_completor'
76+
@completor = TypeCompletor
77+
else
78+
raise ArgumentError
79+
end
80+
@completion_method = completion_method
81+
end
82+
83+
# TODO: make it configurable
84+
begin
85+
self.completion_method = :type
86+
rescue StandardError, LoadError
87+
self.completion_method = :regexp
7188
end
7289

7390
CompletionProc = lambda { |target, preposing = nil, postposing = nil|

lib/irb/completion/scope.rb

Lines changed: 318 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,318 @@
1+
require 'set'
2+
require_relative 'types'
3+
4+
# TODO: rename module
5+
module KatakataIrb
6+
class BaseScope
7+
BREAK_RESULT = '%break'
8+
NEXT_RESULT = '%next'
9+
RETURN_RESULT = '%return'
10+
PATTERNMATCH_BREAK = '%match'
11+
RAISE_BREAK = '%raise'
12+
13+
attr_reader :module_nesting
14+
15+
def initialize(binding, self_object, local_variables)
16+
@binding = binding
17+
@self_object = self_object
18+
@cache = {}
19+
modules = [*binding.eval('Module.nesting'), Object]
20+
@module_nesting = modules.map { [_1, []] }
21+
binding_local_variables = binding.local_variables
22+
uninitialized_locals = local_variables - binding_local_variables
23+
uninitialized_locals.each { @cache[_1] = KatakataIrb::Types::NIL }
24+
@local_variables = (local_variables | binding_local_variables).map(&:to_s).to_set
25+
@global_variables = Kernel.global_variables.map(&:to_s).to_set
26+
@owned_constants_cache = {}
27+
end
28+
29+
def level() = 0
30+
31+
def level_of(_name) = 0
32+
33+
def mutable?() = false
34+
35+
def module_own_constant?(mod, name)
36+
set = (@owned_constants_cache[mod] ||= Set.new(mod.constants.map(&:to_s)))
37+
set.include? name
38+
end
39+
40+
def get_const(nesting, path, _key = nil)
41+
result = path.reduce nesting do |mod, name|
42+
return nil unless mod.is_a?(Module) && module_own_constant?(mod, name)
43+
mod.const_get name
44+
end
45+
KatakataIrb::Types.type_from_object result
46+
end
47+
48+
def [](name)
49+
@cache[name] ||= (
50+
fallback = KatakataIrb::Types::NIL
51+
case BaseScope.type_by_name name
52+
when :cvar
53+
BaseScope.type_of(fallback: fallback) { @self_object.class_variable_get name }
54+
when :ivar
55+
BaseScope.type_of(fallback: fallback) { @self_object.instance_variable_get name }
56+
when :lvar
57+
BaseScope.type_of(fallback: fallback) { @binding.local_variable_get(name) }
58+
when :const
59+
BaseScope.type_of(fallback: fallback) { @binding.eval name }
60+
when :gvar
61+
BaseScope.type_of(fallback: fallback) { @binding.eval name if @global_variables.include? name }
62+
end
63+
)
64+
end
65+
66+
def self_type
67+
KatakataIrb::Types.type_from_object @self_object
68+
end
69+
70+
def local_variables() = @local_variables.to_a
71+
72+
def global_variables() = @global_variables.to_a
73+
74+
def self.type_of(fallback: KatakataIrb::Types::OBJECT)
75+
begin
76+
KatakataIrb::Types.type_from_object yield
77+
rescue
78+
fallback
79+
end
80+
end
81+
82+
def self.type_by_name(name)
83+
if name.start_with? '@@'
84+
:cvar
85+
elsif name.start_with? '@'
86+
:ivar
87+
elsif name.start_with? '$'
88+
:gvar
89+
elsif name.start_with? '%'
90+
:internal
91+
elsif name[0].downcase != name[0]
92+
:const
93+
else
94+
:lvar
95+
end
96+
end
97+
end
98+
99+
class Scope < BaseScope
100+
attr_reader :parent, :mergeable_changes, :level, :module_nesting
101+
102+
def self.from_binding(binding, locals) = new(BaseScope.new(binding, binding.eval('self'), locals))
103+
104+
def initialize(parent, table = {}, trace_cvar: true, trace_ivar: true, trace_lvar: true, self_type: nil, nesting: nil)
105+
@parent = parent
106+
@level = parent.level + 1
107+
@trace_cvar = trace_cvar
108+
@trace_ivar = trace_ivar
109+
@trace_lvar = trace_lvar
110+
@module_nesting = nesting ? [nesting, *parent.module_nesting] : parent.module_nesting
111+
@self_type = self_type
112+
@terminated = false
113+
@jump_branches = []
114+
@mergeable_changes = @table = table.transform_values { [level, _1] }
115+
end
116+
117+
def mutable? = true
118+
119+
def terminated?
120+
@terminated
121+
end
122+
123+
def terminate_with(type, value)
124+
return if terminated?
125+
store_jump type, value, @mergeable_changes
126+
terminate
127+
end
128+
129+
def store_jump(type, value, changes)
130+
return if terminated?
131+
if has_own?(type)
132+
changes[type] = [level, value]
133+
@jump_branches << changes
134+
elsif @parent.mutable?
135+
@parent.store_jump(type, value, changes)
136+
end
137+
end
138+
139+
def terminate
140+
return if terminated?
141+
@terminated = true
142+
@table = @mergeable_changes.dup
143+
end
144+
145+
def trace?(name)
146+
return false unless @parent
147+
type = BaseScope.type_by_name(name)
148+
type == :cvar ? @trace_cvar : type == :ivar ? @trace_ivar : type == :lvar ? @trace_lvar : true
149+
end
150+
151+
def level_of(name)
152+
variable_level, = @table[name]
153+
variable_level || parent.level_of(name)
154+
end
155+
156+
def get_const(nesting, path, key = nil)
157+
key ||= [nesting.__id__, path].join('::')
158+
_l, value = @table[key]
159+
value || @parent.get_const(nesting, path, key)
160+
end
161+
162+
def [](name)
163+
if BaseScope.type_by_name(name) == :const
164+
module_nesting.each do |(nesting, path)|
165+
value = get_const nesting, [*path, name]
166+
return value if value
167+
end
168+
KatakataIrb::Types::NIL
169+
end
170+
level, value = @table[name]
171+
if level
172+
value
173+
elsif trace? name
174+
@parent[name] if trace? name
175+
end
176+
end
177+
178+
def set_const(nesting, path, value)
179+
key = [nesting.__id__, path].join('::')
180+
@table[key] = [0, value]
181+
end
182+
183+
def []=(name, value)
184+
if BaseScope.type_by_name(name) == :const
185+
parent_module, parent_path = module_nesting.first
186+
set_const parent_module, [*parent_path, name], value
187+
return
188+
end
189+
variable_level = level_of name
190+
@table[name] = [variable_level, value] if variable_level
191+
end
192+
193+
def self_type
194+
@self_type || @parent.self_type
195+
end
196+
197+
def global_variables
198+
gvar_keys = @table.keys.select do |name|
199+
BaseScope.type_by_name(name) == :gvar
200+
end
201+
gvar_keys | @parent.global_variables
202+
end
203+
204+
def local_variables
205+
lvar_keys = @table.keys.select do |name|
206+
BaseScope.type_by_name(name) == :lvar
207+
end
208+
lvar_keys |= @parent.local_variables if @trace_lvar
209+
lvar_keys
210+
end
211+
212+
def table_constants
213+
constants = module_nesting.flat_map do |mod, path|
214+
prefix = [mod.__id__, *path].join('::') + '::'
215+
@table.keys.select { _1.start_with? prefix }.map { _1.delete_prefix(prefix).split('::').first }
216+
end.uniq
217+
constants |= @parent.table_constants if @parent.mutable?
218+
constants
219+
end
220+
221+
def table_instance_variables
222+
ivars = @table.keys.select { BaseScope.type_by_name(_1) == :ivar }
223+
ivars |= @parent.table_instance_variables if @parent.mutable? && @trace_ivar
224+
ivars
225+
end
226+
227+
def instance_variables
228+
self_singleton_types = self_type.types.grep(KatakataIrb::Types::SingletonType)
229+
self_singleton_types.flat_map { _1.module_or_class.instance_variables } | table_instance_variables
230+
end
231+
232+
def class_variables
233+
cvars = @table.keys.select { BaseScope.type_by_name(_1) == :cvar }
234+
cvars |= @parent.class_variables if @parent.mutable? && @trace_cvar
235+
unless @trace_cvar
236+
m = module_nesting.first
237+
cvars |= m.class_variables if m.is_a? Module
238+
end
239+
cvars
240+
end
241+
242+
def constants
243+
module_nesting.flat_map do |nest,|
244+
nest.constants
245+
end.map(&:to_s) | table_constants
246+
end
247+
248+
def merge_jumps
249+
if terminated?
250+
@terminated = false
251+
@table = @mergeable_changes
252+
merge @jump_branches
253+
@terminated = true
254+
else
255+
merge [*@jump_branches, {}]
256+
end
257+
end
258+
259+
def conditional(&block)
260+
run_branches(block, ->(_s) {}).first || KatakataIrb::Types::NIL
261+
end
262+
263+
def never(&block)
264+
block.call Scope.new(self, { BREAK_RESULT => nil, NEXT_RESULT => nil, PATTERNMATCH_BREAK => nil, RETURN_RESULT => nil, RAISE_BREAK => nil })
265+
end
266+
267+
def run(*args, **option)
268+
scope = Scope.new(self, *args, **option)
269+
yield scope
270+
merge_jumps
271+
update scope
272+
end
273+
274+
def run_branches(*blocks)
275+
results = []
276+
branches = []
277+
blocks.each do |block|
278+
scope = Scope.new self
279+
result = block.call scope
280+
next if scope.terminated?
281+
results << result
282+
branches << scope.mergeable_changes
283+
end
284+
terminate if branches.empty?
285+
merge branches
286+
results
287+
end
288+
289+
def has_own?(name)
290+
@table.key? name
291+
end
292+
293+
def update(child_scope)
294+
current_level = level
295+
child_scope.mergeable_changes.each do |name, (level, value)|
296+
self[name] = value if level <= current_level
297+
end
298+
end
299+
300+
protected
301+
302+
def merge(branches)
303+
current_level = level
304+
merge = {}
305+
branches.each do |changes|
306+
changes.each do |name, (level, value)|
307+
next if current_level < level
308+
(merge[name] ||= []) << value
309+
end
310+
end
311+
merge.each do |name, values|
312+
values << self[name] unless values.size == branches.size
313+
values.compact!
314+
self[name] = KatakataIrb::Types::UnionType[*values.compact] unless values.empty?
315+
end
316+
end
317+
end
318+
end

0 commit comments

Comments
 (0)