Class: Solargraph::ComplexType

Inherits:
Object
  • Object
show all
Includes:
Equality
Defined in:
lib/solargraph/complex_type.rb,
lib/solargraph/complex_type/conformance.rb,
lib/solargraph/complex_type/unique_type.rb,
lib/solargraph/complex_type/type_methods.rb

Overview

A container for type data based on YARD type tags.

Defined Under Namespace

Modules: TypeMethods Classes: Conformance, UniqueType

Constant Summary collapse

GENERIC_TAG_NAME =
'generic'

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Equality

#==, #eql?, #freeze, #hash

Constructor Details

#initialize(types = [UniqueType::UNDEFINED]) ⇒ ComplexType

Returns a new instance of ComplexType.

Parameters:



17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/solargraph/complex_type.rb', line 17

def initialize types = [UniqueType::UNDEFINED]
  # @todo @items here should not need an annotation
  # @type [Array<UniqueType>]
  items = types.flat_map(&:items).uniq(&:to_s)
  if items.any? { |i| i.name == 'false' } && items.any? { |i| i.name == 'true' }
    items.delete_if { |i| %w[false true].include?(i.name) }
    items.unshift(UniqueType::BOOLEAN)
  end
  # @type [Array<UniqueType>]
  items = [UniqueType::UNDEFINED] if items.any?(&:undefined?)
  # @todo shouldn't need this cast - if statement above adds an 'Array' type
  # @type [Array<UniqueType>]
  @items = items
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name, *args, &block) ⇒ Object?

Parameters:

  • name (Symbol)
  • args (Array<Object>)

Returns:

  • (Object, nil)


160
161
162
163
164
# File 'lib/solargraph/complex_type.rb', line 160

def method_missing name, *args, &block
  return if @items.first.nil?
  return @items.first.send(name, *args, &block) if respond_to_missing?(name)
  super
end

Instance Attribute Details

#itemsObject (readonly)

Returns the value of attribute items.



361
362
363
# File 'lib/solargraph/complex_type.rb', line 361

def items
  @items
end

Class Method Details

.parse(*strings, partial: false) ⇒ ComplexType

Parse type strings into a ComplexType.

# @overload parse(*strings, partial: false) # @todo Need ability to use a literal true as a type below # @param partial [Boolean] True if the string is part of a another type # @return [Array<UniqueType>] @sg-ignore To be able to select the right signature above,

Chain::Call needs to know the decl type (:arg, :optarg,
:kwarg, etc) of the arguments given, instead of just having
an array of Chains as the arguments.

Examples:

ComplexType.parse 'String', 'Foo', 'nil' #=> [String, Foo, nil]

Parameters:

  • partial (Boolean) (defaults to: false)

    if true, method is receiving a string that will be used inside another ComplexType. It returns arrays of ComplexTypes instead of a single cohesive one. Consumers should not need to use this parameter; it should only be used internally.

  • strings (Array<String>)

    The type definitions to parse

Returns:



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
463
464
465
466
467
468
469
470
471
472
473
474
475
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
515
516
517
518
519
520
521
522
523
# File 'lib/solargraph/complex_type.rb', line 437

