solargraph 0.53.4 → 0.54.1

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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +41 -0
  3. data/lib/solargraph/api_map/cache.rb +2 -12
  4. data/lib/solargraph/api_map/store.rb +14 -5
  5. data/lib/solargraph/api_map.rb +67 -24
  6. data/lib/solargraph/complex_type/type_methods.rb +70 -39
  7. data/lib/solargraph/complex_type/unique_type.rb +187 -73
  8. data/lib/solargraph/complex_type.rb +105 -40
  9. data/lib/solargraph/doc_map.rb +19 -3
  10. data/lib/solargraph/gem_pins.rb +9 -1
  11. data/lib/solargraph/language_server/host/dispatch.rb +8 -1
  12. data/lib/solargraph/language_server/host/message_worker.rb +29 -3
  13. data/lib/solargraph/language_server/host/sources.rb +1 -61
  14. data/lib/solargraph/language_server/host.rb +21 -68
  15. data/lib/solargraph/language_server/message/base.rb +1 -1
  16. data/lib/solargraph/language_server/message/initialize.rb +14 -0
  17. data/lib/solargraph/language_server/message/text_document/definition.rb +3 -3
  18. data/lib/solargraph/language_server/message/text_document/document_symbol.rb +3 -3
  19. data/lib/solargraph/language_server/message/text_document/formatting.rb +1 -0
  20. data/lib/solargraph/language_server/message/text_document/hover.rb +1 -1
  21. data/lib/solargraph/language_server/message/text_document/type_definition.rb +3 -3
  22. data/lib/solargraph/language_server/message/workspace/workspace_symbol.rb +2 -2
  23. data/lib/solargraph/language_server/progress.rb +135 -0
  24. data/lib/solargraph/language_server.rb +1 -0
  25. data/lib/solargraph/library.rb +144 -113
  26. data/lib/solargraph/location.rb +14 -1
  27. data/lib/solargraph/parser/node_processor/base.rb +3 -2
  28. data/lib/solargraph/parser/node_processor.rb +1 -0
  29. data/lib/solargraph/parser/parser_gem/class_methods.rb +3 -7
  30. data/lib/solargraph/parser/parser_gem/node_chainer.rb +13 -7
  31. data/lib/solargraph/parser/parser_gem/node_methods.rb +1 -5
  32. data/lib/solargraph/parser/parser_gem/node_processors/args_node.rb +23 -19
  33. data/lib/solargraph/parser/parser_gem/node_processors/lvasgn_node.rb +2 -2
  34. data/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb +53 -0
  35. data/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +6 -4
  36. data/lib/solargraph/parser/parser_gem/node_processors.rb +2 -0
  37. data/lib/solargraph/parser.rb +2 -5
  38. data/lib/solargraph/pin/base.rb +15 -1
  39. data/lib/solargraph/pin/base_variable.rb +35 -6
  40. data/lib/solargraph/pin/block.rb +48 -11
  41. data/lib/solargraph/pin/callable.rb +147 -0
  42. data/lib/solargraph/pin/closure.rb +8 -3
  43. data/lib/solargraph/pin/common.rb +2 -6
  44. data/lib/solargraph/pin/conversions.rb +3 -2
  45. data/lib/solargraph/pin/delegated_method.rb +5 -1
  46. data/lib/solargraph/pin/documenting.rb +2 -0
  47. data/lib/solargraph/pin/instance_variable.rb +2 -2
  48. data/lib/solargraph/pin/method.rb +54 -32
  49. data/lib/solargraph/pin/namespace.rb +4 -4
  50. data/lib/solargraph/pin/parameter.rb +14 -39
  51. data/lib/solargraph/pin/proxy_type.rb +1 -1
  52. data/lib/solargraph/pin/signature.rb +3 -129
  53. data/lib/solargraph/pin.rb +4 -1
  54. data/lib/solargraph/range.rb +2 -4
  55. data/lib/solargraph/rbs_map/conversions.rb +79 -42
  56. data/lib/solargraph/rbs_map/core_fills.rb +6 -6
  57. data/lib/solargraph/rbs_map.rb +11 -3
  58. data/lib/solargraph/shell.rb +35 -15
  59. data/lib/solargraph/source/chain/array.rb +6 -5
  60. data/lib/solargraph/source/chain/block_symbol.rb +1 -1
  61. data/lib/solargraph/source/chain/block_variable.rb +1 -1
  62. data/lib/solargraph/source/chain/call.rb +78 -50
  63. data/lib/solargraph/source/chain/link.rb +9 -0
  64. data/lib/solargraph/source/chain/or.rb +1 -1
  65. data/lib/solargraph/source/chain.rb +60 -16
  66. data/lib/solargraph/source/cursor.rb +13 -2
  67. data/lib/solargraph/source/updater.rb +1 -0
  68. data/lib/solargraph/source.rb +102 -129
  69. data/lib/solargraph/source_map/clip.rb +4 -4
  70. data/lib/solargraph/source_map/data.rb +30 -0
  71. data/lib/solargraph/source_map/mapper.rb +3 -2
  72. data/lib/solargraph/source_map.rb +37 -15
  73. data/lib/solargraph/type_checker/rules.rb +6 -1
  74. data/lib/solargraph/type_checker.rb +50 -25
  75. data/lib/solargraph/version.rb +1 -1
  76. data/lib/solargraph/views/environment.erb +3 -5
  77. data/lib/solargraph/workspace/config.rb +2 -1
  78. data/lib/solargraph/workspace.rb +13 -0
  79. metadata +6 -3
  80. data/lib/solargraph/language_server/host/cataloger.rb +0 -57
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 82af0b18d57296a8eef96680e60f131bad23268e4043d49407d197fc378dd3d2
4
- data.tar.gz: 10c5622ac74f0feef06033ed0b851268c876a68581d846b2874fe61a29612acb
3
+ metadata.gz: d6619265fc1ba2cab9154e70c5f4a6626999b685d58e26a805ec606d6804f17b
4
+ data.tar.gz: cfac32a13734ba9b392d27fcb8da7c76b180f0bf64189d17698b2246ce6e3773
5
5
  SHA512:
