solargraph 0.53.4 → 0.54.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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -0
  3. data/lib/solargraph/api_map/cache.rb +2 -12
  4. data/lib/solargraph/api_map/store.rb +11 -6
  5. data/lib/solargraph/api_map.rb +53 -17
  6. data/lib/solargraph/complex_type/type_methods.rb +62 -30
  7. data/lib/solargraph/complex_type/unique_type.rb +117 -66
  8. data/lib/solargraph/complex_type.rb +41 -25
  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/sources.rb +1 -61
  13. data/lib/solargraph/language_server/host.rb +21 -68
  14. data/lib/solargraph/language_server/message/base.rb +1 -1
  15. data/lib/solargraph/language_server/message/initialize.rb +14 -0
  16. data/lib/solargraph/language_server/message/text_document/formatting.rb +1 -0
  17. data/lib/solargraph/language_server/progress.rb +118 -0
  18. data/lib/solargraph/language_server.rb +1 -0
  19. data/lib/solargraph/library.rb +136 -96
  20. data/lib/solargraph/parser/node_processor/base.rb +3 -2
  21. data/lib/solargraph/parser/node_processor.rb +1 -0
  22. data/lib/solargraph/parser/parser_gem/class_methods.rb +3 -7
  23. data/lib/solargraph/parser/parser_gem/node_methods.rb +0 -4
  24. data/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb +47 -0
  25. data/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +5 -3
  26. data/lib/solargraph/parser/parser_gem/node_processors.rb +2 -0
  27. data/lib/solargraph/pin/base_variable.rb +34 -5
  28. data/lib/solargraph/pin/block.rb +55 -0
  29. data/lib/solargraph/pin/delegated_method.rb +5 -1
  30. data/lib/solargraph/pin/documenting.rb +2 -0
  31. data/lib/solargraph/pin/method.rb +3 -1
  32. data/lib/solargraph/pin/parameter.rb +5 -28
  33. data/lib/solargraph/rbs_map/conversions.rb +10 -6
  34. data/lib/solargraph/rbs_map.rb +11 -3
  35. data/lib/solargraph/shell.rb +18 -13
  36. data/lib/solargraph/source/chain.rb +20 -0
  37. data/lib/solargraph/source/updater.rb +1 -0
  38. data/lib/solargraph/source.rb +0 -44
  39. data/lib/solargraph/source_map/mapper.rb +3 -2
  40. data/lib/solargraph/source_map.rb +10 -0
  41. data/lib/solargraph/type_checker.rb +43 -18
  42. data/lib/solargraph/version.rb +1 -1
  43. data/lib/solargraph/workspace/config.rb +2 -1
  44. data/lib/solargraph/workspace.rb +13 -0
  45. metadata +4 -3
  46. 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: 39ad27afe3afb59299f8701f9a7ea3f25a9f95379ef68e53ac0e22eb60f50e21
4
+ data.tar.gz: 1f01a9d5cdf4aec5b50c245a25b1f7d83e5d6762e5b8f65bc9ce3555654e21ed
5
5
  SHA512:
