solargraph 0.60.3 → 0.61.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 (76) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/plugins.yml +1 -5
  3. data/.github/workflows/rspec.yml +3 -1
  4. data/CHANGELOG.md +26 -0
  5. data/Gemfile +0 -3
  6. data/lib/solargraph/api_map/index.rb +15 -2
  7. data/lib/solargraph/api_map/store.rb +13 -0
  8. data/lib/solargraph/api_map.rb +60 -81
  9. data/lib/solargraph/cache_dir.rb +58 -0
  10. data/lib/solargraph/collection/base.rb +50 -0
  11. data/lib/solargraph/collection/core.rb +17 -0
  12. data/lib/solargraph/collection/gem.rb +57 -0
  13. data/lib/solargraph/collection/rbs.rb +49 -0
  14. data/lib/solargraph/collection/stdlib.rb +25 -0
  15. data/lib/solargraph/collection.rb +31 -0
  16. data/lib/solargraph/complex_type.rb +18 -5
  17. data/lib/solargraph/convention.rb +5 -3
  18. data/lib/solargraph/diagnostics/require_not_found.rb +0 -4
  19. data/lib/solargraph/external.rb +159 -0
  20. data/lib/solargraph/language_server/message/text_document/definition.rb +4 -1
  21. data/lib/solargraph/language_server/message/text_document/hover.rb +1 -2
  22. data/lib/solargraph/library.rb +11 -24
  23. data/lib/solargraph/metagem.rb +80 -0
  24. data/lib/solargraph/parser/parser_gem/node_chainer.rb +22 -3
  25. data/lib/solargraph/parser/parser_gem/node_processors/namespace_node.rb +14 -5
  26. data/lib/solargraph/pin/closure.rb +17 -0
  27. data/lib/solargraph/pin/factory_parameter.rb +84 -0
  28. data/lib/solargraph/pin/method.rb +7 -4
  29. data/lib/solargraph/pin.rb +1 -0
  30. data/lib/solargraph/rbs_map/base.rb +31 -0
  31. data/lib/solargraph/rbs_map/conversions.rb +3 -1
  32. data/lib/solargraph/rbs_map/{core_map.rb → core.rb} +18 -39
  33. data/lib/solargraph/rbs_map/core_fills.rb +1 -1
  34. data/lib/solargraph/rbs_map/gem.rb +13 -0
  35. data/lib/solargraph/rbs_map/helpers.rb +60 -0
  36. data/lib/solargraph/rbs_map/path.rb +19 -0
  37. data/lib/solargraph/rbs_map/stdlib.rb +24 -0
  38. data/lib/solargraph/rbs_map.rb +7 -198
  39. data/lib/solargraph/repo.rb +177 -0
  40. data/lib/solargraph/shell.rb +49 -99
  41. data/lib/solargraph/source/chain/call.rb +190 -67
  42. data/lib/solargraph/source/chain/literal.rb +17 -17
  43. data/lib/solargraph/source/chain/parameter.rb +79 -0
  44. data/lib/solargraph/source/chain.rb +44 -5
  45. data/lib/solargraph/source/source_chainer.rb +40 -44
  46. data/lib/solargraph/source_map/clip.rb +5 -3
  47. data/lib/solargraph/source_map/mapper.rb +6 -2
  48. data/lib/solargraph/source_map.rb +10 -5
  49. data/lib/solargraph/type_checker/rules.rb +2 -1
  50. data/lib/solargraph/version.rb +1 -1
  51. data/lib/solargraph/workspace.rb +0 -1
  52. data/lib/solargraph/yard_map/cache.rb +1 -1
  53. data/lib/solargraph/yard_map/directives/attribute_directive.rb +1 -1
  54. data/lib/solargraph/yard_map/directives/domain_directive.rb +1 -1
  55. data/lib/solargraph/yard_map/directives/method_directive.rb +1 -1
  56. data/lib/solargraph/yard_map/directives/override_directive.rb +1 -1
  57. data/lib/solargraph/yard_map/directives/parse_directive.rb +1 -1
  58. data/lib/solargraph/yard_map/directives/scope_directive.rb +178 -0
  59. data/lib/solargraph/yard_map/directives/visibility_directive.rb +1 -1
  60. data/lib/solargraph/yard_map/directives.rb +5 -2
  61. data/lib/solargraph/yard_map/helpers.rb +1 -1
  62. data/lib/solargraph/yard_map/macro.rb +34 -5
  63. data/lib/solargraph/yard_map/mapper/to_constant.rb +1 -1
  64. data/lib/solargraph/yard_map/mapper/to_method.rb +1 -1
  65. data/lib/solargraph/yard_map/mapper/to_namespace.rb +1 -1
  66. data/lib/solargraph/yard_map/mapper.rb +37 -4
  67. data/lib/solargraph/yard_map.rb +1 -1
  68. data/lib/solargraph/yardoc.rb +43 -60
  69. data/lib/solargraph.rb +6 -3
  70. data/solargraph.gemspec +1 -1
  71. metadata +22 -9
  72. data/lib/solargraph/doc_map.rb +0 -439
  73. data/lib/solargraph/gem_pins.rb +0 -87
  74. data/lib/solargraph/pin_cache.rb +0 -248
  75. data/lib/solargraph/rbs_map/stdlib_map.rb +0 -71
  76. data/lib/solargraph/workspace/gemspecs.rb +0 -373
