Module: ReplTypeCompletor::Types

Defined in:
lib/repl_type_completor/types.rb

Defined Under Namespace

Classes: InstanceType, InterfaceType, SingletonType, UnionType

Constant Summary collapse

OBJECT_TO_TYPE_SAMPLE_SIZE =
50
EXPANSION_NESTING_LIMIT =
3
NIL =
InstanceType.new NilClass
OBJECT =
InstanceType.new Object
TRUE =
InstanceType.new TrueClass
FALSE =
InstanceType.new FalseClass
SYMBOL =
InstanceType.new Symbol
STRING =
InstanceType.new String
INTEGER =
InstanceType.new Integer
RANGE =
InstanceType.new Range
REGEXP =
InstanceType.new Regexp
FLOAT =
InstanceType.new Float
RATIONAL =
InstanceType.new Rational
COMPLEX =
InstanceType.new Complex
ARRAY =
InstanceType.new Array
HASH =
InstanceType.new Hash
CLASS =
InstanceType.new Class
MODULE =
InstanceType.new Module
PROC =
InstanceType.new Proc
BOOLEAN =

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Attribute Details

#rbs_builderObject (readonly)

Returns the value of attribute rbs_builder.



13
14
15
# File 'lib/repl_type_completor/types.rb', line 13

def rbs_builder
  @rbs_builder
end

#rbs_load_errorObject (readonly)

Returns the value of attribute rbs_load_error.



13
14
15
# File 'lib/repl_type_completor/types.rb', line 13

def rbs_load_error
  @rbs_load_error
end

Class Method Details

._match_free_variable(vars, rbs_type, value, accumulator, nesting = 0) ⇒ Object



574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
# File 'lib/repl_type_completor/types.rb', line 574

def self._match_free_variable(vars, rbs_type, value, accumulator, nesting = 0)
  case [rbs_type, value]
  in [RBS::Types::Variable,]
    (accumulator[rbs_type.name] ||= []) << value if vars.include? rbs_type.name
  in [RBS::Types::ClassInstance, InstanceType]
    names = rbs_builder.build_singleton(rbs_type.name).type_params
    names.zip(rbs_type.args).each do |name, arg|
      v = value.named_params[name]
      _match_free_variable vars, arg, v, accumulator, nesting if v
    end
  in [RBS::Types::Tuple, InstanceType] if value.klass == Array
    v = value.params[0]
    rbs_type.types.each do |t|
      _match_free_variable vars, t, v, accumulator, nesting
    end
  in [RBS::Types::Record, InstanceType] if value.klass == Hash
    # TODO
  in [RBS::Types::Interface,] if nesting < EXPANSION_NESTING_LIMIT
    definition = rbs_builder.build_interface rbs_type.name
    convert = {}
    definition.type_params.zip(rbs_type.args).each do |from, arg|
      convert[from] = arg.name if arg.is_a? RBS::Types::Variable
    end
    return if convert.empty?
    ac = {}
    definition.methods.each do |method_name, method|
      return_type = method_return_type value, method_name
      method.defs.each do |method_def|
        interface_return_type = method_def.type.type.return_type
        _match_free_variable convert, interface_return_type, return_type, ac, nesting + 1
      end
    end
    convert.each do |from, to|
      values = ac[from]
      (accumulator[to] ||= []).concat values if values
    end
  in [RBS::Types::Union,]
    rbs_type.types.each do |t|
      _match_free_variable vars, t, value, accumulator, nesting
    end
  in [RBS::Types::Alias,]
    expanded = expand_alias_type(rbs_type, nesting)
    _match_free_variable vars, expanded, value, accumulator, nesting + 1 if expanded
  else
  end
end

.accessor_method_return_type(type, method_name) ⇒ Object



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/repl_type_completor/types.rb', line 116

def self.accessor_method_return_type(type, method_name)
  return unless method_name.match?(/\A[a-z_][a-z_0-9]*\z/)

  ivar_name = :"@#{method_name}"
  instances = type.types.filter_map do |t|
    case t
    in SingletonType
      t.module_or_class
    in InstanceType
      t.instances
    in InterfaceType
      nil
    end
  end.flatten
  instances = instances.sample(OBJECT_TO_TYPE_SAMPLE_SIZE) if instances.size > OBJECT_TO_TYPE_SAMPLE_SIZE
  objects = []
  instances.each do |instance|
    if Methods::OBJECT_INSTANCE_VARIABLE_DEFINED_METHOD.bind_call(instance, ivar_name)
      objects << Methods::OBJECT_INSTANCE_VARIABLE_GET_METHOD.bind_call(instance, ivar_name)
    end
  end
  union_type_from_objects(objects) unless objects.empty?
