solargraph 0.54.4 → 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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -0
  3. data/lib/solargraph/api_map/index.rb +1 -1
  4. data/lib/solargraph/api_map/store.rb +40 -19
  5. data/lib/solargraph/api_map.rb +24 -19
  6. data/lib/solargraph/bench.rb +17 -1
  7. data/lib/solargraph/complex_type/unique_type.rb +88 -7
  8. data/lib/solargraph/complex_type.rb +35 -6
  9. data/lib/solargraph/convention/struct_definition/struct_assignment_node.rb +51 -0
  10. data/lib/solargraph/convention/struct_definition/struct_definition_node.rb +100 -0
  11. data/lib/solargraph/convention/struct_definition.rb +101 -0
  12. data/lib/solargraph/convention.rb +1 -0
  13. data/lib/solargraph/doc_map.rb +42 -18
  14. data/lib/solargraph/language_server/host/message_worker.rb +10 -7
  15. data/lib/solargraph/language_server/host.rb +1 -0
  16. data/lib/solargraph/library.rb +2 -1
  17. data/lib/solargraph/location.rb +8 -0
  18. data/lib/solargraph/parser/comment_ripper.rb +11 -6
  19. data/lib/solargraph/parser/flow_sensitive_typing.rb +226 -0
  20. data/lib/solargraph/parser/node_methods.rb +14 -0
  21. data/lib/solargraph/parser/node_processor.rb +0 -1
  22. data/lib/solargraph/parser/parser_gem/class_methods.rb +11 -6
  23. data/lib/solargraph/parser/parser_gem/node_chainer.rb +10 -10
  24. data/lib/solargraph/parser/parser_gem/node_methods.rb +3 -1
  25. data/lib/solargraph/parser/parser_gem/node_processors/and_node.rb +21 -0
  26. data/lib/solargraph/parser/parser_gem/node_processors/casgn_node.rb +21 -1
  27. data/lib/solargraph/parser/parser_gem/node_processors/if_node.rb +21 -0
  28. data/lib/solargraph/parser/parser_gem/node_processors/namespace_node.rb +26 -5
  29. data/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb +41 -0
  30. data/lib/solargraph/parser/parser_gem/node_processors/until_node.rb +28 -0
  31. data/lib/solargraph/parser/parser_gem/node_processors/while_node.rb +28 -0
  32. data/lib/solargraph/parser/parser_gem/node_processors.rb +10 -0
  33. data/lib/solargraph/parser.rb +1 -0
  34. data/lib/solargraph/pin/base.rb +9 -3
  35. data/lib/solargraph/pin/base_variable.rb +7 -1
  36. data/lib/solargraph/pin/block.rb +2 -0
  37. data/lib/solargraph/pin/breakable.rb +9 -0
  38. data/lib/solargraph/pin/local_variable.rb +7 -1
  39. data/lib/solargraph/pin/method.rb +20 -18
  40. data/lib/solargraph/pin/namespace.rb +10 -7
  41. data/lib/solargraph/pin/parameter.rb +13 -5
  42. data/lib/solargraph/pin/proxy_type.rb +12 -6
  43. data/lib/solargraph/pin/until.rb +18 -0
  44. data/lib/solargraph/pin/while.rb +18 -0
  45. data/lib/solargraph/pin.rb +3 -0
  46. data/lib/solargraph/rbs_map/conversions.rb +8 -8
  47. data/lib/solargraph/rbs_map/core_fills.rb +10 -3
  48. data/lib/solargraph/source/chain/array.rb +4 -3
  49. data/lib/solargraph/source/chain/call.rb +46 -17
  50. data/lib/solargraph/source/chain/constant.rb +1 -1
  51. data/lib/solargraph/source/chain/hash.rb +3 -2
  52. data/lib/solargraph/source/chain/link.rb +2 -0
  53. data/lib/solargraph/source/chain/literal.rb +22 -2
  54. data/lib/solargraph/source/chain/z_super.rb +1 -1
  55. data/lib/solargraph/source/chain.rb +77 -47
  56. data/lib/solargraph/source/source_chainer.rb +2 -2
  57. data/lib/solargraph/source_map/clip.rb +3 -1
  58. data/lib/solargraph/type_checker/checks.rb +4 -0
  59. data/lib/solargraph/type_checker.rb +35 -8
  60. data/lib/solargraph/version.rb +1 -1
  61. data/lib/solargraph/yard_map/mapper/to_method.rb +42 -15
  62. data/lib/solargraph/yardoc.rb +1 -1
  63. metadata +15 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 469236908941a1221785c7add8041544e118731b99e8a81b766856b96acbb307