@@ -98,60 +98,37 @@ module Solargraph
98
98
  # @return [void]
99
99
  def clear
100
100
  puts 'Deleting all cached documentation (gems, core and stdlib)'
101
- Solargraph::PinCache.clear
101
+ Solargraph::CacheDir.clear
102
102
  end
103
103
  map 'clear-cache' => :clear
104
104
  map 'clear-cores' => :clear
105
105
 
106
- desc 'cache', 'Cache a gem', hide: true
107
- option :rebuild, type: :boolean, desc: 'Rebuild existing documentation', default: false
108
- # @return [void]
109
- # @param gem [String]
110
- # @param version [String, nil]
111
- def cache gem, version = nil
112
- gemspec = Gem::Specification.find_by_name(gem, version)
113
-
114
- if options[:rebuild] || !PinCache.has_yard?(gemspec)
115
- pins = GemPins.build_yard_pins(['yard-activesupport-concern'], gemspec)
116
- PinCache.serialize_yard_gem(gemspec, pins)
117
- end
118
-
119
- workspace = Solargraph::Workspace.new(Dir.pwd) if File.exist?('rbs_collection.yaml')
120
- rbs_map = RbsMap.from_gemspec(gemspec, workspace&.rbs_collection_path, workspace&.rbs_collection_config_path)
121
- if options[:rebuild] || !PinCache.has_rbs_collection?(gemspec, rbs_map.cache_key)
122
- PinCache.serialize_rbs_collection_gem(gemspec, rbs_map.cache_key, rbs_map.pins)
123
- end
124
- rescue Gem::MissingSpecError
125
- warn "Gem '#{gem}' not found"
126
- end
127
-
128
106
  desc 'uncache GEM [...GEM]', 'Delete specific cached gem documentation'
129
107
  long_desc %(
130
108
  Specify one or more gem names to clear. 'core' or 'stdlib' may
131
109
  also be specified to clear cached system documentation.
132
110
  Documentation will be regenerated as needed.
133
111
  )
112
+ option :directory, type: :string, desc: 'Workspace directory', default: Dir.pwd
134
113
  # @param gems [Array<String>]
135
114
  # @return [void]
136
115
  def uncache *gems
137
116
  raise ArgumentError, 'No gems specified.' if gems.empty?
117
+ repo = Solargraph::Repo.new(options[:directory])
118
+
138
119
  gems.each do |gem|
139
120
  if gem == 'core'
140
- PinCache.uncache_core
141
- next
142
- end
143
-
144
- if gem == 'stdlib'
145
- PinCache.uncache_stdlib
146
- next
121
+ Solargraph::Collection::Core.uncache
122
+ elsif gem == 'stdlib'
123
+ FileUtils.rm_rf CacheDir.stdlib_dir
124
+ else
125
+ metagem = repo.find_by_name(gem)
126
+ Solargraph::Collection::Gem.uncache(metagem) if metagem
147
127
  end
