solargraph 0.54.1 → 0.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +39 -0
  3. data/lib/solargraph/api_map/cache.rb +10 -1
  4. data/lib/solargraph/api_map/index.rb +167 -0
  5. data/lib/solargraph/api_map/store.rb +71 -122
  6. data/lib/solargraph/api_map.rb +97 -36
  7. data/lib/solargraph/bench.rb +17 -1
  8. data/lib/solargraph/complex_type/type_methods.rb +7 -0
  9. data/lib/solargraph/complex_type/unique_type.rb +109 -8
  10. data/lib/solargraph/complex_type.rb +48 -18
  11. data/lib/solargraph/convention/struct_definition/struct_assignment_node.rb +51 -0
  12. data/lib/solargraph/convention/struct_definition/struct_definition_node.rb +100 -0
  13. data/lib/solargraph/convention/struct_definition.rb +101 -0
  14. data/lib/solargraph/convention.rb +2 -0
  15. data/lib/solargraph/doc_map.rb +43 -18
  16. data/lib/solargraph/equality.rb +33 -0
  17. data/lib/solargraph/language_server/host/message_worker.rb +31 -11
  18. data/lib/solargraph/language_server/host.rb +13 -11
  19. data/lib/solargraph/language_server/message/base.rb +19 -12
  20. data/lib/solargraph/language_server/message/initialize.rb +3 -1
  21. data/lib/solargraph/language_server/message/text_document/completion.rb +0 -3
  22. data/lib/solargraph/language_server/message/text_document/formatting.rb +4 -0
  23. data/lib/solargraph/library.rb +7 -8
  24. data/lib/solargraph/location.rb +15 -0
  25. data/lib/solargraph/parser/comment_ripper.rb +11 -6
  26. data/lib/solargraph/parser/flow_sensitive_typing.rb +226 -0
  27. data/lib/solargraph/parser/node_methods.rb +15 -1
  28. data/lib/solargraph/parser/parser_gem/class_methods.rb +11 -6
  29. data/lib/solargraph/parser/parser_gem/node_chainer.rb +17 -23
  30. data/lib/solargraph/parser/parser_gem/node_methods.rb +5 -3
  31. data/lib/solargraph/parser/parser_gem/node_processors/and_node.rb +21 -0
  32. data/lib/solargraph/parser/parser_gem/node_processors/casgn_node.rb +21 -1
  33. data/lib/solargraph/parser/parser_gem/node_processors/if_node.rb +21 -0
  34. data/lib/solargraph/parser/parser_gem/node_processors/lvasgn_node.rb +2 -2
  35. data/lib/solargraph/parser/parser_gem/node_processors/namespace_node.rb +26 -5
  36. data/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb +41 -0
  37. data/lib/solargraph/parser/parser_gem/node_processors/until_node.rb +28 -0
  38. data/lib/solargraph/parser/parser_gem/node_processors/while_node.rb +28 -0
  39. data/lib/solargraph/parser/parser_gem/node_processors.rb +10 -0
  40. data/lib/solargraph/parser.rb +1 -0
  41. data/lib/solargraph/pin/base.rb +35 -19
  42. data/lib/solargraph/pin/base_variable.rb +10 -3
  43. data/lib/solargraph/pin/block.rb +3 -3
  44. data/lib/solargraph/pin/breakable.rb +9 -0
  45. data/lib/solargraph/pin/callable.rb +3 -3
  46. data/lib/solargraph/pin/local_variable.rb +7 -1
  47. data/lib/solargraph/pin/method.rb +26 -18
  48. data/lib/solargraph/pin/namespace.rb +10 -7
  49. data/lib/solargraph/pin/parameter.rb +15 -6
  50. data/lib/solargraph/pin/proxy_type.rb +12 -6
  51. data/lib/solargraph/pin/until.rb +18 -0
  52. data/lib/solargraph/pin/while.rb +18 -0
  53. data/lib/solargraph/pin.rb +3 -0
  54. data/lib/solargraph/position.rb +7 -0
  55. data/lib/solargraph/range.rb +7 -0
  56. data/lib/solargraph/rbs_map/conversions.rb +16 -10
  57. data/lib/solargraph/rbs_map/core_fills.rb +10 -3
  58. data/lib/solargraph/rbs_map.rb +1 -0
  59. data/lib/solargraph/shell.rb +2 -0
  60. data/lib/solargraph/source/chain/array.rb +5 -4
  61. data/lib/solargraph/source/chain/call.rb +55 -19
  62. data/lib/solargraph/source/chain/constant.rb +1 -1
  63. data/lib/solargraph/source/chain/hash.rb +8 -2
  64. data/lib/solargraph/source/chain/if.rb +5 -0
  65. data/lib/solargraph/source/chain/link.rb +19 -5
  66. data/lib/solargraph/source/chain/literal.rb +27 -2
  67. data/lib/solargraph/source/chain/z_super.rb +1 -1
  68. data/lib/solargraph/source/chain.rb +107 -63
  69. data/lib/solargraph/source/cursor.rb +1 -11
  70. data/lib/solargraph/source/source_chainer.rb +2 -2
  71. data/lib/solargraph/source.rb +2 -1
  72. data/lib/solargraph/source_map/clip.rb +4 -2
  73. data/lib/solargraph/type_checker/checks.rb +4 -0
  74. data/lib/solargraph/type_checker.rb +41 -14
  75. data/lib/solargraph/version.rb +1 -1
  76. data/lib/solargraph/workspace/config.rb +7 -3
  77. data/lib/solargraph/workspace.rb +1 -1
  78. data/lib/solargraph/yard_map/mapper/to_constant.rb +1 -0
  79. data/lib/solargraph/yard_map/mapper/to_method.rb +42 -15
  80. data/lib/solargraph/yard_map/mapper/to_namespace.rb +1 -0
  81. data/lib/solargraph/yard_map/mapper.rb +1 -0
  82. data/lib/solargraph/yardoc.rb +1 -1
  83. data/lib/solargraph.rb +1 -0
  84. data/solargraph.gemspec +5 -5
  85. metadata +39 -25