end

.array_of(*types) ⇒ Object



474
475
476
477
# File 'lib/repl_type_completor/types.rb', line 474

def self.array_of(*types)
  type = types.size >= 2 ? UnionType[*types] : types.first || OBJECT
  InstanceType.new(Array, [type])
end

.class_name_of(klass) ⇒ Object



56
57
58
59
60
61
62
63
# File 'lib/repl_type_completor/types.rb', line 56

def self.class_name_of(klass)
  while true
    name = Methods::MODULE_NAME_METHOD.bind_call klass
    return name if name

    klass = klass.superclass
  end
end

.expand_alias_type(rbs_type, nesting) ⇒ Object

Alias may validly recurse through type args (type json = Integer | Array[json]) and can expand exponentially (type b = a | [a] chains), so expansion is depth limited.



481
482
483
484
485
# File 'lib/repl_type_completor/types.rb', line 481

def self.expand_alias_type(rbs_type, nesting)
  return if nesting >= EXPANSION_NESTING_LIMIT

  rbs_builder.expand_alias2 rbs_type.name, rbs_type.args rescue nil
end

.from_rbs_type(return_type, self_type, extra_vars = {}, nesting = 0) ⇒ Object



487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
# File 'lib/repl_type_completor/types.rb', line 487

def self.from_rbs_type(return_type, self_type, extra_vars = {}, nesting = 0)
  case return_type
  when RBS::Types::Bases::Self
    self_type
  when RBS::Types::Bases::Bottom, RBS::Types::Bases::Nil
    NIL
  when RBS::Types::Bases::Any, RBS::Types::Bases::Void
    OBJECT
  when RBS::Types::Bases::Class
    self_type.transform do |type|
      case type
      in SingletonType
        InstanceType.new(type.module_or_class.is_a?(Class) ? Class : Module)
      in InstanceType
        SingletonType.new type.klass
      in InterfaceType
        CLASS
      end
    end
  when RBS::Types::Bases::Bool
    BOOLEAN
  when RBS::Types::Bases::Instance
    self_type.transform do |type|
      if type.is_a?(SingletonType) && type.module_or_class.is_a?(Class)
        InstanceType.new type.module_or_class
      elsif type.is_a?(InstanceType)
        InstanceType.new type.klass
      else
        OBJECT
      end
    end
  when RBS::Types::Union, RBS::Types::Intersection
    # Intersection is unsupported. fallback to union type
    UnionType[*return_type.types.map { from_rbs_type _1, self_type, extra_vars, nesting }]
  when RBS::Types::Proc
    PROC
  when RBS::Types::Tuple
    elem = UnionType[*return_type.types.map { from_rbs_type _1, self_type, extra_vars, nesting }]
    InstanceType.new(Array, [elem])
  when RBS::Types::Record
    InstanceType.new(Hash, [SYMBOL, OBJECT])
  when RBS::Types::Literal
    InstanceType.new return_type.literal.class
  when RBS::Types::Variable
    if extra_vars.key? return_type.name
      extra_vars[return_type.name]
    elsif self_type.is_a?(InstanceType) || self_type.is_a?(InterfaceType)
      self_type.named_params[return_type.name] || OBJECT
    elsif self_type.is_a? UnionType
      types = self_type.types.filter_map do |t|
        t.named_params[return_type.name] if t.is_a?(InstanceType) || t.is_a?(InterfaceType)
      end
      UnionType[*types]
    else
      OBJECT
    end
  when RBS::Types::Optional
    UnionType[from_rbs_type(return_type.type, self_type, extra_vars, nesting), NIL]
  when RBS::Types::Alias
    expanded = expand_alias_type(return_type, nesting)
    expanded ? from_rbs_type(expanded, self_type, extra_vars, nesting + 1) : OBJECT
  when RBS::Types::Interface
    args = return_type.args.map { from_rbs_type _1, self_type, extra_vars, nesting }
    InterfaceType.new return_type.name, args
  when RBS::Types::ClassInstance
    klass = return_type.name.to_namespace.path.reduce(Object) { _1.const_get _2 }
    if return_type.args
      params = return_type.args.map { from_rbs_type _1, self_type, extra_vars, nesting }
    end
    InstanceType.new(klass, params || [])
  else
    OBJECT
  end
end

.intersect?(a, b) ⇒ Boolean