148
-
149
- spec = Gem::Specification.find_by_name(gem)
150
- PinCache.uncache_gem(spec, out: $stdout)
151
128
  end
152
129
  end
153
130
 
154
- desc 'gems [GEM[=VERSION]...] [STDLIB...] [core]', 'Cache documentation for
131
+ desc 'cache GEM_NAME [...GEM_NAME] [core]', 'Cache documentation for
155
132
  installed libraries'
156
133
  long_desc %( This command will cache the
157
134
  generated type documentation for the specified libraries. While
@@ -172,56 +149,51 @@ module Solargraph
172
149
  If the library is already cached, it will be rebuilt if the
173
150
  --rebuild option is set.
174
151
 
175
- Cached documentation is stored in #{PinCache.base_dir}, which
152
+ Cached documentation is stored in #{CacheDir.base_dir}, which
176
153
  can be stored between CI runs.
177
154
  )
155
+ option :directory, type: :string, desc: 'Workspace directory', default: Dir.pwd
178
156
  option :rebuild, type: :boolean, desc: 'Rebuild existing documentation', default: false
179
157
  # @param names [Array<String>]
180
158
  # @return [void]
181
- def gems *names
182
- # print time with ms
183
- workspace = Solargraph::Workspace.new('.')
184
-
185
- if names.empty?
186
- Gem::Specification.to_a.each { |spec| do_cache spec, rebuild: options[:rebuild] }
187
- $stderr.puts "Documentation cached for all #{Gem::Specification.count} gems."
188
- else
189
- warn("Caching these gems: #{names}")
190
- names.each do |name|
191
- if name == 'core'
192
- # @sg-ignore cache_core and core? are dynamically defined
193
- PinCache.cache_core(out: $stdout) # if !PinCache.core? || options[:rebuild]
194
- next
195
- end
196
-
197
- gemspec = workspace.find_gem(*name.split('='))
198
- if gemspec.nil?
199
- warn "Gem '#{name}' not found"
200
- else
201
- if options[:rebuild] || !PinCache.has_yard?(gemspec)
202
- pins = GemPins.build_yard_pins(['yard-activesupport-concern'], gemspec)
203
- PinCache.serialize_yard_gem(gemspec, pins)
204
- end
205
-
206
- workspace = Solargraph::Workspace.new(Dir.pwd)
207
- rbs_map = RbsMap.from_gemspec(gemspec, workspace.rbs_collection_path, workspace.rbs_collection_config_path)
208
- if options[:rebuild] || !PinCache.has_rbs_collection?(gemspec, rbs_map.cache_key)
209
- # cache pins even if result is zero, so we don't retry building pins
210
- pins = rbs_map.pins || []
211
- PinCache.serialize_rbs_collection_gem(gemspec, rbs_map.cache_key, pins)
212
- end
213
- end
214
- rescue Gem::MissingSpecError
215
- warn "Gem '#{name}' not found"
216
- rescue Gem::Requirement::BadRequirementError => e
217
- warn "Gem '#{name}' failed while loading"
218
- warn e.message
219
- # @sg-ignore Need to add nil check here
220
- warn e.backtrace.join("\n")
221
- end
222
- warn "Documentation cached for #{names.count} gems."
159
+ # @param [Array<Object>] gem_names
160
+ def cache *gem_names
161
+ repo = Solargraph::Repo.new(options[:directory])
162
+ metagems = if gem_names.empty?
163
+ if repo.bundled?
164
+ repo.bundled.select(&:cacheable?)
165
+ else
166
+ Gem::Specification.all_names
167
+ .map { |name| Gem::Specification.find_by_full_name(name) }
168
+ .map { |gemspec| Metagem.from_specification(gemspec) }
169
+ end
170
+ else
171
+ gem_names.each_with_object([]) do |name, result|
172
+ if name == 'core'
173
+ Collection::Core.uncache if options[:rebuild]
174
+ puts 'Caching core'
175
+ Collection::Core.load
176
+ else
177
+ # @todo Quick and dirty hack for solargraph-rspec require bug
178
+ # (see https://github.com/lekemula/solargraph-rspec/pull/38)
179
+ # TL;DR: `repo.find_by_path` should not be necessary
180
+ found = repo.find_by_name(name) || repo.find_by_path(name)
181
+ if found
182
+ result.push found
183
+ else
184
+ warn "Gem #{name} not found"
185
+ end
186
+ end
187
+ end
188
+ end
189
+ metagems.each do |metagem|
190
+ Collection::Gem.uncache(metagem) if options[:rebuild]
191
+ puts "Caching #{metagem.name} #{metagem.version} (#{metagem.cache_name})"
192
+ Collection::Gem.load(metagem)
223
193
  end