@@ -12,6 +12,7 @@ module Solargraph
12
12
  autoload :Cache, 'solargraph/api_map/cache'
13
13
  autoload :SourceToYard, 'solargraph/api_map/source_to_yard'
14
14
  autoload :Store, 'solargraph/api_map/store'
15
+ autoload :Index, 'solargraph/api_map/index'
15
16
 
16
17
  # @return [Array<String>]
17
18
  attr_reader :unresolved_requires
@@ -48,6 +49,15 @@ module Solargraph
48
49
  equality_fields.hash
49
50
  end
50
51
 
52
+ def to_s
53
+ self.class.to_s
54
+ end
55
+
56
+ # avoid enormous dump
57
+ def inspect
58
+ to_s
59
+ end
60
+
51
61
  # @param pins [Array<Pin::Base>]
52
62
  # @return [self]
53
63
  def index pins
@@ -56,7 +66,7 @@ module Solargraph
56
66
  @source_map_hash = {}
57
67
  implicit.clear
58
68
  cache.clear
59
- @store = Store.new(@@core_map.pins + pins)
69
+ store.update @@core_map.pins, pins
60
70
  self
61
71
  end
62
72
 
@@ -75,10 +85,9 @@ module Solargraph
75
85
  # @param bench [Bench]
76
86
  # @return [self]
77
87
  def catalog bench
78
- old_api_hash = @source_map_hash&.values&.map(&:api_hash)
79
- need_to_uncache = (old_api_hash != bench.source_maps.map(&:api_hash))
80
- @source_map_hash = bench.source_maps.map { |s| [s.filename, s] }.to_h
81
- pins = bench.source_maps.flat_map(&:pins).flatten
88
+ @source_map_hash = bench.source_map_hash
89
+ iced_pins = bench.icebox.flat_map(&:pins)
90
+ live_pins = bench.live_map&.pins || []
82
91
  implicit.clear
83
92
  source_map_hash.each_value do |map|
84
93
  implicit.merge map.environ
@@ -87,17 +96,17 @@ module Solargraph
87
96
  if @unresolved_requires != unresolved_requires || @doc_map&.uncached_gemspecs&.any?
88
97
  @doc_map = DocMap.new(unresolved_requires, [], bench.workspace.rbs_collection_path) # @todo Implement gem preferences
89
98
  @unresolved_requires = unresolved_requires
90
- need_to_uncache = true
91
99
  end
92
- @store = Store.new(@@core_map.pins + @doc_map.pins + implicit.pins + pins)
93
- @cache.clear if need_to_uncache
94
-
100
+ @cache.clear if store.update(@@core_map.pins, @doc_map.pins, implicit.pins, iced_pins, live_pins)
95
101
  @missing_docs = [] # @todo Implement missing docs
96
102
  self
97
103
  end
98
104
 
105
+ # @todo need to model type def statement in chains as a symbol so
106
+ # that this overload of 'protected' will typecheck @sg-ignore
107
+ # @sg-ignore
99
108
  protected def equality_fields