def parse *strings, partial: false
  # @type [Hash{Array<String> => ComplexType, Array<ComplexType::UniqueType>}]
  @cache ||= {}
  unless partial
    cached = @cache[strings]
    return cached unless cached.nil?
  end
  # @types [Array<ComplexType::UniqueType>]
  types = []
  key_types = nil
  strings.each do |type_string|
    point_stack = 0
    curly_stack = 0
    paren_stack = 0
    base = String.new
    subtype_string = String.new
    # @param char [String]
    type_string&.each_char do |char|
      if char == '='
        # raise ComplexTypeError, "Invalid = in type #{type_string}" unless curly_stack > 0
      elsif char == '<'
        point_stack += 1
      elsif char == '>'
        if subtype_string.end_with?('=') && curly_stack.positive?
          subtype_string += char
        elsif base.end_with?('=')
          raise ComplexTypeError, 'Invalid hash thing' unless key_types.nil?
          # types.push ComplexType.new([UniqueType.new(base[0..-2].strip)])
          # @sg-ignore Need to add nil check here
          types.push UniqueType.parse(base[0..-2].strip, subtype_string)
          # @todo this should either expand key_type's type
          #   automatically or complain about not being
          #   compatible with key_type's type in type checking
          key_types = types
          types = []
          base.clear
          subtype_string.clear
          next
        else
          raise ComplexTypeError, "Invalid close in type #{type_string}" if point_stack.zero?
          point_stack -= 1
          subtype_string += char
        end
        next
      elsif char == '{'
        curly_stack += 1
      elsif char == '}'
        curly_stack -= 1
        subtype_string += char
        raise ComplexTypeError, "Invalid close in type #{type_string}" if curly_stack.negative?
        next
      elsif char == '('
        paren_stack += 1
      elsif char == ')'
        paren_stack -= 1
        subtype_string += char
        raise ComplexTypeError, "Invalid close in type #{type_string}" if paren_stack.negative?
        next
      elsif char == ',' && point_stack.zero? && curly_stack.zero? && paren_stack.zero?
        # types.push ComplexType.new([UniqueType.new(base.strip, subtype_string.strip)])
        types.push UniqueType.parse(base.strip, subtype_string.strip)
        base.clear
        subtype_string.clear
        next
      end
      if point_stack.zero? && curly_stack.zero? && paren_stack.zero?
        base.concat char
      else
        subtype_string.concat char
      end
    end
    if point_stack != 0 || curly_stack != 0 || paren_stack != 0
      raise ComplexTypeError,
            "Unclosed subtype in #{type_string}"
    end
    # types.push ComplexType.new([UniqueType.new(base, subtype_string)])
    types.push UniqueType.parse(base.strip, subtype_string.strip)
  end
  unless key_types.nil?
    raise ComplexTypeError, 'Invalid use of key/value parameters' unless partial
    return key_types if types.empty?
    return [key_types, types]
  end
  result = partial ? types : ComplexType.new(types)
  @cache[strings] = result unless partial
  result
end

.try_parse(*strings) ⇒ ComplexType

Parameters:

  • strings (Array<String>)

Returns:



527
528
529
530
531
532
# File 'lib/solargraph/complex_type.rb', line 527

def try_parse *strings
  parse(*strings)
rescue ComplexTypeError => e
  Solargraph.logger.info "Error parsing complex type `#{strings.join(', ')}`: #{e.message}"
  ComplexType::UNDEFINED
end

Instance Method Details

#[](index) ⇒ UniqueType

Parameters:

  • index (Integer)

Returns:



136
137
138
# File 'lib/solargraph/complex_type.rb', line 136

def [] index
  @items[index]
end

#all? {|| ... } ⇒ Boolean

Yield Parameters:

Returns:

  • (Boolean)


265
266
267
# File 'lib/solargraph/complex_type.rb', line 265

def all? &block
  @items.all?(&block)
end

#all_paramsArray<ComplexType>

Returns:



327
328
329
# File 'lib/solargraph/complex_type.rb', line 327

def all_params
  @items.first.all_params || []
end

#all_rooted?Boolean

every type and subtype in this union have been resolved to be fully qualified

Returns:

  • (Boolean)


344
345
346
# File 'lib/solargraph/complex_type.rb', line 344

def all_rooted?
  all?(&:all_rooted?)
end

#any? {|| ... } ⇒ Boolean

Yield Parameters:

Yield Returns:

  • (Boolean)

Returns:

  • (Boolean)


272
273
274
# File 'lib/solargraph/complex_type.rb', line 272

def any? &block
  @items.compact.any?(&block)
end

#conforms_to?(api_map, expected, situation, rules = [], variance: erased_variance(situation)) ⇒ Boolean

Parameters:

  • api_map (ApiMap)
  • expected (ComplexType, ComplexType::UniqueType)
  • situation (:method_call, :return_type, :assignment)
  • rules (Array<:allow_subtype_skew, :allow_empty_params, :allow_reverse_match, :allow_any_match, :allow_undefined, :allow_unresolved_generic, :allow_unmatched_interface>) (defaults to: [])

    allow_subtype_skew: if not provided, check if any subtypes of

    the expected type match the inferred type
    

    allow_reverse_match: check if any subtypes

    of the expected type match the inferred type
    

    allow_empty_params: allow a general inferred type without

    parameters to conform to a more specific expected type
    

    allow_any_match: any unique type matched in the inferred

    qualifies as a match
    

    allow_undefined: treat undefined as a wildcard that matches

    anything
    
  • variance (:invariant, :covariant, :contravariant) (defaults to: erased_variance(situation))