194
+ puts "Documentation cached for #{metagems.count} gems."
224
195
  end
196
+ map 'gems' => :cache
225
197
 
226
198
  desc 'reporters', 'Get a list of diagnostics reporters'
227
199
  # @return [void]
@@ -596,27 +568,5 @@ module Solargraph
596
568
  puts pin.inspect
597
569
  end
598
570
  end
599
-
600
- # @param gemspec [Gem::Specification, nil]
601
- # @param rebuild [Boolean]
602
- # @return [void]
603
- def do_cache gemspec, rebuild: false
604
- if gemspec.nil?
605
- warn "Gem '#{gemspec&.name}' not found"
606
- else
607
- if rebuild || !PinCache.has_yard?(gemspec)
608
- pins = GemPins.build_yard_pins(['yard-activesupport-concern'], gemspec)
609
- PinCache.serialize_yard_gem(gemspec, pins)
610
- end
611
-
612
- workspace = Solargraph::Workspace.new(Dir.pwd)
613
- rbs_map = RbsMap.from_gemspec(gemspec, workspace.rbs_collection_path, workspace.rbs_collection_config_path)
614
- if rebuild || !PinCache.has_rbs_collection?(gemspec, rbs_map.cache_key)
615
- # cache pins even if result is zero, so we don't retry building pins
616
- pins = rbs_map.pins || []
617
- PinCache.serialize_rbs_collection_gem(gemspec, rbs_map.cache_key, pins)
618
- end
619
- end
620
- end
621
571
  end
622
572
  end
@@ -64,13 +64,106 @@ module Solargraph
64
64
  [stack.first].compact
65
65
  end
66
66
  pin_groups = [] if !api_map.loose_unions && pin_groups.any?(&:empty?)
67
- pins = pin_groups.flatten.uniq(&:path)
67
+ # Several union members can end up with the same pin and return type.
68
+ # @sg-ignore Array#flatten returns a bare Array
69
+ pins = pin_groups.flatten.uniq { |p| [p.path, p.return_type.rooted_tags] }
68
70
  return [] if pins.empty?
69
71
  inferred_pins(pins, api_map, name_pin, locals)
70
72
  end
71
73
 
72
74
  private
73
75
 