Returns:

  • (Boolean)


199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/repl_type_completor/types.rb', line 199

def self.intersect?(a, b)
  atypes = a.types.group_by(&:class)
  btypes = b.types.group_by(&:class)
  if atypes[SingletonType] && btypes[SingletonType]
    aa, bb = [atypes, btypes].map {|types| types[SingletonType].map(&:module_or_class) }
    return true if (aa & bb).any?
  end

  aa, bb = [atypes, btypes].map {|types| (types[InstanceType] || []).map(&:klass) }
  return true if (aa.flat_map(&:ancestors) & bb).any?

  [[atypes[InterfaceType], b], [btypes[InterfaceType], a]].any? do |interfaces, other|
    interfaces&.any? do |interface|
      methods = interface.methods
      !methods.empty? && other.types.any? { |t| (methods - t.methods).empty? }
    end
  end
end

.load_rbs_builderObject



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/repl_type_completor/types.rb', line 27

def self.load_rbs_builder
  @load_started = true
  loader = RBS::CLI::LibraryOptions.new.loader
  sig_path = Pathname('sig')
  loader.add path: sig_path
  expanded_sig_path = sig_path.expand_path.to_s

  unless File.exist?('rbs_collection.yaml')
    # Load rbs signature from gems. This is a fallback when rbs_collection.yaml is not available.
    Gem.loaded_specs.values.each do |spec|
      gem_sig_path = File.expand_path("#{spec.gem_dir}/sig")
      loader.add(library: spec.name, version: spec.version) if Dir.exist?(gem_sig_path) && expanded_sig_path != gem_sig_path
    end
  end

  # Hack to make this thread priority lower, not to block the main thread.
  thread_pass_counter = 0
  tracepoint = TracePoint.new(:call) do
    Thread.pass if ((thread_pass_counter += 1) % 10).zero?
  end
  tracepoint.enable do
    env = RBS::Environment.from_loader(loader)
    @rbs_builder = RBS::DefinitionBuilder.new env: env.resolve_type_names
  end
rescue LoadError, StandardError => e
  @rbs_load_error = e
  nil
end

.match_free_variables(vars, types, values) ⇒ Object



566
567
568
569
570
571
572
# File 'lib/repl_type_completor/types.rb', line 566

def self.match_free_variables(vars, types, values)
  accumulator = {}
  types.zip values do |t, v|
    _match_free_variable(vars, t, v, accumulator) if v
  end
  accumulator.transform_values { UnionType[*_1] }
end

.method_return_bottom?(method) ⇒ Boolean

Returns:

  • (Boolean)


562
563
564
# File 'lib/repl_type_completor/types.rb', line 562

def self.method_return_bottom?(method)
  method.type.return_type.is_a? RBS::Types::Bases::Bottom
end

.method_return_type(type, method_name) ⇒ Object



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/repl_type_completor/types.rb', line 95

def self.method_return_type(type, method_name)
  receivers = type.types.map do |t|
    case t
    in SingletonType
      [t, t.module_or_class, true]
    in InstanceType
      [t, t.klass, false]
    in InterfaceType
      [t, t, false]
    end
  end
  types = receivers.flat_map do |receiver_type, klass, singleton|
    method = rbs_search_method klass, method_name, singleton
    next [] unless method
    method.method_types.map do |method|
      from_rbs_type(method.type.return_type, receiver_type, {})
    end
  end
  UnionType[*types]
end

.preload_rbs_builderObject



19
20
21
22
23
24
25
# File 'lib/repl_type_completor/types.rb', line 19

def self.preload_rbs_builder
  return if rbs_load_started?
  @load_started = true
  Thread.new do
    load_rbs_builder
  end
end

.rbs_absolute_type_name(name) ⇒ Object



66
67
68
# File 'lib/repl_type_completor/types.rb', line 66

def self.rbs_absolute_type_name(name)
  RBS::TypeName.parse(name).absolute!
end

.rbs_load_started?Boolean

Returns:

  • (Boolean)


15
16
17
# File 'lib/repl_type_completor/types.rb', line 15

def self.rbs_load_started?
  !!@load_started
end

.rbs_methods(type, method_name, args_types, kwargs_type, has_block) ⇒ Object



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/repl_type_completor/types.rb', line 140