100
- [self.class, @source_map_hash, implicit, @doc_map, @unresolved_requires, @missing_docs]
109
+ [self.class, @source_map_hash, implicit, @doc_map, @unresolved_requires]
101
110
  end
102
111
 
103
112
  # @return [::Array<Gem::Specification>]
@@ -183,7 +192,7 @@ module Solargraph
183
192
 
184
193
  # @return [Array<Solargraph::Pin::Base>]
185
194
  def pins
186
- store.pins
195
+ store.pins.clone.freeze
187
196
  end
188
197
 
189
198
  # An array of pins based on Ruby keywords (`if`, `end`, etc.).
@@ -255,15 +264,19 @@ module Solargraph
255
264
  # Should not be prefixed with '::'.
256
265
  # @return [String, nil] fully qualified tag
257
266
  def qualify tag, context_tag = ''
258
- return tag if ['self', nil].include?(tag)
267
+ return tag if ['Boolean', 'self', nil].include?(tag)
259
268
 
260
- context_type = ComplexType.parse(context_tag).force_rooted
269
+ context_type = ComplexType.try_parse(context_tag).force_rooted
261
270
  return unless context_type
262
271
 
263
272
  type = ComplexType.try_parse(tag)
264
273
  return unless type
274
+ return tag if type.literal?
275
+
276
+ context_type = ComplexType.try_parse(context_tag)
277
+ return unless context_type
265
278
 
266
- fqns = qualify_namespace(type.rooted_namespace, context_type.namespace)
279
+ fqns = qualify_namespace(type.rooted_namespace, context_type.rooted_namespace)
267
280
  return unless fqns
268
281
 
269
282
  fqns + type.substring
@@ -310,6 +323,11 @@ module Solargraph
310
323
  result
311
324
  end
312
325
 
326
+ # @see Solargraph::Parser::FlowSensitiveTyping#visible_pins
327
+ def visible_pins(*args, **kwargs, &blk)
328
+ Solargraph::Parser::FlowSensitiveTyping.visible_pins(*args, **kwargs, &blk)
329
+ end
330
+
313
331
  # Get an array of class variable pins for a namespace.
314
332
  #
315
333
  # @param namespace [String] A fully qualified namespace
@@ -341,8 +359,12 @@ module Solargraph
341
359
  # @param deep [Boolean] True to include superclasses, mixins, etc.
342
360
  # @return [Array<Solargraph::Pin::Method>]
343
361
  def get_methods rooted_tag, scope: :instance, visibility: [:public], deep: true
362
+ rooted_type = ComplexType.try_parse(rooted_tag)
363
+ fqns = rooted_type.namespace
364
+ namespace_pin = store.get_path_pins(fqns).select { |p| p.is_a?(Pin::Namespace) }.first
344
365
  cached = cache.get_methods(rooted_tag, scope, visibility, deep)
345
366
  return cached.clone unless cached.nil?
367
+ # @type [Array<Solargraph::Pin::Method>]
346
368
  result = []
347
369
  skip = Set.new
348
370
  if rooted_tag == ''
@@ -363,7 +385,7 @@ module Solargraph
363
385
  init_pin = get_method_stack(rooted_tag, 'initialize').first
364
386
  next pin unless init_pin
365
387
 
366
- type = ComplexType.try_parse(ComplexType.try_parse(rooted_tag).namespace)
388
+ type = ComplexType::SELF
367
389
  Pin::Method.new(
368
390
  name: 'new',
369
391
  scope: :class,
@@ -372,17 +394,21 @@ module Solargraph
372
394
  signatures: init_pin.signatures.map { |sig| sig.proxy(type) },
373
395
  return_type: type,
374
396
  comments: init_pin.comments,
375
- closure: init_pin.closure
376
- # @todo Hack to force TypeChecker#internal_or_core?
377
- ).tap { |pin| pin.source = :rbs }
397
+ closure: init_pin.closure,
398
+ source: init_pin.source,
399
+ type_location: init_pin.type_location,
400
+ )
378
401
  end
379
402
  end
380
403
  result.concat inner_get_methods('Kernel', :instance, [:public], deep, skip) if visibility.include?(:private)
381
404
  result.concat inner_get_methods('Module', scope, visibility, deep, skip) if scope == :module
382
405
  end