Returns:

  • (Boolean)


222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
# File 'lib/solargraph/complex_type.rb', line 222

def conforms_to? api_map, expected,
                 situation,
                 rules = [],
                 variance: erased_variance(situation)
  expected = expected.downcast_to_literal_if_possible
  inferred = downcast_to_literal_if_possible

  return duck_types_match?(api_map, expected, inferred) if expected.duck_type?

  if rules.include? :allow_any_match
    inferred.any? do |inf|
      inf.conforms_to?(api_map, expected, situation, rules,
                       variance: variance)
    end
  else
    inferred.all? do |inf|
      inf.conforms_to?(api_map, expected, situation, rules,
                       variance: variance)
    end
  end
end

#descString

Returns:

  • (String)


196
197
198
# File 'lib/solargraph/complex_type.rb', line 196

def desc
  rooted_tags
end

#downcast_to_literal_if_possibleComplexType

Returns:



191
192
193
# File 'lib/solargraph/complex_type.rb', line 191

def downcast_to_literal_if_possible
  ComplexType.new(items.map(&:downcast_to_literal_if_possible))
end

#duck_types_match?(api_map, expected, inferred) ⇒ Boolean

Parameters:

Returns:

  • (Boolean)

Raises:

  • (ArgumentError)


248
249
250
251
252
253
254
255
256
257
# File 'lib/solargraph/complex_type.rb', line 248

def duck_types_match? api_map, expected, inferred
  raise ArgumentError, 'Expected type must be duck type' unless expected.duck_type?
  expected.each do |exp|
    next unless exp.duck_type?
    quack = exp.to_s[1..]
    # @sg-ignore Need to add nil check here
    return false if api_map.get_method_stack(inferred.namespace, quack, scope: inferred.scope).empty?
  end
  true
end

#each {|| ... } ⇒ Enumerable<UniqueType>

Yield Parameters:

Returns:



93
94
95
# File 'lib/solargraph/complex_type.rb', line 93

def each &block
  @items.each(&block)
end

#each_unique_typeEnumerator<UniqueType>

This method returns an undefined value.

Returns:

Yield Parameters:



101
102
103
104
105
106
107
# File 'lib/solargraph/complex_type.rb', line 101

def each_unique_type &block
  return enum_for(__method__) unless block_given?

  @items.each do |item|
    item.each_unique_type(&block)
  end
end

#erased_version_of?(other) ⇒ Boolean

Parameters:

Returns:

  • (Boolean)


349
350
351
352
353
# File 'lib/solargraph/complex_type.rb', line 349

def erased_version_of? other
  return false if items.length != 1 || other.items.length != 1

  @items.first.erased_version_of?(other.items.first)
end

#exclude(exclude_types, api_map) ⇒ ComplexType, self

Parameters:

Returns:



366
367
368
369
370
371
372
# File 'lib/solargraph/complex_type.rb', line 366

def exclude exclude_types, api_map
  return self if exclude_types.nil?

  types = items - exclude_types.items
  types = [ComplexType::UniqueType::UNDEFINED] if types.empty?
  ComplexType.new(types)
end

#firstUniqueType

Returns:



60
61
62
# File 'lib/solargraph/complex_type.rb', line 60

def first
  @items.first
end

#force_rootedself

Returns:

  • (self)


301
302
303
304
305
# File 'lib/solargraph/complex_type.rb', line 301

def force_rooted
  transform do |t|
    t.recreate(make_rooted: true)
  end
end

#generic?Boolean

Returns:

  • (Boolean)


280
281
282
# File 'lib/solargraph/complex_type.rb', line 280

def generic?
  any?(&:generic?)
end

#intersect_with(intersection_type, api_map) ⇒ self, ComplexType::UniqueType

Parameters:

Returns:

See Also:



379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
# File 'lib/solargraph/complex_type.rb', line 379