76
+ # Checks whether a single overload signature matches the call's
77
+ # arguments/block and, if so, resolves its return type. Threaded
78
+ # through the caller's accumulator so behavior matches the
79
+ # original inline loop: if the overload doesn't match, the
80
+ # incoming type/signature are returned unchanged.
81
+ #
82
+ # @param overload [Pin::Signature]
83
+ # @param pin [Pin::Method]
84
+ # @param api_map [ApiMap]
85
+ # @param name_pin [Pin::Base]
86
+ # @param locals [::Array<Solargraph::Pin::LocalVariable, Solargraph::Pin::Parameter>]
87
+ # @param type [ComplexType]
88
+ # @param new_signature_pin [Pin::Signature, nil]
89
+ # @return [::Array(ComplexType, Pin::Signature)]
90
+ def match_overload_type overload, pin, api_map, name_pin, locals, type, new_signature_pin
91
+ return [type, new_signature_pin] unless overload.arity_matches?(arguments, with_block?)
92
+
93
+ match = true
94
+ atypes = []
95
+ arguments.each_with_index do |arg, idx|
96
+ param = overload.parameters[idx]
97
+ if param.nil?
98
+ match = overload.parameters.any?(&:restarg?)
99
+ break
100
+ end
101
+ arg_name_pin = Pin::ProxyType.anonymous(name_pin.context,
102
+ closure: name_pin.closure,
103
+ gates: name_pin.gates,
104
+ source: :chain)
105
+ atype = atypes[idx] ||= arg.infer(api_map, arg_name_pin, locals)
106
+ # @sg-ignore flow sensitive typing should handle is_a? and next
107
+ unless param.compatible_arg?(atype, api_map) || param.restarg?
108
+ match = false
109
+ break
110
+ end
111
+ end
112
+ return [type, new_signature_pin] unless match
113
+
114
+ if overload.block && with_block?
115
+ block_atypes = overload.block.parameters.map(&:return_type)
116
+ # @todo Need to add nil check here
117
+ # @sg-ignore Need to add nil check here
118
+ blocktype = if block.links.map(&:class) == [BlockSymbol]
119
+ # like the bar in foo(&:bar)
120
+ block_symbol_call_type(api_map, name_pin.context, block_atypes, locals)
121
+ else
122
+ block_call_type(api_map, name_pin, locals)
123
+ end
124
+ end
125
+ new_signature_pin = overload.resolve_generics_from_context_until_complete(overload.generics, atypes, nil, nil,
126
+ blocktype)
127
+ # @todo It shouldn't be necessary to choose either generics or macros
128
+ # @sg-ignore Need to add nil check here
129
+ new_return_type = if new_signature_pin.return_type.defined?
130
+ # @sg-ignore Need to add nil check here
131
+ new_signature_pin.return_type
132
+ else
133
+ # @sg-ignore Need to add nil check here
134
+ named_types = pin.parameter_names.zip(arguments.map { |arg| ComplexType.try_parse(simple_convert(arg.node).to_s) }).to_h
135
+ pin.typify(api_map).expand(named_types)
136
+ end
137
+ self_type = if head?
138
+ # If we're at the head of the chain, we called a
139
+ # method somewhere that marked itself as returning
140
+ # self. Given we didn't invoke this on an object,
141
+ # this must be a method in this same class - so we
142
+ # use our own self type
143
+ name_pin.context
144
+ else
145
+ # if we're past the head in the chain, whatever the
146
+ # type of the lhs side is what 'self' will be in its
147
+ # declaration - we can't just use the type of the
148
+ # method pin, as this might be a subclass of the
149
+ # place where the method is defined
150
+ name_pin.binder
151
+ end
152
+ # This same logic applies to the YARD work done by
153
+ # 'with_params()'.
154
+ #
155
+ # qualify(), however, happens in the namespace where
156
+ # the docs were written - from the method pin.
157
+ # @todo Need to add nil check here
158
+ type = if new_return_type.defined?
159
+ with_params(new_return_type.self_to_type(self_type), self_type).qualify(api_map, *pin.gates)
160
+ else
161
+ inferr_from_factory_parameters(api_map, pin)
162
+ end
163
+ type ||= ComplexType::UNDEFINED
164
+ [type, new_signature_pin]
165
+ end
166
+
74
167
  # @param pins [::Enumerable<Pin::Base>]
75
168
  # @param api_map [ApiMap]
76
169
  # @param name_pin [Pin::Base]
@@ -96,76 +189,20 @@ module Solargraph
96
189
  # @sg-ignore flow sensitive typing should handle is_a? and next
97
190
  # @param ol [Pin::Signature]
98
191
  sorted_overloads.each do |ol|