383
- resolved = resolve_method_aliases(result, visibility)
384
- cache.set_methods(rooted_tag, scope, visibility, deep, resolved)
385
- resolved
406
+ result = resolve_method_aliases(result, visibility)
407
+ if namespace_pin && rooted_tag != rooted_type.name
408
+ result = result.map { |method_pin| method_pin.resolve_generics(namespace_pin, rooted_type) }
409
+ end
410
+ cache.set_methods(rooted_tag, scope, visibility, deep, result)
411
+ result
386
412
  end
387
413
 
388
414
  # Get an array of method pins for a complex type.
@@ -438,8 +464,13 @@ module Solargraph
438
464
  # @param name [String] Method name to look up
439
465
  # @param scope [Symbol] :instance or :class
440
466
  # @return [Array<Solargraph::Pin::Method>]
441
- def get_method_stack rooted_tag, name, scope: :instance
442
- get_methods(rooted_tag, scope: scope, visibility: [:private, :protected, :public]).select { |p| p.name == name }
467
+ def get_method_stack rooted_tag, name, scope: :instance, visibility: [:private, :protected, :public], preserve_generics: false
468
+ rooted_type = ComplexType.parse(rooted_tag)
469
+ fqns = rooted_type.namespace
470
+ namespace_pin = store.get_path_pins(fqns).select { |p| p.is_a?(Pin::Namespace) }.first
471
+ methods = get_methods(rooted_tag, scope: scope, visibility: visibility).select { |p| p.name == name }
472
+ methods = erase_generics(namespace_pin, rooted_type, methods) unless preserve_generics
473
+ methods
443
474
  end
444
475
 
445
476
  # Get an array of all suggestions that match the specified path.
@@ -601,6 +632,7 @@ module Solargraph
601
632
  rooted_type = ComplexType.parse(rooted_tag).force_rooted
602
633
  fqns = rooted_type.namespace
603
634
  fqns_generic_params = rooted_type.all_params
635
+ namespace_pin = store.get_path_pins(fqns).select { |p| p.is_a?(Pin::Namespace) }.first
604
636
  return [] if no_core && fqns =~ /^(Object|BasicObject|Class|Module)$/
605
637
  reqstr = "#{fqns}|#{scope}|#{visibility.sort}|#{deep}"
606
638
  return [] if skip.include?(reqstr)
@@ -615,15 +647,7 @@ module Solargraph
615
647
  # Store#get_methods doesn't know about full tags, just
616
648
  # namespaces; resolving the generics in the method pins is this
617
649
  # class' responsibility
618
- raw_methods = store.get_methods(fqns, scope: scope, visibility: visibility).sort{ |a, b| a.name <=> b.name }
619
- namespace_pin = store.get_path_pins(fqns).select { |p| p.is_a?(Pin::Namespace) }.first
620
- methods = if namespace_pin && rooted_tag != fqns
621
- methods = raw_methods.map do |method_pin|
622
- method_pin.resolve_generics(namespace_pin, rooted_type)
623
- end
624
- else
625
- raw_methods
626
- end
650
+ methods = store.get_methods(fqns, scope: scope, visibility: visibility).sort{ |a, b| a.name <=> b.name }
627
651
  result.concat methods
628
652
  if deep
629
653
  if scope == :instance
@@ -768,8 +792,8 @@ module Solargraph
768
792
 
769
793
  # Sort an array of pins to put nil or undefined variables last.
770
794
  #
771
- # @param pins [Enumerable<Solargraph::Pin::Base>]
772
- # @return [Enumerable<Solargraph::Pin::Base>]
795
+ # @param pins [Enumerable<Pin::BaseVariable>]
796
+ # @return [Enumerable<Pin::BaseVariable>]
773
797
  def prefer_non_nil_variables pins
774
798
  result = []
775
799
  nil_pins = []
@@ -800,7 +824,7 @@ module Solargraph
800
824
  return pin unless pin.is_a?(Pin::MethodAlias)
801
825
  return nil if @method_alias_stack.include?(pin.path)
802
826
  @method_alias_stack.push pin.path
803
- origin = get_method_stack(pin.full_context.tag, pin.original, scope: pin.scope).first
827
+ origin = get_method_stack(pin.full_context.tag, pin.original, scope: pin.scope, preserve_generics: true).first
804
828
  @method_alias_stack.pop
805
829
  return nil if origin.nil?
806
830
  args = {
@@ -809,11 +833,48 @@ module Solargraph
809
833
  name: pin.name,
810
834
  comments: origin.comments,
811
835
  scope: origin.scope,
836
+ # context: pin.context,
812
837
  visibility: origin.visibility,
813
838
  signatures: origin.signatures,
814
- attribute: origin.attribute?
839
+ attribute: origin.attribute?,
840
+ generics: origin.generics,
841
+ return_type: origin.return_type,
815
842
  }
