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.



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

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:



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
524
# File 'lib/solargraph/complex_type.rb', line 438

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:



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

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)


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

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

#all_paramsArray<ComplexType>

Returns:



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

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)


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

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

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

Yield Parameters:

Yield Returns:

  • (Boolean)

Returns:

  • (Boolean)


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

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)


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

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)


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

def desc
  rooted_tags
end

#downcast_to_literal_if_possibleComplexType

Returns:



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

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

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

Parameters:

Returns:

  • (Boolean)

Raises:

  • (ArgumentError)


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

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)


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

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:



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

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)


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

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

#generic?Boolean

Returns:

  • (Boolean)


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

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

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

Parameters:

Returns:

See Also:



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

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)


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

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:



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

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:



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

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_method_defined?(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)


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

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

#rooted_tagsString

Returns:

  • (String)


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

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)


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

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)


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

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:



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

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:



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

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