6
- metadata.gz: 1671b54ae2c314d5c566629706b6de94ca5de0902d1d56e7b96853a889d54b0caef084a5c416222f6fe9f4d40387ea3b56d34b6af917a36855ae902f0b8acf0d
7
- data.tar.gz: e7b24e7c08164da0a0b60fc8f04281bfceef59e95470d3f3e78193b024d615da2ae62f94712222ffac722623f7de8207f7af29013bdad6787c63716556ecaf43
6
+ metadata.gz: 8d1d40bf5d19b37f6a2ee8cce73e36027140ae402d27326c15e39a961217153e8b269722503ca0a8a3b592bee50cb6227fffcd812f62a1ec8a1bf94219270eea
7
+ data.tar.gz: 627df7fb0bd23b641f0b1a40434a6384def8f32bf318b4f92d243266ee0c3145059adc29ea487dcdc30c914045e5737bca92bff6bd67fd06527f97c2e4690fb8
data/CHANGELOG.md CHANGED
@@ -1,3 +1,44 @@
1
+ ## 0.54.1 - April 26, 2025
2
+ - Retire more RubyVM-specific code (#797)
3
+ - Add additional docs for key classes, modules and methods (#802)
4
+ - Populate location information from RBS files (#768)
5
+ - Consolidate parameter handling into Pin::Callable (#844)
6
+ - Adjust local variable presence to start after assignment, not before (#864)
7
+ - Resolve params from ref tags (#872)
8
+ - Reduce use of ComplexType.parse() to preserve rooted? information (#870)
9
+ - Ensure yield return types are qualified (#886)
10
+ - Understand type of 'def foo; @foo ||= bar; end' reader methods (#888)
11
+ - Improvements to #inspect output on pins and chains (#895)
12
+ - Block method resolution improvements (#885)
13
+ - Understand mass assignment into instance variables (#893)
14
+ - Library sync and cache invalidation (#903)
15
+ - Handle super and yield scenarios from blocks (#891)
16
+ - Allow core and stdlib documentation to be uncached (#899)
17
+ - Surface variable names in LSP, e.g., textDocument/hover (#910)
18
+ - Keep idle progress notifications alive (#911)
19
+
20
+ ## 0.54.0 - April 14, 2025
21
+ - Add support for simple block argument destructuring (#821)
22
+ - Benchmark the typecheck command (#852)
23
+ - Send Gem Caching Progress Notifications to LSP Clients (#855)
24
+ - [breaking] Fix more complex_type_spec.rb cases (#813)
25
+ - Mass assignment support - e.g., a, b = ['1', '2'] (#843)
26
+ - Memoize result of Chain#infer (#857)
27
+ - Ignore malformed mixins and overloads (#862)
28
+ - Drop Parser::ParserGem::ClassMethods#returns_from_node (#866)
29
+ - Refactor TypeChecker#argument_problems_for for type safety (#867)
30
+ - Specify more type behavior for variable reassignment (#863)
31
+ - One-step source synchronization (#871)
32
+ - Show cache progress in shell commands (#874)
33
+ - Fix miscellaneous scan errors (#875)
34
+ - Synchronous libraries (#876)
35
+ - Fix parsing of Set#classify method signature from RBS (#878)
36
+ - Sync Library#diagnose (#882)
37
+ - Doesn't false-alarm over splatted non-final args in typechecking (#883)
38
+ - Remove accidental inclusion of Module's methods in objects (#884)
39
+ - Remove another splat-related false alarm in strict typechecking (#889)
40
+ - Change require path `warn` to `debug` (#897)
41
+
1
42
  ## 0.53.4 - March 30, 2025
2
43
  - [regression] Restore 'Unresolved call' typecheck for stdlib objects (#849)
3
44
  - Lazy dynamic rebinding (#851)
@@ -78,20 +78,10 @@ module Solargraph
78
78
  @receiver_definitions[path] = pin
79
79
  end
80
80
 
81
- # @param cursor [Source::Cursor]
82
- # @return [SourceMap::Clip, nil]
83
- def get_clip(cursor)
84
- @clips["#{cursor.filename}|#{cursor.range.inspect}|#{cursor.node&.to_sexp}"]
85
- end
86
-
87
- # @param cursor [Source::Cursor]
88
- # @param clip [SourceMap::Clip]
89
- def set_clip(cursor, clip)
90
- @clips["#{cursor.filename}|#{cursor.range.inspect}|#{cursor.node&.to_sexp}"] = clip
91
- end
92
-
93
81
  # @return [void]
94
82
  def clear
83
+ return if empty?
84
+
95
85
  all_caches.each(&:clear)
96
86
  end
97
87
 
@@ -3,6 +3,9 @@
3
3
 
4
4
  module Solargraph
5
5
  class ApiMap
6
+ # Queryable collection of Pins representing a Workspace, gems and the Ruby
7
+ # core.
8
+ #
6
9
  class Store
7
10
  # @return [Array<Solargraph::Pin::Base>]
8
11
  attr_reader :pins
@@ -35,6 +38,7 @@ module Solargraph
35
38
  # @param fqns [String]
36
39
  # @return [String, nil]
37
40
  def get_superclass fqns
41
+ raise "Do not prefix fully qualified namespaces with '::' - #{fqns.inspect}" if fqns.start_with?('::')
38
42
  return superclass_references[fqns].first if superclass_references.key?(fqns)
39
43
  return 'Object' if fqns != 'BasicObject' && namespace_exists?(fqns)
40
44
  return 'Object' if fqns == 'Boolean'
@@ -65,6 +69,10 @@ module Solargraph
65
69
  path_pin_hash[path] || []
66
70
  end
67
71
 
72
+ def cacheable_pins
73
+ @cacheable_pins ||= pins_by_class(Pin::Namespace) + pins_by_class(Pin::Constant) + pins_by_class(Pin::Method) + pins_by_class(Pin::Reference)
74
+ end
75
+
68
76
  # @param fqns [String]
69
77
  # @param scope [Symbol] :class or :instance
70
78
  # @return [Enumerable<Solargraph::Pin::Base>]
@@ -140,8 +148,9 @@ module Solargraph
140
148
  to_s
141
149
  end
142
150
 
143
- # @param klass [Class]
144
- # @return [Enumerable<Solargraph::Pin::Base>]
151
+ # @generic T
152
+ # @param klass [Class<T>]
153
+ # @return [Set<T>]
145
154
  def pins_by_class klass
146
155
  # @type [Set<Solargraph::Pin::Base>]
147
156
  s = Set.new
@@ -165,7 +174,7 @@ module Solargraph
165
174
 
166
175
  private
167
176
 
168
- # @return [Hash{Array(String, String) => Array<Pin::Namespace>}]
177
+ # @return [Hash{::Array(String, String) => ::Array<Pin::Namespace>}]
169
178
  def fqns_pins_map
170
179
  @fqns_pins_map ||= Hash.new do |h, (base, name)|
171
180
  value = namespace_children(base).select { |pin| pin.name == name && pin.is_a?(Pin::Namespace) }
@@ -178,7 +187,7 @@ module Solargraph
178
187
  pins_by_class(Pin::Symbol)
179
188
  end
180
189
 
181
- # @return [Hash{String => Enumerable<String>}]
190
+ # @return [Hash{String => Array<String>}]
182
191
  def superclass_references
183
192
  @superclass_references ||= {}
184
193
  end
@@ -243,7 +252,7 @@ module Solargraph
243
252
  def index
244
253
  set = pins.to_set
245
254
  @pin_class_hash = set.classify(&:class).transform_values(&:to_a)
246
- # @type [Hash{Class => Enumerable<Solargraph::Pin::Base>}]
255
+ # @type [Hash{Class => ::Array<Solargraph::Pin::Base>}]
247
256
  @pin_select_cache = {}
248
257
  @namespace_map = set.classify(&:namespace)
249
258
  @path_pin_hash = set.classify(&:path)
@@ -5,7 +5,7 @@ require 'yard'
5
5
  require 'solargraph/yard_tags'
6
6
 
7
7
  module Solargraph
8
- # An aggregate provider for information about workspaces, sources, gems, and
8
+ # An aggregate provider for information about Workspaces, Sources, gems, and
9
9
  # the Ruby core.
10
10
  #
11
11
  class ApiMap
@@ -29,6 +29,25 @@ module Solargraph
29
29
  index pins
30
30
  end
31
31
 
32
+ #
33
+ # This is a mutable object, which is cached in the Chain class -
34
+ # if you add any fields which change the results of calls (not
35
+ # just caches), please also change `equality_fields` below.
36
+ #
37
+
38
+ def eql?(other)
39
+ self.class == other.class &&
40
+ equality_fields == other.equality_fields
41
+ end
42
+
43
+ def ==(other)
44
+ self.eql?(other)
45
+ end
46
+
47
+ def hash
48
+ equality_fields.hash
49
+ end
50
+
32
51
  # @param pins [Array<Pin::Base>]
33
52
  # @return [self]
34
53
  def index pins
@@ -56,21 +75,31 @@ module Solargraph
56
75
  # @param bench [Bench]
57
76
  # @return [self]
58
77
  def catalog bench
59
- implicit.clear
60
- @cache.clear
78
+ old_api_hash = @source_map_hash&.values&.map(&:api_hash)
79
+ need_to_uncache = (old_api_hash != bench.source_maps.map(&:api_hash))
61
80
  @source_map_hash = bench.source_maps.map { |s| [s.filename, s] }.to_h
62
- pins = bench.source_maps.map(&:pins).flatten
81
+ pins = bench.source_maps.flat_map(&:pins).flatten
82
+ implicit.clear
63
83
  source_map_hash.each_value do |map|
64
84
  implicit.merge map.environ
65
85
  end
66
- unresolved_requires = (bench.external_requires + implicit.requires + bench.workspace.config.required).uniq
67
- @doc_map = DocMap.new(unresolved_requires, []) # @todo Implement gem preferences
86
+ unresolved_requires = (bench.external_requires + implicit.requires + bench.workspace.config.required).to_a.compact.uniq
87
+ if @unresolved_requires != unresolved_requires || @doc_map&.uncached_gemspecs&.any?
88
+ @doc_map = DocMap.new(unresolved_requires, [], bench.workspace.rbs_collection_path) # @todo Implement gem preferences
89
+ @unresolved_requires = unresolved_requires
90
+ need_to_uncache = true
91
+ end
68
92
  @store = Store.new(@@core_map.pins + @doc_map.pins + implicit.pins + pins)
69
- @unresolved_requires = @doc_map.unresolved_requires
93
+ @cache.clear if need_to_uncache
94
+
70
95
  @missing_docs = [] # @todo Implement missing docs
71
96
  self
72
97
  end
73
98
 
99
+ protected def equality_fields
100
+ [self.class, @source_map_hash, implicit, @doc_map, @unresolved_requires, @missing_docs]
101
+ end
102
+
74
103
  # @return [::Array<Gem::Specification>]
75
104
  def uncached_gemspecs
76
105
  @doc_map&.uncached_gemspecs || []
@@ -133,14 +162,19 @@ module Solargraph
133
162
  # Create an ApiMap with a workspace in the specified directory and cache
134
163
  # any missing gems.
135
164
  #
165
+ #
166
+ # @todo IO::NULL is incorrectly inferred to be a String.
167
+ # @sg-ignore
168
+ #
136
169
  # @param directory [String]
170
+ # @param out [IO] The output stream for messages
137
171
  # @return [ApiMap]
138
- def self.load_with_cache directory
172
+ def self.load_with_cache directory, out = IO::NULL
139
173
  api_map = load(directory)
140
174
  return api_map if api_map.uncached_gemspecs.empty?
141
175
 
142
176
  api_map.uncached_gemspecs.each do |gemspec|
143
- Solargraph.logger.info "Caching #{gemspec.name} #{gemspec.version}..."
177
+ out.puts "Caching gem #{gemspec.name} #{gemspec.version}"
144
178
  pins = GemPins.build(gemspec)
145
179
  Solargraph::Cache.save('gems', "#{gemspec.name}-#{gemspec.version}.ser", pins)
146
180
  end
@@ -216,15 +250,22 @@ module Solargraph
216
250
  # @param tag [String, nil] The namespace to
217
251
  # match, complete with generic parameters set to appropriate
218
252
  # values if available
219
- # @param context_tag [String] The context in which the tag was
220
- # referenced; start from here to resolve the name
253
+ # @param context_tag [String] The fully qualified context in which
254
+ # the tag was referenced; start from here to resolve the name.
255
+ # Should not be prefixed with '::'.
221
256
  # @return [String, nil] fully qualified tag
222
257
  def qualify tag, context_tag = ''
223
258
  return tag if ['self', nil].include?(tag)
224
- context_type = ComplexType.parse(context_tag)
225
- type = ComplexType.parse(tag)
226
- fqns = qualify_namespace(type.rooted_namespace, context_type.rooted_namespace)
227
- return nil if fqns.nil?
259
+
260
+ context_type = ComplexType.parse(context_tag).force_rooted
261
+ return unless context_type
262
+
263
+ type = ComplexType.try_parse(tag)
264
+ return unless type
265
+
266
+ fqns = qualify_namespace(type.rooted_namespace, context_type.namespace)
267
+ return unless fqns
268
+
228
269
  fqns + type.substring
229
270
  end
230
271
 
@@ -287,6 +328,11 @@ module Solargraph
287
328
  store.pins_by_class(Pin::GlobalVariable)
288
329
  end
289
330
 
331
+ # @return [Enumerable<Solargraph::Pin::Block>]
332
+ def get_block_pins
333
+ store.pins_by_class(Pin::Block)
334
+ end
335
+
290
336
  # Get an array of methods available in a particular context.
291
337
  #
292
338
  # @param rooted_tag [String] The fully qualified namespace to search for methods
@@ -332,7 +378,7 @@ module Solargraph
332
378
  end
333
379
  end
334
380
  result.concat inner_get_methods('Kernel', :instance, [:public], deep, skip) if visibility.include?(:private)
335
- result.concat inner_get_methods('Module', scope, visibility, deep, skip)
381
+ result.concat inner_get_methods('Module', scope, visibility, deep, skip) if scope == :module
336
382
  end
337
383
  resolved = resolve_method_aliases(result, visibility)
338
384
  cache.set_methods(rooted_tag, scope, visibility, deep, resolved)
@@ -466,9 +512,6 @@ module Solargraph
466
512
  def clip cursor
467
513
  raise FileNotFoundError, "ApiMap did not catalog #{cursor.filename}" unless source_map_hash.key?(cursor.filename)
468
514
 
469
- # @todo Clip caches are disabled pending resolution of a stale cache bug
470
- # cache.get_clip(cursor) ||
471
- # SourceMap::Clip.new(self, cursor).tap { |clip| cache.set_clip(cursor, clip) }
472
515
  SourceMap::Clip.new(self, cursor)
473
516
  end
474
517
 
@@ -555,7 +598,7 @@ module Solargraph
555
598
  # @param no_core [Boolean] Skip core classes if true
556
599
  # @return [Array<Pin::Base>]
557
600
  def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false
558
- rooted_type = ComplexType.parse(rooted_tag)
601
+ rooted_type = ComplexType.parse(rooted_tag).force_rooted
559
602
  fqns = rooted_type.namespace
560
603
  fqns_generic_params = rooted_type.all_params
561
604
  return [] if no_core && fqns =~ /^(Object|BasicObject|Class|Module)$/
@@ -573,8 +616,8 @@ module Solargraph
573
616
  # namespaces; resolving the generics in the method pins is this
574
617
  # class' responsibility
575
618
  raw_methods = store.get_methods(fqns, scope: scope, visibility: visibility).sort{ |a, b| a.name <=> b.name }
576
- namespace_pin = store.get_path_pins(fqns).select{|p| p.is_a?(Pin::Namespace)}.first
577
- methods = if rooted_tag != fqns
619
+ namespace_pin = store.get_path_pins(fqns).select { |p| p.is_a?(Pin::Namespace) }.first
620
+ methods = if namespace_pin && rooted_tag != fqns
578
621
  methods = raw_methods.map do |method_pin|
579
622
  method_pin.resolve_generics(namespace_pin, rooted_type)
580
623
  end
@@ -593,7 +636,7 @@ module Solargraph
593
636
  # @todo perform the same translation in the other areas
594
637
  # here after adding a spec and handling things correctly
595
638
  # in ApiMap::Store and RbsMap::Conversions
596
- resolved_include_type = ComplexType.parse(rooted_include_tag).resolve_generics(namespace_pin, rooted_type)
639
+ resolved_include_type = ComplexType.parse(rooted_include_tag).force_rooted.resolve_generics(namespace_pin, rooted_type)
597
640
  methods = inner_get_methods(resolved_include_type.tag, scope, visibility, deep, skip, true)
598
641
  result.concat methods
599
642
  end
@@ -654,7 +697,7 @@ module Solargraph
654
697
 
655
698
  # @param namespace [String]
656
699
  # @param context [String]
657
- # @return [String]
700
+ # @return [String, nil]
658
701
  def qualify_lower namespace, context
659
702
  qualify namespace, context.split('::')[0..-2].join('::')
660
703
  end
@@ -12,24 +12,36 @@ module Solargraph
12
12
  # @rooted: boolish
13
13
  # methods:
14
14
  # transform()
15
+ # all_params()
16
+ # rooted?()
17
+ # can_root_name?()
15
18
  module TypeMethods
16
19
  # @!method transform(new_name = nil, &transform_type)
17
20
  # @param new_name [String, nil]
18
21
  # @yieldparam t [UniqueType]
19
22
  # @yieldreturn [UniqueType]
20
23
  # @return [UniqueType, nil]
24
+ # @!method all_params
25
+ # @return [Array<ComplexType>]
26
+ # @!method rooted?
27
+ # @!method can_root_name?(name_to_check = nil)
28
+ # @param name_to_check [String, nil]
21
29
 
22
30
  # @return [String]
23
31
  attr_reader :name
24
32
 
25
- # @return [String]
26
- attr_reader :substring
33
+ # @return [Array<ComplexType>]
34
+ attr_reader :subtypes
27
35
 
28
36
  # @return [String]
29
- attr_reader :tag
37
+ def tag
38
+ @tag ||= "#{name}#{substring}"
39
+ end
30
40
 
31
- # @return [Array<ComplexType>]
32
- attr_reader :subtypes
41
+ # @return [String]
42
+ def rooted_tag
43
+ @rooted_tag ||= rooted_name + rooted_substring
44
+ end
33
45
 
34
46
  # @return [Boolean]
35
47
  def duck_type?
@@ -41,9 +53,8 @@ module Solargraph
41
53
  @nil_type ||= (name.casecmp('nil') == 0)
42
54
  end
43
55
 
44
- # @return [Boolean]
45
- def parameters?
46
- !substring.empty?
56
+ def tuple?
57
+ @tuple_type ||= (name == 'Tuple') || (name == 'Array' && subtypes.length >= 1 && fixed_parameters?)
47
58
  end
48
59
 
49
60
  def void?
@@ -74,19 +85,28 @@ module Solargraph
74
85
  end
75
86
  end
76
87
 
88
+ # @return [Symbol, nil]
89
+ attr_reader :parameters_type
90
+
91
+ PARAMETERS_TYPE_BY_STARTING_TAG = {
92
+ '{' => :hash,
93
+ '(' => :fixed,
94
+ '<' => :list
95
+ }.freeze
96
+
77
97
  # @return [Boolean]
78
98
  def list_parameters?
79
- substring.start_with?('<')
99
+ parameters_type == :list
80
100
  end
81
101
 
82
102
  # @return [Boolean]
83
103
  def fixed_parameters?
84
- substring.start_with?('(')
104
+ parameters_type == :fixed
85
105
  end
86
106
 
87
107
  # @return [Boolean]
88
108
  def hash_parameters?
89
- substring.start_with?('{')
109
+ parameters_type == :hash
90
110
  end
91
111
 
92
112
  # @return [Array<ComplexType>]
@@ -111,16 +131,43 @@ module Solargraph
111
131
 
112
132
  # @return [String]
113
133
  def rooted_namespace
114
- return namespace unless rooted?
134
+ return namespace unless rooted? && can_root_name?(namespace)
115
135
  "::#{namespace}"
116
136
  end
117
137
 
118
138
  # @return [String]
119
139
  def rooted_name
120
- return name unless rooted?
140
+ return name unless @rooted && can_root_name?
121
141
  "::#{name}"
122
142
  end
123
143
 
144
+ # @return [String]
145
+ def substring
146
+ @substring ||= generate_substring_from(&:tags)
147
+ end
148
+
149
+ # @return [String]
150
+ def rooted_substring
151
+ @rooted_substring = generate_substring_from(&:rooted_tags)
152
+ end
153
+
154
+ # @return [String]
155
+ def generate_substring_from(&to_str)
156
+ key_types_str = key_types.map(&to_str).join(', ')
157
+ subtypes_str = subtypes.map(&to_str).join(', ')
158
+ if key_types.none?(&:defined?) && subtypes.none?(&:defined?)
159
+ ''
160
+ elsif key_types.empty? && subtypes.empty?
161
+ ''
162
+ elsif hash_parameters?
163
+ "{#{key_types_str} => #{subtypes_str}}"
164
+ elsif fixed_parameters?
165
+ "(#{subtypes_str})"
166
+ else
167
+ "<#{subtypes_str}>"
168
+ end
169
+ end
170
+
124
171
  # @return [::Symbol] :class or :instance
125
172
  def scope
126
173
  @scope ||= :instance if duck_type? || nil_type?
@@ -133,38 +180,22 @@ module Solargraph
133
180
  tag == other.tag
134
181
  end
135
182
 
136
- def rooted?
137
- @rooted
138
- end
139
-
140
183
  # Generate a ComplexType that fully qualifies this type's namespaces.
141
184
  #
142
185
  # @param api_map [ApiMap] The ApiMap that performs qualification
143
186
  # @param context [String] The namespace from which to resolve names
144
187
  # @return [self, ComplexType, UniqueType] The generated ComplexType
145
188
  def qualify api_map, context = ''
146
- return self if name == GENERIC_TAG_NAME
147
- return ComplexType.new([self]) if duck_type? || void? || undefined?
148
- recon = (rooted? ? '' : context)
149
- fqns = api_map.qualify(name, recon)
150
- if fqns.nil?
151
- return UniqueType::BOOLEAN if tag == 'Boolean'
152
- return UniqueType::UNDEFINED
153
- end
154
- fqns = "::#{fqns}" # Ensure the resulting complex type is rooted
155
- all_ltypes = key_types.map { |t| t.qualify api_map, context }.uniq
156
- all_rtypes = value_types.map { |t| t.qualify api_map, context }
157
- if list_parameters?
158
- rtypes = all_rtypes.uniq
159
- Solargraph::ComplexType.parse("#{fqns}<#{rtypes.map(&:tag).join(', ')}>")
160
- elsif fixed_parameters?
161
- Solargraph::ComplexType.parse("#{fqns}(#{all_rtypes.map(&:tag).join(', ')})")
162
- elsif hash_parameters?
163
- ltypes = all_ltypes.uniq
164
- rtypes = all_rtypes.uniq
165
- Solargraph::ComplexType.parse("#{fqns}{#{ltypes.map(&:tag).join(', ')} => #{rtypes.map(&:tag).join(', ')}}")
166
- else
167
- Solargraph::ComplexType.parse(fqns)
189
+ transform do |t|
190
+ next t if t.name == GENERIC_TAG_NAME
191
+ next t if t.duck_type? || t.void? || t.undefined?
192
+ recon = (t.rooted? ? '' : context)
193
+ fqns = api_map.qualify(t.name, recon)
194
+ if fqns.nil?
195
+ next UniqueType::BOOLEAN if t.tag == 'Boolean'
196
+ next UniqueType::UNDEFINED
197
+ end
198
+ t.recreate(new_name: fqns, make_rooted: true)
168
199
  end
169
200
  end
170
201