def intersect_with intersection_type, api_map
  return self if intersection_type.nil?
  return intersection_type if undefined?
  types = []
  # try to find common types via conformance
  items.each do |ut|
    intersection_type.each do |int_type|
      if int_type.conforms_to?(api_map, ut, :assignment)
        types << int_type
      elsif ut.conforms_to?(api_map, int_type, :assignment)
        types << ut
      end
    end
  end
  types = [ComplexType::UniqueType::UNDEFINED] if types.empty?
  ComplexType.new(types)
end

#lengthInteger

Returns:

  • (Integer)


125
126
127
# File 'lib/solargraph/complex_type.rb', line 125

def length
  @items.length
end

#literal?Boolean

Returns:

  • (Boolean)


186
187
188
# File 'lib/solargraph/complex_type.rb', line 186

def literal?
  @items.any?(&:literal?)
end

#map {|| ... } ⇒ Array<UniqueType>

@sg-ignore Declared return type

::Array<::Solargraph::ComplexType::UniqueType> does not match
inferred type ::Array<::Proc> for Solargraph::ComplexType#map

Yield Parameters:

Yield Returns:

Returns:



87
88
89
# File 'lib/solargraph/complex_type.rb', line 87

def map &block
  @items.map(&block)
end

#namespaceString

Returns:

  • (String)


146
147
148
149
# File 'lib/solargraph/complex_type.rb', line 146

def namespace
  # cache this attr for high frequency call
  @namespace ||= method_missing(:namespace).to_s
end

#namespacesArray<String>

Returns:

  • (Array<String>)


152
153
154
# File 'lib/solargraph/complex_type.rb', line 152

def namespaces
  @items.map(&:namespace)
end

#nullable?Boolean

Returns:

  • (Boolean)


315
316
317
# File 'lib/solargraph/complex_type.rb', line 315

def nullable?
  @items.any?(&:nil_type?)
end

#qualify(api_map, *gates) ⇒ ComplexType

Parameters:

  • api_map (ApiMap)
  • gates (Array<String>)

Returns:



36
37
38
39
40
41
42
43
44
# File 'lib/solargraph/complex_type.rb', line 36

def qualify api_map, *gates
  red = reduce_object
  types = red.items.map do |t|
    next t if %w[nil void undefined].include?(t.name)
    next t if ['::Boolean'].include?(t.rooted_name)
    api_map.unalias(t.name) || t.qualify(api_map, *gates)
  end
  ComplexType.new(types).reduce_object
end

#recreate(new_name: nil, make_rooted: nil, new_key_types: nil, new_subtypes: nil) ⇒ self

Parameters:

  • new_name (String, nil) (defaults to: nil)
  • make_rooted (Boolean, nil) (defaults to: nil)
  • new_key_types (Array<ComplexType>, nil) (defaults to: nil)
  • make_rooted (Boolean, nil) (defaults to: nil)
  • new_subtypes (Array<ComplexType>, nil) (defaults to: nil)

Returns:

  • (self)


115
116
117
118
119
120
121
122
# File 'lib/solargraph/complex_type.rb', line 115

def recreate new_name: nil, make_rooted: nil, new_key_types: nil, new_subtypes: nil
  ComplexType.new(map do |ut|
                    ut.recreate(new_name: new_name,
                                make_rooted: make_rooted,
                                new_key_types: new_key_types,
                                new_subtypes: new_subtypes)
                  end)
end

#reduce_class_typeComplexType

Returns:



332
333
334
335
336
337
338
339
340
# File 'lib/solargraph/complex_type.rb', line 332

def reduce_class_type
  new_items = items.flat_map do |type|
    next type unless %w[Module Class].include?(type.name)
    next type if type.all_params.empty?

    type.all_params
  end
  ComplexType.new(new_items)
end

#resolve_generics(definitions, context_type) ⇒ ComplexType

Parameters:

Returns:



310
311
312
313
# File 'lib/solargraph/complex_type.rb', line 310

def resolve_generics definitions, context_type
  result = @items.map { |i| i.resolve_generics(definitions, context_type) }
  ComplexType.new(result)
end

#resolve_generics_from_context(generics_to_resolve, context_type, resolved_generic_values: {}) ⇒ self