816
843
  Pin::Method.new **args
817
844
  end
845
+
846
+ include Logging
847
+
848
+ private
849
+
850
+ # @param namespace_pin [Pin::Namespace]
851
+ # @param rooted_type [ComplexType]
852
+ # @param pins [Enumerable<Pin::Base>]
853
+ # @return [Array<Pin::Base>]
854
+ def erase_generics(namespace_pin, rooted_type, pins)
855
+ return pins unless should_erase_generics_when_done?(namespace_pin, rooted_type)
856
+
857
+ logger.debug("Erasing generics on namespace_pin=#{namespace_pin} / rooted_type=#{rooted_type}")
858
+ pins.map do |method_pin|
859
+ method_pin.erase_generics(namespace_pin.generics)
860
+ end
861
+ end
862
+
863
+ # @param namespace_pin [Pin::Namespace]
864
+ # @param rooted_type [ComplexType]
865
+ def should_erase_generics_when_done?(namespace_pin, rooted_type)
866
+ has_generics?(namespace_pin) && !can_resolve_generics?(namespace_pin, rooted_type)
867
+ end
868
+
869
+ # @param namespace_pin [Pin::Namespace]
870
+ def has_generics?(namespace_pin)
871
+ namespace_pin && !namespace_pin.generics.empty?
872
+ end
873
+
874
+ # @param namespace_pin [Pin::Namespace]
875
+ # @param rooted_type [ComplexType]
876
+ def can_resolve_generics?(namespace_pin, rooted_type)
877
+ has_generics?(namespace_pin) && !rooted_type.all_params.empty?
878
+ end
818
879
  end
819
880
  end
@@ -11,18 +11,34 @@ module Solargraph
11
11
  # @return [Workspace]
12
12
  attr_reader :workspace
13
13
 
14
+ # @return [SourceMap]
15
+ attr_reader :live_map
16
+
14
17
  # @return [Set<String>]
15
18
  attr_reader :external_requires
16
19
 
17
20
  # @param source_maps [Array<SourceMap>, Set<SourceMap>]
18
21
  # @param workspace [Workspace]
22
+ # @param live_map [SourceMap, nil]
19
23
  # @param external_requires [Array<String>, Set<String>]
20
- def initialize source_maps: [], workspace: Workspace.new, external_requires: []
24
+ def initialize source_maps: [], workspace: Workspace.new, live_map: nil, external_requires: []
21
25
  @source_maps = source_maps.to_set
22
26
  @workspace = workspace
27
+ @live_map = live_map
23
28
  @external_requires = external_requires.reject { |path| workspace.would_require?(path) }
24
29
  .compact
25
30
  .to_set
26
31
  end
32
+
33
+ # @return [Hash{String => SourceMap}]
34
+ def source_map_hash
35
+ # @todo Work around #to_h bug in current Ruby head (3.5) with #map#to_h
36
+ @source_map_hash ||= source_maps.map { |s| [s.filename, s] }
37
+ .to_h
38
+ end
39
+
40
+ def icebox
41
+ @icebox ||= (source_maps - [live_map])
42
+ end
27
43
  end
28
44
  end
@@ -129,6 +129,13 @@ module Solargraph
129
129
  end.call
130
130
  end
131
131
 
132
+ def namespace_type
133
+ return ComplexType.parse('::Object') if duck_type?
134
+ return ComplexType.parse('::NilClass') if nil_type?
135
+ return subtypes.first if (name == 'Class' || name == 'Module') && !subtypes.empty?
136
+ self
137
+ end
138
+
132
139
  # @return [String]
133
140
  def rooted_namespace
134
141
  return namespace unless rooted? && can_root_name?(namespace)
@@ -7,9 +7,15 @@ module Solargraph
7
7
  #
8
8
  class UniqueType
9
9
  include TypeMethods
10
+ include Equality
10
11
 
11
12
  attr_reader :all_params, :subtypes, :key_types
12
13
 
14
+ # @sg-ignore Fix "Not enough arguments to Module#protected"
15
+ protected def equality_fields
16
+ [@name, @all_params, @subtypes, @key_types]
17
+ end
18
+
13
19
  # Create a UniqueType with the specified name and an optional substring.
14
20
  # The substring is the parameter section of a parametrized type, e.g.,
15
21
  # for the type `Array<String>`, the name is `Array` and the substring is
