Module: ReplTypeCompletor::Types

Defined in:
lib/repl_type_completor/types.rb

Defined Under Namespace

Classes: InstanceType, SingletonType, UnionType

Constant Summary collapse

OBJECT_TO_TYPE_SAMPLE_SIZE =
50
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.



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

def rbs_builder
  @rbs_builder
end

#rbs_load_errorObject (readonly)

Returns the value of attribute rbs_load_error.



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

def rbs_load_error
  @rbs_load_error
end

Class Method Details

._match_free_variable(vars, rbs_type, value, accumulator) ⇒ Object



476
477
478
479
480
481
482
483
484
485
486
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
# File 'lib/repl_type_completor/types.rb', line 476

def self._match_free_variable(vars, rbs_type, value, accumulator)
  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.params[name]
      _match_free_variable vars, arg, v, accumulator if v
    end
  in [RBS::Types::Tuple, InstanceType] if value.klass == Array
    v = value.params[:Elem]
    rbs_type.types.each do |t|
      _match_free_variable vars, t, v, accumulator
    end
  in [RBS::Types::Record, InstanceType] if value.klass == Hash
    # TODO
  in [RBS::Types::Interface,]
    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
      end
    end
    convert.each do |from, to|
      values = ac[from]
      (accumulator[to] ||= []).concat values if values
    end
  else
  end
end

.array_of(*types) ⇒ Object



374
375
376
377
# File 'lib/repl_type_completor/types.rb', line 374

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

.class_name_of(klass) ⇒ Object



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

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

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



379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
# File 'lib/repl_type_completor/types.rb', line 379

def self.from_rbs_type(return_type, self_type, extra_vars = {})
  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(self_type.module_or_class.is_a?(Class) ? Class : Module)
      in InstanceType
        SingletonType.new type.klass
      end
    end
    UnionType[*types]
  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 }]
  when RBS::Types::Proc
    PROC
  when RBS::Types::Tuple
    elem = UnionType[*return_type.types.map { from_rbs_type _1, self_type, extra_vars }]
    InstanceType.new Array, Elem: elem
  when RBS::Types::Record
    InstanceType.new Hash, K: SYMBOL, V: 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.params[return_type.name] || OBJECT
    elsif self_type.is_a? UnionType
      types = self_type.types.filter_map do |t|
        t.params[return_type.name] if t.is_a? InstanceType
      end
      UnionType[*types]
    else
      OBJECT
    end
  when RBS::Types::Optional
    UnionType[from_rbs_type(return_type.type, self_type, extra_vars), NIL]
  when RBS::Types::Alias
    case return_type.name.name
    when :int
      INTEGER
    when :boolish
      BOOLEAN
    when :string
      STRING
    else
      # TODO: ???
      OBJECT
    end
  when RBS::Types::Interface
    # unimplemented
    OBJECT
  when RBS::Types::ClassInstance
    klass = return_type.name.to_namespace.path.reduce(Object) { _1.const_get _2 }
    if return_type.args
      args = return_type.args.map { from_rbs_type _1, self_type, extra_vars }
      names = rbs_builder.build_singleton(return_type.name).type_params
      params = names.map.with_index { [_1, args[_2] || OBJECT] }.to_h
    end
    InstanceType.new klass, params || {}
  else
    OBJECT
  end
end

.intersect?(a, b) ⇒ Boolean

Returns:

  • (Boolean)


165
166
167
168
169
170
171
172
173
174
175
# File 'lib/repl_type_completor/types.rb', line 165

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) }
  (aa.flat_map(&:ancestors) & bb).any?
end

.load_rbs_builderObject



26
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
# File 'lib/repl_type_completor/types.rb', line 26

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



468
469
470
471
472
473
474
# File 'lib/repl_type_completor/types.rb', line 468

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)


464
465
466
# File 'lib/repl_type_completor/types.rb', line 464

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

.method_return_type(type, method_name) ⇒ Object



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/repl_type_completor/types.rb', line 89

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]
    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



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

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



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

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

.rbs_load_started?Boolean

Returns:

  • (Boolean)


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

def self.rbs_load_started?
  !!@load_started
end

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



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/repl_type_completor/types.rb', line 108

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]
    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, K: SYMBOL, V: 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



75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/repl_type_completor/types.rb', line 75

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

  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



177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/repl_type_completor/types.rb', line 177

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



190
191
192
193
194
# File 'lib/repl_type_completor/types.rb', line 190

def self.union_type_from_objects(objects)
  instanes = objects.size <= OBJECT_TO_TYPE_SAMPLE_SIZE ? objects : objects.sample(OBJECT_TO_TYPE_SAMPLE_SIZE)
  class_instanes = instanes.group_by { Methods::OBJECT_CLASS_METHOD.bind_call(_1) }
  UnionType[*class_instanes.map { InstanceType.new _1, nil, _2 }]
end