Skip to content

Commit e56296b

Browse files
tompngclaude
andcommitted
Introduce InterfaceType for rbs interface and type alias resolution
rbs HEAD uses type aliases (range[T], array[U], etc.) in core method signatures where concrete classes and interfaces used to appear, which broke completion. Resolve type aliases from their definitions with expand_alias2 instead of mapping known alias names, and represent rbs interfaces as InterfaceType instead of falling back to Object. Interface types provide completion candidates from the interface definition, bind type variables through named_params, and match argument types by duck typing in overload scoring. Also fix RBS::Types::Bases::Class conversion that discarded the transform result and referenced an undefined variable. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent cf89f1f commit e56296b

4 files changed

Lines changed: 100 additions & 20 deletions

File tree

lib/repl_type_completor/type_analyzer.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1152,7 +1152,7 @@ def method_call(receiver, method_name, args, kwargs, block, scope, name_match: t
11521152
methods = Types.rbs_methods receiver, method_name.to_sym, args, kwargs, !!block
11531153
block_called = false
11541154
type_breaks = methods.map do |method, given_params, method_params|
1155-
receiver_vars = receiver.is_a?(Types::InstanceType) ? receiver.named_params : {}
1155+
receiver_vars = receiver.is_a?(Types::InstanceType) || receiver.is_a?(Types::InterfaceType) ? receiver.named_params : {}
11561156
free_vars = method.type.free_variables - receiver_vars.keys.to_set
11571157
vars = receiver_vars.merge Types.match_free_variables(free_vars, method_params, given_params)
11581158
if block && method.block && method.block.type.respond_to?(:required_positionals)

lib/repl_type_completor/types.rb

Lines changed: 71 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ def self.rbs_absolute_type_name(name)
7575
def self.rbs_search_method(klass, method_name, singleton)
7676
return unless rbs_builder
7777

78+
if klass.is_a? InterfaceType
79+
definition = klass.definition
80+
return definition && definition.methods[method_name]
81+
end
82+
7883
klass.ancestors.each do |ancestor|
7984
next unless (name = Methods::MODULE_NAME_METHOD.bind_call(ancestor))
8085

@@ -93,6 +98,8 @@ def self.method_return_type(type, method_name)
9398
[t, t.module_or_class, true]
9499
in InstanceType
95100
[t, t.klass, false]
101+
in InterfaceType
102+
[t, t, false]
96103
end
97104
end
98105
types = receivers.flat_map do |receiver_type, klass, singleton|
@@ -115,6 +122,8 @@ def self.accessor_method_return_type(type, method_name)
115122
t.module_or_class
116123
in InstanceType
117124
t.instances
125+
in InterfaceType
126+
nil
118127
end
119128
end.flatten
120129
instances = instances.sample(OBJECT_TO_TYPE_SAMPLE_SIZE) if instances.size > OBJECT_TO_TYPE_SAMPLE_SIZE
@@ -136,6 +145,8 @@ def self.rbs_methods(type, method_name, args_types, kwargs_type, has_block)
136145
[t, t.module_or_class, true]
137146
in InstanceType
138147
[t, t.klass, false]
148+
in InterfaceType
149+
[t, t, false]
139150
end
140151
end
141152
has_splat = args_types.include?(nil)
@@ -193,7 +204,14 @@ def self.intersect?(a, b)
193204
end
194205

195206
aa, bb = [atypes, btypes].map {|types| (types[InstanceType] || []).map(&:klass) }
196-
(aa.flat_map(&:ancestors) & bb).any?
207+
return true if (aa.flat_map(&:ancestors) & bb).any?
208+
209+
[[atypes[InterfaceType], b], [btypes[InterfaceType], a]].any? do |interfaces, other|
210+
interfaces&.any? do |interface|
211+
methods = interface.methods
212+
!methods.empty? && other.types.any? { |t| (methods - t.methods).empty? }
213+
end
214+
end
197215
end
198216

199217
def self.type_from_object(object)
@@ -340,6 +358,38 @@ def inspect_without_params
340358
end
341359
end
342360

361+
class InterfaceType
362+
attr_reader :name, :args
363+
364+
def initialize(name, args = [])
365+
@name = name
366+
@args = args
367+
end
368+
369+
def definition
370+
return @definition if defined?(@definition)
371+
372+
@definition = (Types.rbs_builder&.build_interface(@name) rescue nil)
373+
end
374+
375+
def transform() = yield(self)
376+
def methods() = definition ? definition.methods.keys : []
377+
def all_methods() = methods
378+
379+
def named_params
380+
definition ? definition.type_params.zip(@args).to_h.compact : {}
381+
end
382+
383+
def constants() = []
384+
def types() = [self]
385+
def nillable?() = false
386+
def nonnillable() = self
387+
388+
def inspect
389+
args.empty? ? name.name.to_s : "#{name.name}[#{args.map(&:inspect).join(', ')}]"
390+
end
391+
end
392+
343393
NIL = InstanceType.new NilClass
344394
OBJECT = InstanceType.new Object
345395
TRUE = InstanceType.new TrueClass
@@ -364,6 +414,7 @@ class UnionType
364414
def initialize(*types)
365415
@types = []
366416
singleton_types = []
417+
interface_types = {}
367418
instance_types = {}
368419
collect = -> type do
369420
case type
@@ -377,10 +428,12 @@ def initialize(*types)
377428
end
378429
in SingletonType
379430
singleton_types << type
431+
in InterfaceType
432+
interface_types[[type.name, type.args]] ||= type
380433
end
381434
end
382435
types.each(&collect)
383-
@types = singleton_types.uniq + instance_types.map do |klass, (params, instances)|
436+
@types = singleton_types.uniq + interface_types.values + instance_types.map do |klass, (params, instances)|
384437
params = params.map { |v| UnionType[*v] }
385438
InstanceType.new(klass, params, instances)
386439
end
@@ -434,12 +487,13 @@ def self.from_rbs_type(return_type, self_type, extra_vars = {})
434487
self_type.transform do |type|
435488
case type
436489
in SingletonType
437-
InstanceType.new(self_type.module_or_class.is_a?(Class) ? Class : Module)
490+
InstanceType.new(type.module_or_class.is_a?(Class) ? Class : Module)
438491
in InstanceType
439492
SingletonType.new type.klass
493+
in InterfaceType
494+
CLASS
440495
end
441496
end
442-
UnionType[*types]
443497
when RBS::Types::Bases::Bool
444498
BOOLEAN
445499
when RBS::Types::Bases::Instance
@@ -467,11 +521,11 @@ def self.from_rbs_type(return_type, self_type, extra_vars = {})
467521
when RBS::Types::Variable
468522
if extra_vars.key? return_type.name
469523
extra_vars[return_type.name]
470-
elsif self_type.is_a? InstanceType
524+
elsif self_type.is_a?(InstanceType) || self_type.is_a?(InterfaceType)
471525
self_type.named_params[return_type.name] || OBJECT
472526
elsif self_type.is_a? UnionType
473527
types = self_type.types.filter_map do |t|
474-
t.named_params[return_type.name] if t.is_a? InstanceType
528+
t.named_params[return_type.name] if t.is_a?(InstanceType) || t.is_a?(InterfaceType)
475529
end
476530
UnionType[*types]
477531
else
@@ -480,20 +534,11 @@ def self.from_rbs_type(return_type, self_type, extra_vars = {})
480534
when RBS::Types::Optional
481535
UnionType[from_rbs_type(return_type.type, self_type, extra_vars), NIL]
482536
when RBS::Types::Alias
483-
case return_type.name.name
484-
when :int
485-
INTEGER
486-
when :boolish
487-
BOOLEAN
488-
when :string
489-
STRING
490-
else
491-
# TODO: ???
492-
OBJECT
493-
end
537+
expanded = (rbs_builder.expand_alias2 return_type.name, return_type.args rescue nil)
538+
expanded ? from_rbs_type(expanded, self_type, extra_vars) : OBJECT
494539
when RBS::Types::Interface
495-
# unimplemented
496-
OBJECT
540+
args = return_type.args.map { from_rbs_type _1, self_type, extra_vars }
541+
InterfaceType.new return_type.name, args
497542
when RBS::Types::ClassInstance
498543
klass = return_type.name.to_namespace.path.reduce(Object) { _1.const_get _2 }
499544
if return_type.args
@@ -553,6 +598,13 @@ def self._match_free_variable(vars, rbs_type, value, accumulator)
553598
values = ac[from]
554599
(accumulator[to] ||= []).concat values if values
555600
end
601+
in [RBS::Types::Union,]
602+
rbs_type.types.each do |t|
603+
_match_free_variable vars, t, value, accumulator
604+
end
605+
in [RBS::Types::Alias,]
606+
expanded = rbs_builder.expand_alias2 rbs_type.name, rbs_type.args rescue nil
607+
_match_free_variable vars, expanded, value, accumulator if expanded
556608
else
557609
end
558610
end

test/repl_type_completor/test_type_analyze.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -774,6 +774,8 @@ def test_block_args
774774
def test_array_aref
775775
assert_call('[1][0..].', include: [Array, NilClass], exclude: Integer)
776776
assert_call('[1][0].', include: Integer, exclude: [Array, NilClass])
777+
# Float matches the `int` index param by duck typing (Float#to_int)
778+
assert_call('[1][0.5].', include: Integer, exclude: Array)
777779
assert_call('[1].[](0).', include: Integer, exclude: [Array, NilClass])
778780
assert_call('[1].[](0){}.', include: Integer, exclude: [Array, NilClass])
779781
end

test/repl_type_completor/test_types.rb

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,32 @@ def foobar; end
9494
assert_include type.all_methods, :rand
9595
end
9696

97+
def type_from_rbs(rbs_string)
98+
ReplTypeCompletor::Types.load_rbs_builder unless ReplTypeCompletor::Types.rbs_builder
99+
rbs_type = RBS::Parser.parse_type(rbs_string)
100+
ReplTypeCompletor::Types.from_rbs_type(rbs_type, ReplTypeCompletor::Types::OBJECT)
101+
end
102+
103+
def test_interface_type
104+
to_int = type_from_rbs('::_ToInt')
105+
assert_equal '_ToInt', to_int.inspect
106+
assert_equal [:to_int], to_int.methods
107+
assert ReplTypeCompletor::Types.intersect?(ReplTypeCompletor::Types::FLOAT, to_int)
108+
assert ReplTypeCompletor::Types.intersect?(to_int, ReplTypeCompletor::Types::FLOAT)
109+
refute ReplTypeCompletor::Types.intersect?(ReplTypeCompletor::Types::STRING, to_int)
110+
111+
to_ary = type_from_rbs('::_ToAry[::Integer]')
112+
assert_equal '_ToAry[Integer]', to_ary.inspect
113+
return_type = ReplTypeCompletor::Types.method_return_type(to_ary, :to_ary)
114+
assert_equal Array, return_type.klass
115+
assert_equal Integer, return_type.params[0].klass
116+
end
117+
118+
def test_alias_type_expansion
119+
int_type = type_from_rbs('::int')
120+
assert_equal 'Integer | _ToInt', int_type.inspect
121+
end
122+
97123
def test_basic_object_methods
98124
bo = BasicObject.new
99125
def bo.foobar; end

0 commit comments

Comments
 (0)