Parameters:

  • generics_to_resolve (Enumerable<String>)

    ]

  • context_type (ComplexType, ComplexType::UniqueType, nil)
  • resolved_generic_values (Hash{String => ComplexType}) (defaults to: {})

    Added to as types are encountered or resolved

Returns:

  • (self)


50
51
52
53
54
55
56
57
# File 'lib/solargraph/complex_type.rb', line 50

def resolve_generics_from_context generics_to_resolve, context_type, resolved_generic_values: {}
  return self unless generic?

  ComplexType.new(@items.map do |i|
    i.resolve_generics_from_context(generics_to_resolve, context_type,
                                    resolved_generic_values: resolved_generic_values)
  end)
end

#respond_to_missing?(name, include_private = false) ⇒ Boolean

Parameters:

  • name (Symbol)
  • include_private (Boolean) (defaults to: false)

Returns:

  • (Boolean)


168
169
170
# File 'lib/solargraph/complex_type.rb', line 168

def respond_to_missing? name, include_private = false
  TypeMethods.public_instance_methods.include?(name) || super
end

#rooted?Boolean

every top-level type has resolved to be fully qualified; see #all_rooted? to check their subtypes as well

Returns:

  • (Boolean)


357
358
359
# File 'lib/solargraph/complex_type.rb', line 357

def rooted?
  all?(&:rooted?)
end

#rooted_tagsString

Returns:

  • (String)


260
261
262
# File 'lib/solargraph/complex_type.rb', line 260

def rooted_tags
  map(&:rooted_tag).join(', ')
end

#select(&block) ⇒ Array<UniqueType>

Returns:



141
142
143
# File 'lib/solargraph/complex_type.rb', line 141

def select &block
  @items.select(&block)
end

#self_to_type(dst) ⇒ ComplexType

Parameters:

Returns:



73
74
75
76
77
78
79
# File 'lib/solargraph/complex_type.rb', line 73

def self_to_type dst
  object_type_dst = dst.reduce_class_type
  transform do |t|
    next t if t.name != 'self'
    object_type_dst
  end
end

#selfy?Boolean

Returns:

  • (Boolean)


276
277
278
# File 'lib/solargraph/complex_type.rb', line 276

def selfy?
  @items.any?(&:selfy?)
end

#simple_tagsString

Returns:

  • (String)


182
183
184
# File 'lib/solargraph/complex_type.rb', line 182

def simple_tags
  simplify_literals.tags
end

#simplify_literalsself

Returns:

  • (self)


285
286
287
# File 'lib/solargraph/complex_type.rb', line 285

def simplify_literals
  ComplexType.new(map(&:simplify_literals))
end

#tagsString

Returns:

  • (String)


177
178
179
# File 'lib/solargraph/complex_type.rb', line 177

def tags
  map(&:tag).join(', ')
end

#to_aArray<UniqueType>

Returns:



130
131
132
# File 'lib/solargraph/complex_type.rb', line 130

def to_a
  @items
end

#to_rbsString

Returns:

  • (String)


65
66
67
68
69
# File 'lib/solargraph/complex_type.rb', line 65

def to_rbs
  ((@items.length > 1 ? '(' : '') +
   @items.map(&:to_rbs).join(' | ') +
   (@items.length > 1 ? ')' : ''))
end

#to_sObject



172
173
174
# File 'lib/solargraph/complex_type.rb', line 172

def to_s
  map(&:tag).join(', ')
end

#transform(new_name = nil) {|t| ... } ⇒ ComplexType

Parameters:

  • new_name (String, nil) (defaults to: nil)

Yield Parameters:

Yield Returns:

Returns:



293
294
295
296
297
298
# File 'lib/solargraph/complex_type.rb', line 293

def transform new_name = nil, &transform_type
  if new_name&.start_with?('::')
    raise "Please remove leading :: and set rooted with recreate() instead - #{new_name}"
  end
  ComplexType.new(map { |ut| ut.transform(new_name, &transform_type) })
end

#without_nilComplexType

Returns:



320
321
322
323
324
# File 'lib/solargraph/complex_type.rb', line 320

def without_nil
  new_items = @items.reject(&:nil_type?)
  return ComplexType::UNDEFINED if new_items.empty?
  ComplexType.new(new_items)
end