@@ -21,7 +27,7 @@ module Solargraph
21
27
  # @return [UniqueType]
22
28
  def self.parse name, substring = '', make_rooted: nil
23
29
  if name.start_with?(':::')
24
- raise "Illegal prefix: #{name}"
30
+ raise ComplexTypeError, "Illegal prefix: #{name}"
25
31
  end
26
32
  if name.start_with?('::')
27
33
  name = name[2..-1]
@@ -42,7 +48,7 @@ module Solargraph
42
48
  subs = ComplexType.parse(substring[1..-2], partial: true)
43
49
  parameters_type = PARAMETERS_TYPE_BY_STARTING_TAG.fetch(substring[0])
44
50
  if parameters_type == :hash
45
- raise ComplexTypeError, "Bad hash type" unless !subs.is_a?(ComplexType) and subs.length == 2 and !subs[0].is_a?(UniqueType) and !subs[1].is_a?(UniqueType)
51
+ raise ComplexTypeError, "Bad hash type: name=#{name}, substring=#{substring}" unless !subs.is_a?(ComplexType) and subs.length == 2 and !subs[0].is_a?(UniqueType) and !subs[1].is_a?(UniqueType)
46
52
  # @todo should be able to resolve map; both types have it
47
53
  # with same return type
48
54
  # @sg-ignore
@@ -67,19 +73,63 @@ module Solargraph
67
73
  end
68
74
  raise "Please remove leading :: and set rooted instead - #{name.inspect}" if name.start_with?('::')
69
75
  @name = name
70
- @key_types = key_types
71
- @subtypes = subtypes
76
+ @parameters_type = parameters_type
77
+ if implicit_union?
78
+ @key_types = key_types.uniq
79
+ @subtypes = subtypes.uniq
80
+ else
81
+ @key_types = key_types
82
+ @subtypes = subtypes
83
+ end
72
84
  @rooted = rooted
73
85
  @all_params = []
74
- @all_params.concat key_types
75
- @all_params.concat subtypes
76
- @parameters_type = parameters_type
86
+ @all_params.concat @key_types
87
+ @all_params.concat @subtypes
88
+ end
89
+
90
+ def implicit_union?
91
+ # @todo use api_map to establish number of generics in type;
92
+ # if only one is allowed but multiple are passed in, treat
93
+ # those as implicit unions
94
+ ['Hash', 'Array', 'Set', '_ToAry', 'Enumerable', '_Each'].include?(name) && parameters_type != :fixed
77
95
  end
78
96
 
79
97
  def to_s
80
98
  tag
81
99
  end
82
100
 
101
+ def simplify_literals
102
+ transform do |t|
103
+ next t unless t.literal?
104
+ t.recreate(new_name: t.non_literal_name)
105
+ end
106
+ end
107
+
108
+ def literal?
109
+ non_literal_name != name
110
+ end
111
+
112
+ def non_literal_name
113
+ @non_literal_name ||= determine_non_literal_name
114
+ end
115
+
116
+ def determine_non_literal_name
117
+ # https://github.com/ruby/rbs/blob/master/docs/syntax.md
118
+ #
119
+ # _literal_ ::= _string-literal_
120
+ # | _symbol-literal_
121
+ # | _integer-literal_
122
+ # | `true`
123
+ # | `false`
124
+ return name if name.empty?
125
+ return 'NilClass' if name == 'nil'
126
+ return 'Boolean' if ['true', 'false'].include?(name)
127
+ return 'Symbol' if name[0] == ':'
128
+ return 'String' if ['"', "'"].include?(name[0])
129
+ return 'Integer' if name.match?(/^-?\d+$/)
130
+ name
131
+ end
132
+
83
133
  def eql?(other)
84
134
  self.class == other.class &&
85
135
  @name == other.name &&
@@ -107,11 +157,17 @@ module Solargraph
107
157
  def rbs_name
108
158
  if name == 'undefined'
109
159
  'untyped'
160
+ elsif literal?
161
+ name
110
162
  else
111
163
  rooted_name
112
164
  end
113
165
  end
114
166
 
167
+ def desc
168
+ rooted_tags
169
+ end
170
+
115
171
  # @return [String]
116
172
  def to_rbs
117
173
  if duck_type?
@@ -173,6 +229,23 @@ module Solargraph
173
229
  name == GENERIC_TAG_NAME || all_params.any?(&:generic?)
174
230
  end
175
231
 