4
- data.tar.gz: 6f2cf614a67abd04e53bb3a30a5ce2bc51b6b9cac16c474818e959c1e4c582ab
3
+ metadata.gz: c8e98755e559a9524b7d057c860e1ebb6d8a46eeb73dd567744f7562a4d49a64
4
+ data.tar.gz: 33943305fcf0c4c534fd4abf5eb24a937f6390021e681017296aac7bb9546592
5
5
  SHA512:
6
- metadata.gz: 22777792098e88c264a08e8a9785df73e00b1ed20fbfb4efe0988f9cb8aa5deec93ae520961672f2848ac7b2bf02fefccbb83a14e0c9092c2e18bd861d938f0b
7
- data.tar.gz: f154c4badced68ac0ab43bfe43c380009787c424f909801da183fdaf63e9a818daf62eb7ddb904172b9f91957d634758732460aab317235bf163c9d26921f452
6
+ metadata.gz: 37c0b331d67d299a865e4201c030dec6c1fd4ca14955012d702a44849bece0497c70449a3594ce8cdfe56669b93fbc80d1b291331a540453409a990344464ac1
7
+ data.tar.gz: c91397eeb5d2f10c551e97a741acb9a1e8fbae371b810bf375cc6024fc5985d6e2e581fb1d39c62ba9dd7f71b2cc6fa0ca801fcb74c89b5b8717beeaf8af10f0
data/CHANGELOG.md CHANGED
@@ -1,3 +1,21 @@
1
+ ## 0.55.0 - June 3, 2025
2
+ - Flow-sensitive typing - automatically downcast from is_a? calls (#856)
3
+ - Tuple enabler: infer literal types and use them for signature selection (#836)
4
+ - Signature selection improvements (#907)
5
+ - Add support for Ruby Structs (#939)
6
+ - [regression] Fix interface change breaking solargraph-rails (#940)
7
+ - [regression] Add back bundler/require support for solargraph-rails (#941)
8
+ - Add specs for initialize capabilities (#955)
9
+ - Create MethodAlias pins from YARD (#945)
10
+ - MessageWorker prioritizes synchronization (#956)
11
+ - initialize/new method pin cleanups (#949)
12
+ - Clip rebinds blocks when cursor is not part of receiver (#958)
13
+
14
+ ## 0.54.5 - May 17, 2025
15
+ - Repair unknown encoding errors (#936, #935)
16
+ - Index arbitrary pinsets (#937)
17
+ - Separate YARD cache from doc map cache (#938)
18
+
1
19
  ## 0.54.4 - May 14, 2025
2
20
  - Delete files from Library hash (#932)
3
21
  - Clip#define returns variable pins (#934, #933)
@@ -4,7 +4,7 @@ module Solargraph
4
4
  class ApiMap
5
5
  class Index
6
6
  # @param pins [Array<Pin::Base>]
7
- def initialize pins
7
+ def initialize pins = []
8
8
  catalog pins
9
9
  end
10
10
 
@@ -1,18 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
-
4
3
  module Solargraph
5
4
  class ApiMap
6
5
  # Queryable collection of Pins representing a Workspace, gems and the Ruby
7
6
  # core.
8
7
  #
9
8
  class Store
10
- # @param static [Enumerable<Pin::Base>]
11
- # @param dynamic [Enumerable<Pin::Base>]
12
- def initialize static = [], dynamic = []
13
- @static_index = Index.new(static)
14
- @dynamic = dynamic
15
- @index = @static_index.merge(dynamic)
9
+ # @param pinsets [Array<Enumerable<Pin::Base>>]
10
+ def initialize *pinsets
11
+ catalog pinsets
16
12
  end
17
13
 
18
14
  # @return [Array<Solargraph::Pin::Base>]
@@ -20,19 +16,27 @@ module Solargraph
20
16
  index.pins
21
17
  end
22
18
 
23
- # @param static [Enumerable<Pin::Base>]
24
- # @param dynamic [Enumerable<Pin::Base>]
25
- def update! static = [], dynamic = []
19
+ # @param pinsets [Array<Enumerable<Pin::Base>>]
20
+ # @return [Boolean] True if the index was updated
21
+ def update *pinsets
22
+ return catalog(pinsets) if pinsets.length != @pinsets.length
23
+
24
+ changed = pinsets.find_index.with_index { |pinset, idx| @pinsets[idx] != pinset }
25
+ return false unless changed
26
+
26
27
  # @todo Fix this map
27
28
  @fqns_pins_map = nil
28
- if @static_index.pins == static
29
- return self if @dynamic == dynamic
30
- else
31
- @static_index = Index.new(static)
29
+ return catalog(pinsets) if changed == 0
30
+
31
+ pinsets[changed..].each_with_index do |pins, idx|
32
+ @pinsets[changed + idx] = pins
33
+ @indexes[changed + idx] = if pins.empty?
34
+ @indexes[changed + idx - 1]
35
+ else
36
+ @indexes[changed + idx - 1].merge(pins)
37
+ end
32
38
  end
33
- @dynamic = dynamic
34
- @index = @static_index.merge(dynamic)
35
- self
39
+ true
36
40
  end
37
41
 
38
42
  def to_s
@@ -69,6 +73,8 @@ module Solargraph
69
73
  return superclass_references[fqns].first if superclass_references.key?(fqns)
70
74
  return 'Object' if fqns != 'BasicObject' && namespace_exists?(fqns)
71
75
  return 'Object' if fqns == 'Boolean'
76
+ simplified_literal_name = ComplexType.parse("#{fqns}").simplify_literals.name
77
+ return simplified_literal_name if simplified_literal_name != fqns
72
78
  nil
73
79
  end
74
80
 
@@ -108,7 +114,7 @@ module Solargraph
108
114
  # @param fqns [String]
109
115
  # @return [Enumerable<Solargraph::Pin::Base>]
110
116
  def get_class_variables(fqns)
111
- namespace_children(fqns).select{|pin| pin.is_a?(Pin::ClassVariable)}
117
+ namespace_children(fqns).select { |pin| pin.is_a?(Pin::ClassVariable)}
112
118
  end
113
119
 
114
120
  # @return [Enumerable<Solargraph::Pin::Base>]
@@ -190,7 +196,22 @@ module Solargraph
190
196
 
191
197
  private
192
198
 
193
- attr_reader :index
199
+ def index
200
+ @indexes.last
201
+ end
202
+
203
+ def catalog pinsets
204
+ @pinsets = pinsets
205
+ @indexes = []
206
+ pinsets.each do |pins|
207
+ if @indexes.last && pins.empty?
208
+ @indexes.push @indexes.last
209
+ else
210
+ @indexes.push(@indexes.last&.merge(pins) || Solargraph::ApiMap::Index.new(pins))
211
+ end
212
+ end
213
+ true
214
+ end
194
215
 
195
216
  # @return [Hash{::Array(String, String) => ::Array<Pin::Namespace>}]
196
217
  def fqns_pins_map
@@ -66,7 +66,7 @@ module Solargraph
66
66
  @source_map_hash = {}
67
67
  implicit.clear
68
68
  cache.clear
69
- store.update! @@core_map.pins, pins
69
+ store.update @@core_map.pins, pins
70
70
  self
71
71
  end
72
72
 
@@ -85,11 +85,9 @@ module Solargraph
85
85
  # @param bench [Bench]
86
86
  # @return [self]
87
87
  def catalog bench
88
- old_api_hash = @source_map_hash&.values&.map(&:api_hash)
89
- need_to_uncache = (old_api_hash != bench.source_maps.map(&:api_hash))
90
- # @todo Work around #to_h problem in current Ruby head (3.5)
91
- @source_map_hash = bench.source_maps.map { |s| [s.filename, s] }.to_h
92
- 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 || []
93
91
  implicit.clear
94
92
  source_map_hash.each_value do |map|
95
93
  implicit.merge map.environ
@@ -98,11 +96,8 @@ module Solargraph
98
96
  if @unresolved_requires != unresolved_requires || @doc_map&.uncached_gemspecs&.any?
99
97
  @doc_map = DocMap.new(unresolved_requires, [], bench.workspace.rbs_collection_path) # @todo Implement gem preferences
100
98
  @unresolved_requires = unresolved_requires
101
- need_to_uncache = true
102
99
  end
103
- store.update! @@core_map.pins + @doc_map.pins, implicit.pins + pins
104
- @cache.clear if need_to_uncache
105
-
100
+ @cache.clear if store.update(@@core_map.pins, @doc_map.pins, implicit.pins, iced_pins, live_pins)
106
101
  @missing_docs = [] # @todo Implement missing docs
107
102
  self
108
103
  end
@@ -111,7 +106,7 @@ module Solargraph
111
106
  # that this overload of 'protected' will typecheck @sg-ignore
112
107
  # @sg-ignore
113
108
  protected def equality_fields
114
- [self.class, @source_map_hash, implicit, @doc_map, @unresolved_requires, @missing_docs]
109
+ [self.class, @source_map_hash, implicit, @doc_map, @unresolved_requires]
115
110
  end
116
111
 
117
112
  # @return [::Array<Gem::Specification>]
@@ -269,15 +264,19 @@ module Solargraph
269
264
  # Should not be prefixed with '::'.
270
265
  # @return [String, nil] fully qualified tag
271
266
  def qualify tag, context_tag = ''
272
- return tag if ['self', nil].include?(tag)
267
+ return tag if ['Boolean', 'self', nil].include?(tag)
273
268
 
274
- context_type = ComplexType.parse(context_tag).force_rooted
269
+ context_type = ComplexType.try_parse(context_tag).force_rooted
275
270
  return unless context_type
276
271
 
277
272
  type = ComplexType.try_parse(tag)
278
273
  return unless type
274
+ return tag if type.literal?
275
+
276
+ context_type = ComplexType.try_parse(context_tag)
277
+ return unless context_type
279
278
 
280
- fqns = qualify_namespace(type.rooted_namespace, context_type.namespace)
279
+ fqns = qualify_namespace(type.rooted_namespace, context_type.rooted_namespace)
281
280
  return unless fqns
282
281
 
283
282
  fqns + type.substring
@@ -324,6 +323,11 @@ module Solargraph
324
323
  result
325
324
  end
326
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
+
327
331
  # Get an array of class variable pins for a namespace.
328
332
  #
329
333
  # @param namespace [String] A fully qualified namespace
@@ -381,7 +385,7 @@ module Solargraph
381
385
  init_pin = get_method_stack(rooted_tag, 'initialize').first
382
386
  next pin unless init_pin
383
387
 
384
- type = ComplexType.try_parse(ComplexType.try_parse(rooted_tag).namespace)
388
+ type = ComplexType::SELF
385
389
  Pin::Method.new(
386
390
  name: 'new',
387
391
  scope: :class,
@@ -390,9 +394,10 @@ module Solargraph
390
394
  signatures: init_pin.signatures.map { |sig| sig.proxy(type) },
391
395
  return_type: type,
392
396
  comments: init_pin.comments,
393
- closure: init_pin.closure
394
- # @todo Hack to force TypeChecker#internal_or_core?
395
- ).tap { |pin| pin.source = :rbs }
397
+ closure: init_pin.closure,
398
+ source: init_pin.source,
399
+ type_location: init_pin.type_location,
400
+ )
396
401
  end
397
402
  end
398
403
  result.concat inner_get_methods('Kernel', :instance, [:public], deep, skip) if visibility.include?(:private)
@@ -819,7 +824,7 @@ module Solargraph
819
824
  return pin unless pin.is_a?(Pin::MethodAlias)
820
825
  return nil if @method_alias_stack.include?(pin.path)
821
826
  @method_alias_stack.push pin.path
822
- 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
823
828
  @method_alias_stack.pop
824
829
  return nil if origin.nil?
825
830
  args = {
@@ -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
@@ -27,7 +27,7 @@ module Solargraph
27
27
  # @return [UniqueType]
28
28
  def self.parse name, substring = '', make_rooted: nil
29
29
  if name.start_with?(':::')
30
- raise "Illegal prefix: #{name}"
30
+ raise ComplexTypeError, "Illegal prefix: #{name}"
31
31
  end
32
32
  if name.start_with?('::')
33
33
  name = name[2..-1]
@@ -48,7 +48,7 @@ module Solargraph
48
48
  subs = ComplexType.parse(substring[1..-2], partial: true)
49
49
  parameters_type = PARAMETERS_TYPE_BY_STARTING_TAG.fetch(substring[0])
50
50
  if parameters_type == :hash
51
- 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)
52
52
  # @todo should be able to resolve map; both types have it
53
53
  # with same return type
54
54
  # @sg-ignore
@@ -73,19 +73,63 @@ module Solargraph
73
73
  end
74
74
  raise "Please remove leading :: and set rooted instead - #{name.inspect}" if name.start_with?('::')
75
75
  @name = name
76
- @key_types = key_types
77
- @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
78
84
  @rooted = rooted
79
85
  @all_params = []
80
- @all_params.concat key_types
81
- @all_params.concat subtypes
82
- @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
83
95
  end
84
96
 
85
97
  def to_s
86
98
  tag
87
99
  end
88
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
+
89
133
  def eql?(other)
90
134
  self.class == other.class &&
91
135
  @name == other.name &&
@@ -113,6 +157,8 @@ module Solargraph
113
157
  def rbs_name
114
158
  if name == 'undefined'
115
159
  'untyped'
160
+ elsif literal?
161
+ name
116
162
  else
117
163
  rooted_name
118
164
  end
@@ -183,6 +229,23 @@ module Solargraph
183
229
  name == GENERIC_TAG_NAME || all_params.any?(&:generic?)
184
230
  end
185
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
+
186
249
  # @param generics_to_resolve [Enumerable<String>]
187
250
  # @param context_type [UniqueType, nil]
188
251
  # @param resolved_generic_values [Hash{String => ComplexType}] Added to as types are encountered or resolved
@@ -253,6 +316,12 @@ module Solargraph
253
316
  else
254
317
  next ComplexType::UNDEFINED
255
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
256
325
  else
257
326
  context_type.all_params[idx] || ComplexType::UNDEFINED
258
327
  end
@@ -381,6 +450,18 @@ module Solargraph
381
450
 
382
451
  UNDEFINED = UniqueType.new('undefined', rooted: false)
383
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
384
465
  end
385
466
  end
386
467
  end
@@ -16,7 +16,13 @@ module Solargraph
16
16
  def initialize types = [UniqueType::UNDEFINED]
17
17
  # @todo @items here should not need an annotation
18
18
  # @type [Array<UniqueType>]
19
- @items = types.flat_map(&:items).uniq(&:to_s)
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
20
26
  end
21
27
 
22
28
  # @sg-ignore Fix "Not enough arguments to Module#protected"
@@ -70,7 +76,7 @@ module Solargraph
70
76
  end
71
77
 
72
78
  # @yieldparam [UniqueType]
73
- # @return [Array]
79
+ # @return [Array<UniqueType>]
74
80
  def map &block
75
81
  @items.map &block
76
82
  end
@@ -93,6 +99,12 @@ module Solargraph
93
99
  end
94
100
  end
95
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
+
96
108
  # @return [Integer]
97
109
  def length
98
110
  @items.length
@@ -103,10 +115,6 @@ module Solargraph
103
115
  @items
104
116
  end
105
117
 
106
- def tags
107
- @items.map(&:tag).join(', ')
108
- end
109
-
110
118
  # @param index [Integer]
111
119
  # @return [UniqueType]
112
120
  def [](index)
@@ -147,6 +155,23 @@ module Solargraph
147
155
  map(&:tag).join(', ')
148
156
  end
149
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
+
150
175
  def desc
151
176
  rooted_tags
152
177
  end
@@ -175,6 +200,10 @@ module Solargraph
175
200
  any?(&:generic?)
176
201
  end
177
202
 
203
+ def simplify_literals
204
+ ComplexType.new(map(&:simplify_literals))
205
+ end
206
+
178
207
  # @param new_name [String, nil]
179
208
  # @yieldparam t [UniqueType]
180
209
  # @yieldreturn [UniqueType]
@@ -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
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solargraph
4
+ module Convention
5
+ module StructDefinition
6
+ # A node wrapper for a Struct definition via inheritance.
7
+ # @example
8
+ # class MyStruct < Struct.new(:bar, :baz)
9
+ # def foo
10
+ # end
11
+ # end
12
+ class StructDefintionNode
13
+ class << self
14
+ # @example
15
+ # s(:class,
16
+ # s(:const, nil, :Foo),
17
+ # s(:send,
18
+ # s(:const, nil, :Struct), :new,
19
+ # s(:sym, :bar),
20
+ # s(:sym, :baz)),
21
+ # s(:hash,
22
+ # s(:pair,
23
+ # s(:sym, :keyword_init),
24
+ # s(:true)))),
25
+ # s(:def, :foo,
26
+ # s(:args),
27
+ # s(:send, nil, :bar)))
28
+ def valid?(node)
29
+ return false unless node&.type == :class
30
+
31
+ struct_definition_node?(node.children[1])
32
+ end
33
+
34
+ private
35
+
36
+ # @param struct_node [Parser::AST::Node]
37
+ # @return [Boolean]
38
+ def struct_definition_node?(struct_node)
39
+ return false unless struct_node.is_a?(::Parser::AST::Node)
40
+ return false unless struct_node&.type == :send
41
+ return false unless struct_node.children[0]&.type == :const
42
+ return false unless struct_node.children[0].children[1] == :Struct
43
+ return false unless struct_node.children[1] == :new
44
+
45
+ true
46
+ end
47
+ end
48
+
49
+ # @return [Parser::AST::Node]
50
+ def initialize(node)
51
+ @node = node
52
+ end
53
+
54
+ # @return [String]
55
+ def class_name
56
+ Parser::NodeMethods.unpack_name(node)
57
+ end
58
+
59
+ # @return [Array<Array(Parser::AST::Node, String)>]
60
+ def attributes
61
+ struct_attribute_nodes.map do |struct_def_param|
62
+ next unless struct_def_param.type == :sym
63
+ [struct_def_param, struct_def_param.children[0].to_s]
64
+ end.compact
65
+ end
66
+
67
+ def keyword_init?
68
+ keyword_init_param = struct_attribute_nodes.find do |struct_def_param|
69
+ struct_def_param.type == :hash && struct_def_param.children[0].type == :pair &&
70
+ struct_def_param.children[0].children[0].children[0] == :keyword_init
71
+ end
72
+
73
+ return false if keyword_init_param.nil?
74
+
75
+ keyword_init_param.children[0].children[1].type == :true
76
+ end
77
+
78
+ # @return [Parser::AST::Node]
79
+ def body_node
80
+ node.children[2]
81
+ end
82
+
83
+ private
84
+
85
+ # @return [Parser::AST::Node]
86
+ attr_reader :node
87
+
88
+ # @return [Parser::AST::Node]
89
+ def struct_node
90
+ node.children[1]
91
+ end
92
+
93
+ # @return [Array<Parser::AST::Node>]
94
+ def struct_attribute_nodes
95
+ struct_node.children[2..-1]
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end