99
- next unless ol.arity_matches?(arguments, with_block?)
100
- match = true
101
-
102
- atypes = []
103
- arguments.each_with_index do |arg, idx|
104
- param = ol.parameters[idx]
105
- if param.nil?
106
- match = ol.parameters.any?(&:restarg?)
107
- break
108
- end
109
- arg_name_pin = Pin::ProxyType.anonymous(name_pin.context,
110
- closure: name_pin.closure,
111
- gates: name_pin.gates,
112
- source: :chain)
113
- atype = atypes[idx] ||= arg.infer(api_map, arg_name_pin, locals)
114
- unless param.compatible_arg?(atype, api_map) || param.restarg?
115
- match = false
116
- break
117
- end
118
- end
119
- if match
120
- if ol.block && with_block?
121
- block_atypes = ol.block.parameters.map(&:return_type)
122
- # @todo Need to add nil check here
123
- blocktype = if block.links.map(&:class) == [BlockSymbol]
124
- # like the bar in foo(&:bar)
125
- block_symbol_call_type(api_map, name_pin.context, block_atypes, locals)
126
- else
127
- block_call_type(api_map, name_pin, locals)
128
- end
129
- end
130
- new_signature_pin = ol.resolve_generics_from_context_until_complete(ol.generics, atypes, nil, nil,
131
- blocktype)
132
- # @todo It shouldn't be necessary to choose either generics or macros
133
- new_return_type = if new_signature_pin.return_type.defined?
134
- new_signature_pin.return_type
135
- else
136
- named_types = p.parameter_names.zip(arguments.map { |arg| ComplexType.try_parse(simple_convert(arg.node).to_s) }).to_h
137
- p.typify(api_map).expand(named_types)
138
- end
139
- self_type = if head?
140
- # If we're at the head of the chain, we called a
141
- # method somewhere that marked itself as returning
142
- # self. Given we didn't invoke this on an object,
143
- # this must be a method in this same class - so we
144
- # use our own self type
145
- name_pin.context
146
- else
147
- # if we're past the head in the chain, whatever the
148
- # type of the lhs side is what 'self' will be in its
149
- # declaration - we can't just use the type of the
150
- # method pin, as this might be a subclass of the
151
- # place where the method is defined
152
- name_pin.binder
153
- end
154
- # This same logic applies to the YARD work done by
155
- # 'with_params()'.
156
- #
157
- # qualify(), however, happens in the namespace where
158
- # the docs were written - from the method pin.
159
- # @todo Need to add nil check here
160
- if new_return_type.defined?
161
- type = with_params(new_return_type.self_to_type(self_type), self_type).qualify(api_map, *p.gates)
162
- end
163
- type ||= ComplexType::UNDEFINED
164
- end
192
+ type, new_signature_pin = match_overload_type(ol, p, api_map, name_pin, locals, type, new_signature_pin)
165
193
  break if type.defined?
166
194
  end
167
195
  p = p.with_single_signature(new_signature_pin) unless new_signature_pin.nil?
168
196
  next p.proxy(type) if type.defined?
197
+ if !p.macros.empty?
198
+ result = process_macro(p, api_map, name_pin.context, locals)
199
+ # @sg-ignore flow sensitive typing should be able to handle redefinition
200
+ next result unless result.return_type.undefined?
201
+ elsif !p.directives.empty?
202
+ result = process_directive(p, api_map, name_pin.context, locals)
203
+ # @sg-ignore flow sensitive typing should be able to handle redefinition
204
+ next result unless result.return_type.undefined?
205
+ end
169
206
  p
170
207
  end
171
208
  logger.debug do
@@ -186,6 +223,92 @@ module Solargraph
186
223
  end
187
224
  end
188
225
 