232
+ # @param api_map [ApiMap] The ApiMap that performs qualification
233
+ # @param atype [ComplexType] type which may be assigned to this type
234
+ def can_assign?(api_map, atype)
235
+ logger.debug { "UniqueType#can_assign?(self=#{rooted_tags.inspect}, atype=#{atype.rooted_tags.inspect})" }
236
+ downcasted_atype = atype.downcast_to_literal_if_possible
237
+ out = downcasted_atype.all? do |autype|
238
+ autype.name == name || api_map.super_and_sub?(name, autype.name)
239
+ end
240
+ logger.debug { "UniqueType#can_assign?(self=#{rooted_tags.inspect}, atype=#{atype.rooted_tags.inspect}) => #{out}" }
241
+ out
242
+ end
243
+
244
+ # @return [UniqueType]
245
+ def downcast_to_literal_if_possible
246
+ SINGLE_SUBTYPE.fetch(rooted_tag, self)
247
+ end
248
+
176
249
  # @param generics_to_resolve [Enumerable<String>]
177
250
  # @param context_type [UniqueType, nil]
178
251
  # @param resolved_generic_values [Hash{String => ComplexType}] Added to as types are encountered or resolved
@@ -235,7 +308,23 @@ module Solargraph
235
308
  if t.name == GENERIC_TAG_NAME
236
309
  idx = definitions.generics.index(t.subtypes.first&.name)
237
310
  next t if idx.nil?
238
- context_type.all_params[idx] || ComplexType::UNDEFINED
311
+ if context_type.parameters_type == :hash
312
+ if idx == 0
313
+ next ComplexType.new(context_type.key_types)
314
+ elsif idx == 1
315
+ next ComplexType.new(context_type.subtypes)
316
+ else
317
+ next ComplexType::UNDEFINED
318
+ end
319
+ elsif context_type.all?(&:implicit_union?)
320
+ if idx == 0 && !context_type.all_params.empty?
321
+ ComplexType.new(context_type.all_params)
322
+ else
323
+ ComplexType::UNDEFINED
324
+ end
325
+ else
326
+ context_type.all_params[idx] || ComplexType::UNDEFINED
327
+ end
239
328
  else
240
329
  t
241
330
  end
@@ -361,6 +450,18 @@ module Solargraph
361
450
 
362
451
  UNDEFINED = UniqueType.new('undefined', rooted: false)
363
452
  BOOLEAN = UniqueType.new('Boolean', rooted: true)
453
+ TRUE = UniqueType.new('true', rooted: true)
454
+ FALSE = UniqueType.new('false', rooted: true)
455
+ NIL = UniqueType.new('nil', rooted: true)
456
+ # @type [Hash{String => UniqueType}]
457
+ SINGLE_SUBTYPE = {
458
+ '::TrueClass' => UniqueType::TRUE,
459
+ '::FalseClass' => UniqueType::FALSE,
460
+ '::NilClass' => UniqueType::NIL
461
+ }.freeze
462
+
463
+
464
+ include Logging
364
465
  end
365
466
  end
366
467
  end
@@ -7,6 +7,7 @@ module Solargraph
7
7
  GENERIC_TAG_NAME = 'generic'.freeze
8
8
  # @!parse
9
9
  # include TypeMethods
10
+ include Equality
10
11
 
11
12
  autoload :TypeMethods, 'solargraph/complex_type/type_methods'
12
13
  autoload :UniqueType, 'solargraph/complex_type/unique_type'
@@ -15,20 +16,18 @@ module Solargraph
15
16
  def initialize types = [UniqueType::UNDEFINED]
16
17
  # @todo @items here should not need an annotation
17
18
  # @type [Array<UniqueType>]
18
- @items = types.flat_map(&:items).uniq(&:to_s)
19
- end
20
-
21
- def eql?(other)
22
- self.class == other.class &&
23
- @items == other.items
24
- end
25
-
26
- def ==(other)
27
- self.eql?(other)
19
+ items = types.flat_map(&:items).uniq(&:to_s)
20
+ if items.any? { |i| i.name == 'false' } && items.any? { |i| i.name == 'true' }
21
+ items.delete_if { |i| i.name == 'false' || i.name == 'true' }
22
+ items.unshift(ComplexType::BOOLEAN)
23
+ end
24
+ items = [UniqueType::UNDEFINED] if items.any?(&:undefined?)
25
+ @items = items
28
26
  end
29
27
 
30
- def hash
31
- [self.class, @items].hash
28
+ # @sg-ignore Fix "Not enough arguments to Module#protected"
29
+ protected def equality_fields
30
+ [self.class, items]
32
31
  end