def self.rbs_methods(type, method_name, args_types, kwargs_type, has_block)
  return [] unless rbs_builder

  receivers = type.types.map do |t|
    case t
    in SingletonType
      [t, t.module_or_class, true]
    in InstanceType
      [t, t.klass, false]
    in InterfaceType
      [t, t, false]
    end
  end
  has_splat = args_types.include?(nil)
  methods_with_score = receivers.flat_map do |receiver_type, klass, singleton|
    method = rbs_search_method klass, method_name, singleton
    next [] unless method
    method.method_types.filter_map do |method_type|
      next unless method_type.type.respond_to?(:required_positionals)

      score = 0
      score += 2 if !!method_type.block == has_block
      reqs = method_type.type.required_positionals
      opts = method_type.type.optional_positionals
      rest = method_type.type.rest_positionals
      trailings = method_type.type.trailing_positionals
      keyreqs = method_type.type.required_keywords
      keyopts = method_type.type.optional_keywords
      keyrest = method_type.type.rest_keywords
      args = args_types
      if kwargs_type&.any? && keyreqs.empty? && keyopts.empty? && keyrest.nil?
        kw_value_type = UnionType[*kwargs_type.values]
        args += [InstanceType.new(Hash, [SYMBOL, kw_value_type])]
      end
      if has_splat
        score += 1 if args.count(&:itself) <= reqs.size + opts.size + trailings.size
      elsif reqs.size + trailings.size <= args.size && (rest || args.size <= reqs.size + opts.size + trailings.size)
        score += 2
        centers = args[reqs.size...-trailings.size]
        given = args.first(reqs.size) + centers.take(opts.size) + args.last(trailings.size)
        expected = (reqs + opts.take(centers.size) + trailings).map(&:type)
        if rest
          given << UnionType[*centers.drop(opts.size)]
          expected << rest.type
        end
        if given.any?
          score += given.zip(expected).count do |t, e|
            e = from_rbs_type e, receiver_type
            intersect?(t, e) || (intersect?(STRING, e) && t.methods.include?(:to_str)) || (intersect?(INTEGER, e) && t.methods.include?(:to_int)) || (intersect?(ARRAY, e) && t.methods.include?(:to_ary))
          end.fdiv(given.size)
        end
      end
      [[method_type, given || [], expected || []], score]
    end
  end
  max_score = methods_with_score.map(&:last).max
  methods_with_score.select { _2 == max_score }.map(&:first)
end

.rbs_search_method(klass, method_name, singleton) ⇒ Object



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/repl_type_completor/types.rb', line 76

def self.rbs_search_method(klass, method_name, singleton)
  return unless rbs_builder

  if klass.is_a? InterfaceType
    definition = klass.definition
    return definition && definition.methods[method_name]
  end

  klass.ancestors.each do |ancestor|
    next unless (name = Methods::MODULE_NAME_METHOD.bind_call(ancestor))

    type_name = rbs_absolute_type_name(name)
    definition = (singleton ? rbs_builder.build_singleton(type_name) : rbs_builder.build_instance(type_name)) rescue nil
    method = definition.methods[method_name] if definition
    return method if method
  end
  nil
end

.type_from_object(object) ⇒ Object



218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/repl_type_completor/types.rb', line 218

def self.type_from_object(object)
  case object
  when Array
    InstanceType.new Array, nil, [object]
  when Hash
    InstanceType.new Hash, nil, [object]
  when Module
    SingletonType.new object
  else
    InstanceType.new Methods::OBJECT_CLASS_METHOD.bind_call(object), nil, [object]
  end
end

.union_type_from_objects(objects) ⇒ Object



231
232
233
234
235
236
# File 'lib/repl_type_completor/types.rb', line 231

def self.union_type_from_objects(objects)
  instances = objects.size <= OBJECT_TO_TYPE_SAMPLE_SIZE ? objects : objects.sample(OBJECT_TO_TYPE_SAMPLE_SIZE)
  modules, instances = instances.partition { Module === _1 }
  class_instances = instances.group_by { Methods::OBJECT_CLASS_METHOD.bind_call(_1) }
  UnionType[*class_instances.map { InstanceType.new _1, nil, _2 }, *modules.uniq.map { SingletonType.new _1 }]
end

.union_type_from_objects_list(objects_list) ⇒ Object



238
239
240
241
242
243
# File 'lib/repl_type_completor/types.rb', line 238

def self.union_type_from_objects_list(objects_list)
  objects = objects_list.flat_map do |objects|
    objects.size < OBJECT_TO_TYPE_SAMPLE_SIZE ? objects : objects.sample(OBJECT_TO_TYPE_SAMPLE_SIZE)
  end
  union_type_from_objects(objects)
end