226
+ # @param api_map [ApiMap]
227
+ # @param method_pin [Pin::Method]
228
+ # @return [ComplexType, nil]
229
+ def inferr_from_factory_parameters api_map, method_pin
230
+ factory_parameter = api_map.factory_parameters_for_method(method_pin).find do |factory_param|
231
+ method_pin.parameters.each_with_index.find do |param, index|
232
+ current_argument = arguments[index]
233
+ next unless current_argument&.literal?
234
+ # @type [Solargraph::Source::Chain::Literal]
235
+ last_link = current_argument.links.last
236
+ argument_value = last_link.value
237
+
238
+ param.name == factory_param.param_name && argument_value == factory_param.value
239
+ end
240
+ end
241
+
242
+ return nil if factory_parameter.nil?
243
+
244
+ factory_parameter.return_type.qualify(api_map, method_pin.namespace)
245
+ end
246
+
247
+ # @param pin [Pin::Base]
248
+ # @param api_map [ApiMap]
249
+ # @param context [ComplexType, ComplexType::UniqueType]
250
+ # @param locals [::Array<Solargraph::Pin::LocalVariable, Solargraph::Pin::Parameter>]
251
+ # @return [Pin::Base]
252
+ def process_macro pin, api_map, context, locals
253
+ pin.macros.each do |macro|
254
+ # @todo 'Wrong argument type for
255
+ # Solargraph::Source::Chain::Call#inner_process_macro:
256
+ # macro expected YARD::Tags::MacroDirective, received
257
+ # generic<Elem>' is because we lose 'rooted' information
258
+ # in the 'Chain::Array' class internally, leaving
259
+ # ::Array#each shadowed when it shouldn't be.
260
+ # @sg-ignore macro is Solargraph::YardMap::Macro, wraps a YARD::Tags::MacroDirective
261
+ result = inner_process_macro(pin, macro, api_map, context, locals)
262
+ return result unless result.return_type.undefined?
263
+ end
264
+ Pin::ProxyType.anonymous(ComplexType::UNDEFINED, source: :chain)
265
+ end
266
+
267
+ # @param pin [Pin::Method]
268
+ # @param api_map [ApiMap]
269
+ # @param context [ComplexType, ComplexType::UniqueType]
270
+ # @param locals [::Array<Solargraph::Pin::LocalVariable, Solargraph::Pin::Parameter>]
271
+ # @return [Pin::ProxyType]
272
+ def process_directive pin, api_map, context, locals
273
+ pin.directives.each do |dir|
274
+ macro = api_map.named_macro(dir.tag.name)
275
+ next if macro.nil?
276
+ # @sg-ignore macro is Solargraph::YardMap::Macro, wraps a YARD::Tags::MacroDirective
277
+ result = inner_process_macro(pin, macro, api_map, context, locals)
278
+ return result unless result.return_type.undefined?
279
+ end
280
+ Pin::ProxyType.anonymous ComplexType::UNDEFINED, source: :chain
281
+ end
282
+
283
+ # @param pin [Pin::Base]
284
+ # @param macro [YARD::Tags::MacroDirective]
285
+ # @param api_map [ApiMap]
286
+ # @param context [ComplexType, ComplexType::UniqueType]
287
+ # @param locals [::Array<Pin::LocalVariable, Pin::Parameter>]
288
+ # @return [Pin::ProxyType]
289
+ def inner_process_macro pin, macro, api_map, context, locals
290
+ vals = arguments.map { |c| Pin::ProxyType.anonymous(c.infer(api_map, pin, locals), source: :chain) }
291
+ txt = macro.tag.text.clone
292
+ # @sg-ignore Need to add nil check here
293
+ if txt.empty? && macro.tag.name
294
+ named = api_map.named_macro(macro.tag.name)
295
+ txt = named.tag.text.clone if named
296
+ end
297
+ i = 1
298
+ vals.each do |v|
299
+ # @sg-ignore Need to add nil check here
300
+ txt.gsub!(/\$#{i}/, v.context.namespace)
301
+ i += 1
302
+ end
303
+ # @sg-ignore Need to add nil check here
304
+ docstring = Solargraph::Source.parse_docstring(txt).to_docstring
305
+ tag = docstring.tag(:return)
306
+ unless tag.nil? || tag.types.nil?
307
+ return Pin::ProxyType.anonymous(ComplexType.try_parse(*tag.types), source: :chain)
308
+ end
309
+ Pin::ProxyType.anonymous(ComplexType::UNDEFINED, source: :chain)
310
+ end
311
+
189
312
  # @param docstring [YARD::Docstring]
190
313
  # @param context [ComplexType]
191
314
  # @return [ComplexType, nil]
@@ -6,29 +6,29 @@ module Solargraph
6
6
  class Source
7
7
  class Chain
8
8
  class Literal < Link
9
- attr_reader :word, :value
9
+ attr_reader :word
10
+
11
+ # @return [::String, ::Symbol]
12
+ attr_reader :value
13
+
14
+ # @return [Parser::AST::Node]
15
+ attr_reader :node
10
16
 
11
17
  # @param type [String]
12
18
  # @param node [Parser::AST::Node, Object]
13
19
  def initialize type, node
14
20
  super("<#{type}>")
21
+ @node = node
15
22
 
16
- # @todo We might be able to do some light inference from literals and
17
- # tuples as long as literal values are intransitive.
18
-
19
- # if node.is_a?(::Parser::AST::Node)
20
- # # @sg-ignore flow sensitive typing needs to narrow down type with an if is_a? check
21
- # if node.type == :true
22
- # @value = true
23
- # # @sg-ignore flow sensitive typing needs to narrow down type with an if is_a? check
24
- # elsif node.type == :false
25
- # @value = false
26
- # # @sg-ignore flow sensitive typing needs to narrow down type with an if is_a? check
27
- # elsif %i[int sym].include?(node.type)
28
- # # @sg-ignore flow sensitive typing needs to narrow down type with an if is_a? check
29
- # @value = node.children.first
30
- # end
31
- # end
23
+ if node.is_a?(::Parser::AST::Node)
24
+ if node.type == :true
25
+ @value = true
26
+ elsif node.type == :false
27
+ @value = false
28
+ elsif %i[int sym].include?(node.type)
29
+ @value = node.children.first
30
+ end
31
+ end
32
32
  @type = type
33
33
  # @literal_type = ComplexType.try_parse(@value.inspect)
34
34
  @literal_type = ComplexType::UNDEFINED
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solargraph
4
+ class Source
5
+ class Chain
6
+ # A method parameter represented as a link in a method call chain.
7
+ #
8
+ # @example
9
+ # some_method('literal_value')
10
+ # ^^^^^^^^^^^^^
11
+ class Parameter < Link
12
+ # @param literal [Chain::Literal] - literal argument
13
+ # @param method_call_chain [Chain] method call that contains the argument
14
+ def initialize literal, method_call_chain # rubocop:disable Lint/MissingSuper
15
+ @literal = literal
16
+ @method_call_chain = method_call_chain
17
+ end
18
+
19
+ def word
20
+ @word ||= "#{method_name}(.., #{@literal.word}, ..)"
21
+ end
22
+
23
+ # @param api_map [ApiMap]
24
+ # @param name_pin [Pin::Base]
25
+ # @param locals [::Array<Pin::Parameter, Pin::LocalVariable>]
26
+ # @return [::Array<Pin::Base>]
27
+ def resolve api_map, name_pin, locals
28
+ # @type [Pin::Method]
29
+ method_pin = method_call_chain.define(api_map, name_pin, locals)&.first
30
+ return [] unless method_pin
31
+
32
+ # Use indexed lookup for better performance
33
+ api_map.factory_parameters_for_method(method_pin).select do |fp|
34
+ param_index = method_pin.parameters.find_index { |param| param.name == fp.param_name }
35
+ next if param_index.nil?
36
+ next false unless fp.value == literal_value
37
+
38
+ # a splat takes every argument from its position on
39
+ method_pin.parameters[param_index].restarg? ? current_index >= param_index : current_index == param_index
40
+ end
41
+ end
42
+
43
+ # @return [Boolean] true if this is a parameter of Kernel#require
44
+ def require_parameter?
45
+ method_call_chain.links.last.word == 'require'
46
+ end
47
+
48
+ private
49
+
50
+ # @return [Chain::Literal]
51
+ attr_reader :literal
52
+ # @ return [Chain]
53
+ attr_reader :method_call_chain
54
+
55
+ # @return [String] The name of the method that this parameter belongs to
56
+ def method_name
57
+ @method_name ||= method_call.word
58
+ end
59
+
60
+ # @return [Chain::Call]
61
+ def method_call
62
+ @method_call_chain.links.last
63
+ end
64
+
65
+ # The index of the literal in the method call chain.
66
+ # @return [Integer]
67
+ def current_index
68
+ @current_index ||= method_call_chain.node.children[2..].index(literal.node)
69
+ end
70
+
71
+ # @return [::String, ::Symbol] The literal value of the parameter
72
+ # @sg-ignore false "return type could not be inferred"
73
+ def literal_value
74
+ literal.node.children.first
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end