33
32
 
34
33
  # @param api_map [ApiMap]
@@ -77,7 +76,7 @@ module Solargraph
77
76
  end
78
77
 
79
78
  # @yieldparam [UniqueType]
80
- # @return [Array]
79
+ # @return [Array<UniqueType>]
81
80
  def map &block
82
81
  @items.map &block
83
82
  end
@@ -100,6 +99,12 @@ module Solargraph
100
99
  end
101
100
  end
102
101
 
102
+ # @param atype [ComplexType] type which may be assigned to this type
103
+ # @param api_map [ApiMap] The ApiMap that performs qualification
104
+ def can_assign?(api_map, atype)
105
+ any? { |ut| ut.can_assign?(api_map, atype) }
106
+ end
107
+
103
108
  # @return [Integer]
104
109
  def length
105
110
  @items.length
@@ -110,10 +115,6 @@ module Solargraph
110
115
  @items
111
116
  end
112
117
 
113
- def tags
114
- @items.map(&:tag).join(', ')
115
- end
116
-
117
118
  # @param index [Integer]
118
119
  # @return [UniqueType]
119
120
  def [](index)
@@ -154,14 +155,39 @@ module Solargraph
154
155
  map(&:tag).join(', ')
155
156
  end
156
157
 
158
+ def tags
159
+ map(&:tag).join(', ')
160
+ end
161
+
162
+ def simple_tags
163
+ simplify_literals.tags
164
+ end
165
+
166
+ def literal?
167
+ @items.any?(&:literal?)
168
+ end
169
+
170
+ # @return [ComplexType]
171
+ def downcast_to_literal_if_possible
172
+ ComplexType.new(items.map(&:downcast_to_literal_if_possible))
173
+ end
174
+
175
+ def desc
176
+ rooted_tags
177
+ end
178
+
157
179
  def rooted_tags
158
180
  map(&:rooted_tag).join(', ')
159
181
  end
160
182
 
183
+ # @yieldparam [UniqueType]
161
184
  def all? &block
162
185
  @items.all? &block
163
186
  end
164
187
 
188
+ # @yieldparam [UniqueType]
189
+ # @yieldreturn [Boolean]
190
+ # @return [Boolean]
165
191
  def any? &block
166
192
  @items.compact.any? &block
167
193
  end
@@ -174,6 +200,10 @@ module Solargraph
174
200
  any?(&:generic?)
175
201
  end
176
202
 
203
+ def simplify_literals
204
+ ComplexType.new(map(&:simplify_literals))
205
+ end
206
+
177
207
  # @param new_name [String, nil]
178
208
  # @yieldparam t [UniqueType]
179
209
  # @yieldreturn [UniqueType]
@@ -263,7 +293,7 @@ module Solargraph
263
293
  # Consumers should not need to use this parameter; it should only be
264
294
  # used internally.
265
295
  #
266
- # @param *strings [Array<String>] The type definitions to parse
296
+ # @param strings [Array<String>] The type definitions to parse
267
297
  # @return [ComplexType]
268
298
  # # @overload parse(*strings, partial: false)
269
299
  # # @todo Need ability to use a literal true as a type below
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solargraph
4
+ module Convention
5
+ module StructDefinition
6
+ # A node wrapper for a Struct definition via const assignment.
7
+ # @example
8
+ # MyStruct = Struct.new(:bar, :baz) do
9
+ # def foo
10
+ # end
11
+ # end
12
+ class StructAssignmentNode < StructDefintionNode
13
+ class << self
14
+ # @example
15
+ # s(:casgn, nil, :Foo,
16
+ # s(:block,
17
+ # s(:send,
18
+ # s(:const, nil, :Struct), :new,
19
+ # s(:sym, :bar),
20
+ # s(:sym, :baz)),
21
+ # s(:args),
22
+ # s(:def, :foo,
23
+ # s(:args),
24
+ # s(:send, nil, :bar))))
25
+ def valid?(node)
26
+ return false unless node&.type == :casgn
27
+ return false if node.children[2].nil?
28
+ struct_node = node.children[2].children[0]
29
+
30
+ struct_definition_node?(struct_node)
31
+ end
32
+ end
33
+
34
+ def class_name
35
+ if node.children[0]
36
+ Parser::NodeMethods.unpack_name(node.children[0]) + "::#{node.children[1]}"
37
+ else
38
+ node.children[1].to_s
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ # @return [Parser::AST::Node]
45
+ def struct_node
46
+ node.children[2].children[0]
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end