6
- metadata.gz: 1671b54ae2c314d5c566629706b6de94ca5de0902d1d56e7b96853a889d54b0caef084a5c416222f6fe9f4d40387ea3b56d34b6af917a36855ae902f0b8acf0d
7
- data.tar.gz: e7b24e7c08164da0a0b60fc8f04281bfceef59e95470d3f3e78193b024d615da2ae62f94712222ffac722623f7de8207f7af29013bdad6787c63716556ecaf43
6
+ metadata.gz: 788f17e92a54e782ea1dcf03cb44245df823ba33b4fe866d1afa5d06c7171b40a4e1f93c97c8b26c13b58be7478b9b07cd75d6735da65ecef21ab6a4a61dbb0e
7
+ data.tar.gz: 742f43fcdce4a592a80ebe8e3acbd020f39f569cf323621a527e626ae8c6154f7dedb32739b8b62f38e98997ed414ec30471160332ecfb4a2fe5d5052e871333
data/CHANGELOG.md CHANGED
@@ -1,3 +1,25 @@
1
+ ## 0.54.0 - April 14, 2025
2
+ - Add support for simple block argument destructuring (#821)
3
+ - Benchmark the typecheck command (#852)
4
+ - Send Gem Caching Progress Notifications to LSP Clients (#855)
5
+ - [breaking] Fix more complex_type_spec.rb cases (#813)
6
+ - Mass assignment support - e.g., a, b = ['1', '2'] (#843)
7
+ - Memoize result of Chain#infer (#857)
8
+ - Ignore malformed mixins and overloads (#862)
9
+ - Drop Parser::ParserGem::ClassMethods#returns_from_node (#866)
10
+ - Refactor TypeChecker#argument_problems_for for type safety (#867)
11
+ - Specify more type behavior for variable reassignment (#863)
12
+ - One-step source synchronization (#871)
13
+ - Show cache progress in shell commands (#874)
14
+ - Fix miscellaneous scan errors (#875)
15
+ - Synchronous libraries (#876)
16
+ - Fix parsing of Set#classify method signature from RBS (#878)
17
+ - Sync Library#diagnose (#882)
18
+ - Doesn't false-alarm over splatted non-final args in typechecking (#883)
19
+ - Remove accidental inclusion of Module's methods in objects (#884)
20
+ - Remove another splat-related false alarm in strict typechecking (#889)
21
+ - Change require path `warn` to `debug` (#897)
22
+
1
23
  ## 0.53.4 - March 30, 2025
2
24
  - [regression] Restore 'Unresolved call' typecheck for stdlib objects (#849)
3
25
  - 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
 
@@ -65,6 +65,10 @@ module Solargraph
65
65
  path_pin_hash[path] || []
66
66
  end
67
67
 
68
+ def cacheable_pins
69
+ @cacheable_pins ||= pins_by_class(Pin::Namespace) + pins_by_class(Pin::Constant) + pins_by_class(Pin::Method) + pins_by_class(Pin::Reference)
70
+ end
71
+
68
72
  # @param fqns [String]
69
73
  # @param scope [Symbol] :class or :instance
70
74
  # @return [Enumerable<Solargraph::Pin::Base>]
@@ -96,7 +100,7 @@ module Solargraph
96
100
  @namespaces ||= Set.new
97
101
  end
98
102
 
99
- # @return [Enumerable<Solargraph::Pin::Base>]
103
+ # @return [Array<Solargraph::Pin::Base>]
100
104
  def namespace_pins
101
105
  pins_by_class(Solargraph::Pin::Namespace)
102
106
  end
@@ -140,8 +144,9 @@ module Solargraph
140
144
  to_s
141
145
  end
142
146
 
143
- # @param klass [Class]
144
- # @return [Enumerable<Solargraph::Pin::Base>]
147
+ # @generic T
148
+ # @param klass [Class<T>]
149
+ # @return [Array<T>]
145
150
  def pins_by_class klass
146
151
  # @type [Set<Solargraph::Pin::Base>]
147
152
  s = Set.new
@@ -165,7 +170,7 @@ module Solargraph
165
170
 
166
171
  private
167
172
 
168
- # @return [Hash{Array(String, String) => Array<Pin::Namespace>}]
173
+ # @return [Hash{::Array(String, String) => ::Array<Pin::Namespace>}]
169
174
  def fqns_pins_map
170
175
  @fqns_pins_map ||= Hash.new do |h, (base, name)|
171
176
  value = namespace_children(base).select { |pin| pin.name == name && pin.is_a?(Pin::Namespace) }
@@ -178,7 +183,7 @@ module Solargraph
178
183
  pins_by_class(Pin::Symbol)
179
184
  end
180
185
 
181
- # @return [Hash{String => Enumerable<String>}]
186
+ # @return [Hash{String => Array<String>}]
182
187
  def superclass_references
183
188
  @superclass_references ||= {}
184
189
  end
@@ -243,7 +248,7 @@ module Solargraph
243
248
  def index
244
249
  set = pins.to_set
245
250
  @pin_class_hash = set.classify(&:class).transform_values(&:to_a)
246
- # @type [Hash{Class => Enumerable<Solargraph::Pin::Base>}]
251
+ # @type [Hash{Class => ::Array<Solargraph::Pin::Base>}]
247
252
  @pin_select_cache = {}
248
253
  @namespace_map = set.classify(&:namespace)
249
254
  @path_pin_hash = set.classify(&:path)
@@ -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
@@ -221,10 +255,15 @@ module Solargraph
221
255
  # @return [String, nil] fully qualified tag
222
256
  def qualify tag, context_tag = ''
223
257
  return tag if ['self', nil].include?(tag)
224
- context_type = ComplexType.parse(context_tag)
225
- type = ComplexType.parse(tag)
258
+ context_type = ComplexType.try_parse(context_tag)
259
+ return unless context_type
260
+
261
+ type = ComplexType.try_parse(tag)
262
+ return unless type
263
+
226
264
  fqns = qualify_namespace(type.rooted_namespace, context_type.rooted_namespace)
227
- return nil if fqns.nil?
265
+ return unless fqns
266
+
228
267
  fqns + type.substring
229
268
  end
230
269
 
@@ -332,7 +371,7 @@ module Solargraph
332
371
  end
333
372
  end
334
373
  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)
374
+ result.concat inner_get_methods('Module', scope, visibility, deep, skip) if scope == :module
336
375
  end
337
376
  resolved = resolve_method_aliases(result, visibility)
338
377
  cache.set_methods(rooted_tag, scope, visibility, deep, resolved)
@@ -466,9 +505,6 @@ module Solargraph
466
505
  def clip cursor
467
506
  raise FileNotFoundError, "ApiMap did not catalog #{cursor.filename}" unless source_map_hash.key?(cursor.filename)
468
507
 
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
508
  SourceMap::Clip.new(self, cursor)
473
509
  end
474
510
 
@@ -573,8 +609,8 @@ module Solargraph
573
609
  # namespaces; resolving the generics in the method pins is this
574
610
  # class' responsibility
575
611
  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
612
+ namespace_pin = store.get_path_pins(fqns).select { |p| p.is_a?(Pin::Namespace) }.first
613
+ methods = if namespace_pin && rooted_tag != fqns
578
614
  methods = raw_methods.map do |method_pin|
579
615
  method_pin.resolve_generics(namespace_pin, rooted_type)
580
616
  end
@@ -22,14 +22,18 @@ module Solargraph
22
22
  # @return [String]
23
23
  attr_reader :name
24
24
 
25
- # @return [String]
26
- attr_reader :substring
25
+ # @return [Array<ComplexType>]
26
+ attr_reader :subtypes
27
27
 
28
28
  # @return [String]
29
- attr_reader :tag
29
+ def tag
30
+ @tag ||= "#{name}#{substring}"
31
+ end
30
32
 
31
- # @return [Array<ComplexType>]
32
- attr_reader :subtypes
33
+ # @return [String]
34
+ def rooted_tag
35
+ @rooted_tag ||= rooted_name + rooted_substring
36
+ end
33
37
 
34
38
  # @return [Boolean]
35
39
  def duck_type?
@@ -46,6 +50,10 @@ module Solargraph
46
50
  !substring.empty?
47
51
  end
48
52
 
53
+ def tuple?
54
+ @tuple_type ||= (name == 'Tuple') || (name == 'Array' && subtypes.length >= 1 && fixed_parameters?)
55
+ end
56
+
49
57
  def void?
50
58
  name == 'void'
51
59
  end
@@ -74,19 +82,28 @@ module Solargraph
74
82
  end
75
83
  end
76
84
 
85
+ # @return [Symbol, nil]
86
+ attr_reader :parameters_type
87
+
88
+ PARAMETERS_TYPE_BY_STARTING_TAG = {
89
+ '{' => :hash,
90
+ '(' => :fixed,
91
+ '<' => :list
92
+ }.freeze
93
+
77
94
  # @return [Boolean]
78
95
  def list_parameters?
79
- substring.start_with?('<')
96
+ parameters_type == :list
80
97
  end
81
98
 
82
99
  # @return [Boolean]
83
100
  def fixed_parameters?
84
- substring.start_with?('(')
101
+ parameters_type == :fixed
85
102
  end
86
103
 
87
104
  # @return [Boolean]
88
105
  def hash_parameters?
89
- substring.start_with?('{')
106
+ parameters_type == :hash
90
107
  end
91
108
 
92
109
  # @return [Array<ComplexType>]
@@ -121,6 +138,33 @@ module Solargraph
121
138
  "::#{name}"
122
139
  end
123
140
 
141
+ # @return [String]
142
+ def substring
143
+ @substring ||= generate_substring_from(&:tags)
144
+ end
145
+
146
+ # @return [String]
147
+ def rooted_substring
148
+ @rooted_substring = generate_substring_from(&:rooted_tags)
149
+ end
150
+
151
+ # @return [String]
152
+ def generate_substring_from(&to_str)
153
+ key_types_str = key_types.map(&to_str).join(', ')
154
+ subtypes_str = subtypes.map(&to_str).join(', ')
155
+ if key_types.none?(&:defined?) && subtypes.none?(&:defined?)
156
+ ''
157
+ elsif key_types.empty? && subtypes.empty?
158
+ ''
159
+ elsif hash_parameters?
160
+ "{#{key_types_str} => #{subtypes_str}}"
161
+ elsif fixed_parameters?
162
+ "(#{subtypes_str})"
163
+ else
164
+ "<#{subtypes_str}>"
165
+ end
166
+ end
167
+
124
168
  # @return [::Symbol] :class or :instance
125
169
  def scope
126
170
  @scope ||= :instance if duck_type? || nil_type?
@@ -143,28 +187,16 @@ module Solargraph
143
187
  # @param context [String] The namespace from which to resolve names
144
188
  # @return [self, ComplexType, UniqueType] The generated ComplexType
145
189
  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)
190
+ transform do |t|
191
+ next t if t.name == GENERIC_TAG_NAME
192
+ next t if t.duck_type? || t.void? || t.undefined?
193
+ recon = (t.rooted? ? '' : context)
194
+ fqns = api_map.qualify(t.name, recon)
195
+ if fqns.nil?
196
+ next UniqueType::BOOLEAN if t.tag == 'Boolean'
197
+ next UniqueType::UNDEFINED
198
+ end
199
+ t.recreate(new_name: fqns, make_rooted: true)
168
200
  end
169
201
  end
170
202
 
@@ -17,43 +17,61 @@ module Solargraph
17
17
  #
18
18
  # @param name [String] The name of the type
19
19
  # @param substring [String] The substring of the type
20
- def initialize name, substring = ''
20
+ # @param make_rooted [Boolean, nil]
21
+ # @return [UniqueType]
22
+ def self.parse name, substring = '', make_rooted: nil
23
+ if name.start_with?(':::')
24
+ raise "Illegal prefix: #{name}"
25
+ end
21
26
  if name.start_with?('::')
22
- @name = name[2..-1]
23
- @rooted = true
27
+ name = name[2..-1]
28
+ rooted = true
24
29
  else
25
- @name = name
26
- @rooted = false
30
+ rooted = false
27
31
  end
28
- @substring = substring
29
- @tag = @name + substring
30
- # @type [Array<ComplexType>]
31
- @key_types = []
32
+ rooted = make_rooted unless make_rooted.nil?
33
+
32
34
  # @type [Array<ComplexType>]
33
- @subtypes = []
35
+ key_types = []
34
36
  # @type [Array<ComplexType>]
35
- @all_params = []
36
- return unless parameters?
37
- # @todo we should be able to probe the type of 'subs' without
38
- # hoisting the definition outside of the if statement
39
- subs = if @substring.start_with?('<(') && @substring.end_with?(')>')
40
- ComplexType.parse(substring[2..-3], partial: true)
41
- else
42
- ComplexType.parse(substring[1..-2], partial: true)
43
- end
44
- if hash_parameters?
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)
46
- # @todo should be able to resolve map; both types have it
47
- # with same return type
48
- # @sg-ignore
49
- @key_types.concat subs[0].map { |u| ComplexType.new([u]) }
50
- # @sg-ignore
51
- @subtypes.concat subs[1].map { |u| ComplexType.new([u]) }
52
- else
53
- @subtypes.concat subs
37
+ subtypes = []
38
+ parameters_type = nil
39
+ unless substring.empty?
40
+ subs = ComplexType.parse(substring[1..-2], partial: true)
41
+ parameters_type = PARAMETERS_TYPE_BY_STARTING_TAG.fetch(substring[0])
42
+ if parameters_type == :hash
43
+ 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)
44
+ # @todo should be able to resolve map; both types have it
45
+ # with same return type
46
+ # @sg-ignore
47
+ key_types.concat(subs[0].map { |u| ComplexType.new([u]) })
48
+ # @sg-ignore
49
+ subtypes.concat(subs[1].map { |u| ComplexType.new([u]) })
50
+ else
51
+ subtypes.concat subs
52
+ end
54
53
  end
55
- @all_params.concat @key_types
56
- @all_params.concat @subtypes
54
+ new(name, key_types, subtypes, rooted: rooted, parameters_type: parameters_type)
55
+ end
56
+
57
+ # @param name [String]
58
+ # @param key_types [Array<ComplexType>]
59
+ # @param subtypes [Array<ComplexType>]
60
+ # @param rooted [Boolean]
61
+ # @param parameters_type [Symbol, nil]
62
+ def initialize(name, key_types = [], subtypes = [], rooted:, parameters_type: nil)
63
+ if parameters_type.nil?
64
+ raise "You must supply parameters_type if you provide parameters" unless key_types.empty? && subtypes.empty?
65
+ end
66
+ raise "Please remove leading :: and set rooted instead - #{name}" if name.start_with?('::')
67
+ @name = name
68
+ @key_types = key_types
69
+ @subtypes = subtypes
70
+ @rooted = rooted
71
+ @all_params = []
72
+ @all_params.concat key_types
73
+ @all_params.concat subtypes
74
+ @parameters_type = parameters_type
57
75
  end
58
76
 
59
77
  def to_s
@@ -76,7 +94,17 @@ module Solargraph
76
94
 
77
95
  # @return [String]
78
96
  def to_rbs
79
- if ['Tuple', 'Array'].include?(name) && fixed_parameters?
97
+ if duck_type?
98
+ 'untyped'
99
+ elsif name == 'Boolean'
100
+ 'bool'
101
+ elsif name.downcase == 'nil'
102
+ 'nil'
103
+ elsif name == GENERIC_TAG_NAME
104
+ all_params.first.name
105
+ elsif ['Class', 'Module'].include?(name)
106
+ rbs_name
107
+ elsif ['Tuple', 'Array'].include?(name) && fixed_parameters?
80
108
  # tuples don't have a name; they're just [foo, bar, baz].
81
109
  if substring == '()'
82
110
  # but there are no zero element tuples, so we go with an array
@@ -90,16 +118,37 @@ module Solargraph
90
118
  end
91
119
  end
92
120
 
121
+ # @return [Boolean]
122
+ def parameters?
123
+ !all_params.empty?
124
+ end
125
+
126
+ # @param types [Array<UniqueType, ComplexType>]
127
+ # @return [String]
128
+ def rbs_union(types)
129
+ if types.length == 1
130
+ types.first.to_rbs
131
+ else
132
+ "(#{types.map(&:to_rbs).join(' | ')})"
133
+ end
134
+ end
135
+
93
136
  # @return [String]
94
137
  def parameters_as_rbs
95
- parameters? ? "[#{all_params.map { |s| s.to_rbs }.join(', ')}]" : ''
138
+ return '' unless parameters?
139
+
140
+ return "[#{all_params.map(&:to_rbs).join(', ')}]" if key_types.empty?
141
+
142
+ # handle, e.g., Hash[K, V] case
143
+ key_types_str = rbs_union(key_types)
144
+ subtypes_str = rbs_union(subtypes)
145
+ "[#{key_types_str}, #{subtypes_str}]"
96
146
  end
97
147
 
98
148
  def generic?
99
149
  name == GENERIC_TAG_NAME || all_params.any?(&:generic?)
100
150
  end
101
151
 
102
-
103
152
  # @param generics_to_resolve [Enumerable<String>]
104
153
  # @param context_type [UniqueType, nil]
105
154
  # @param resolved_generic_values [Hash{String => ComplexType}] Added to as types are encountered or resolved
@@ -182,39 +231,34 @@ module Solargraph
182
231
  end
183
232
 
184
233
  # @param new_name [String, nil]
234
+ # @param make_rooted [Boolean, nil]
185
235
  # @param new_key_types [Array<UniqueType>, nil]
236
+ # @param rooted [Boolean, nil]
186
237
  # @param new_subtypes [Array<UniqueType>, nil]
187
238
  # @return [self]
188
- def recreate(new_name: nil, new_key_types: nil, new_subtypes: nil)
239
+ def recreate(new_name: nil, make_rooted: nil, new_key_types: nil, new_subtypes: nil)
240
+ raise "Please remove leading :: and set rooted instead - #{new_name}" if new_name&.start_with?('::')
189
241
  new_name ||= name
190
242
  new_key_types ||= @key_types
191
243
  new_subtypes ||= @subtypes
192
- if new_key_types.none?(&:defined?) && new_subtypes.none?(&:defined?)
193
- # if all subtypes are undefined, erase down to the non-parametric type
194
- UniqueType.new(new_name)
195
- elsif new_key_types.empty? && new_subtypes.empty?
196
- UniqueType.new(new_name)
197
- elsif hash_parameters?
198
- UniqueType.new(new_name, "{#{new_key_types.join(', ')} => #{new_subtypes.join(', ')}}")
199
- elsif @substring.start_with?('<(')
200
- # @todo This clause is probably wrong, and if so, fixing it
201
- # will be some level of breaking change. Probably best
202
- # handled before real tuple support is rolled out and
203
- # folks start relying on it more.
204
- #
205
- # (String) is a one element tuple in https://yardoc.org/types
206
- # <String> is an array of zero or more Strings in https://yardoc.org/types
207
- # Array<(String)> could be an Array of one-element tuples or a
208
- # one element tuple. https://yardoc.org/types treats it
209
- # as the former.
210
- # Array<(String), Integer> is not ambiguous if we accept
211
- # (String) as a tuple type, but not currently understood
212
- # by Solargraph.
213
- UniqueType.new(new_name, "<(#{new_subtypes.join(', ')})>")
214
- elsif fixed_parameters?
215
- UniqueType.new(new_name, "(#{new_subtypes.join(', ')})")
216
- else
217
- UniqueType.new(new_name, "<#{new_subtypes.join(', ')}>")
244
+ make_rooted = @rooted if make_rooted.nil?
245
+ UniqueType.new(new_name, new_key_types, new_subtypes, rooted: make_rooted, parameters_type: parameters_type)
246
+ end
247
+
248
+ # @return [String]
249
+ def rooted_tags
250
+ rooted_tag
251
+ end
252
+
253
+ # @return [String]
254
+ def tags
255
+ tag
256
+ end
257
+
258
+ # @return [self]
259
+ def force_rooted
260
+ transform do |t|
261
+ t.recreate(make_rooted: true)
218
262
  end
219
263
  end
220
264
 
@@ -225,9 +269,16 @@ module Solargraph
225
269
  # @yieldreturn [self]
226
270
  # @return [self]
227
271
  def transform(new_name = nil, &transform_type)
228
- new_key_types = @key_types.flat_map { |ct| ct.map { |ut| ut.transform(&transform_type) } }.compact
229
- new_subtypes = @subtypes.flat_map { |ct| ct.map { |ut| ut.transform(&transform_type) } }.compact
230
- new_type = recreate(new_name: new_name || rooted_name, new_key_types: new_key_types, new_subtypes: new_subtypes)
272
+ raise "Please remove leading :: and set rooted with recreate() instead - #{new_name}" if new_name&.start_with?('::')
273
+ if name == ComplexType::GENERIC_TAG_NAME
274
+ # doesn't make sense to manipulate the name of the generic
275
+ new_key_types = @key_types
276
+ new_subtypes = @subtypes
277
+ else
278
+ new_key_types = @key_types.flat_map { |ct| ct.items.map { |ut| ut.transform(&transform_type) } }
279
+ new_subtypes = @subtypes.flat_map { |ct| ct.items.map { |ut| ut.transform(&transform_type) } }
280
+ end
281
+ new_type = recreate(new_name: new_name || name, new_key_types: new_key_types, new_subtypes: new_subtypes)
231
282
  yield new_type
232
283
  end
233
284
 
@@ -245,8 +296,8 @@ module Solargraph
245
296
  @name == 'self' || @key_types.any?(&:selfy?) || @subtypes.any?(&:selfy?)
246
297
  end
247
298
 
248
- UNDEFINED = UniqueType.new('undefined')
249
- BOOLEAN = UniqueType.new('Boolean')
299
+ UNDEFINED = UniqueType.new('undefined', rooted: false)
300
+ BOOLEAN = UniqueType.new('Boolean', rooted: true)
250
301
  end
251
302
  end
252
303
  end