solargraph 0.58.1 → 0.59.0.dev.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 (162) hide show
  1. checksums.yaml +4 -4
  2. data/.envrc +3 -0
  3. data/.github/workflows/linting.yml +4 -5
  4. data/.github/workflows/plugins.yml +40 -36
  5. data/.github/workflows/rspec.yml +45 -13
  6. data/.github/workflows/typecheck.yml +2 -2
  7. data/.rubocop_todo.yml +27 -49
  8. data/README.md +3 -3
  9. data/Rakefile +1 -0
  10. data/lib/solargraph/api_map/cache.rb +110 -110
  11. data/lib/solargraph/api_map/constants.rb +289 -279
  12. data/lib/solargraph/api_map/index.rb +204 -193
  13. data/lib/solargraph/api_map/source_to_yard.rb +109 -97
  14. data/lib/solargraph/api_map/store.rb +387 -384
  15. data/lib/solargraph/api_map.rb +1000 -945
  16. data/lib/solargraph/complex_type/conformance.rb +176 -0
  17. data/lib/solargraph/complex_type/type_methods.rb +242 -228
  18. data/lib/solargraph/complex_type/unique_type.rb +632 -482
  19. data/lib/solargraph/complex_type.rb +549 -444
  20. data/lib/solargraph/convention/data_definition/data_definition_node.rb +93 -91
  21. data/lib/solargraph/convention/data_definition.rb +108 -105
  22. data/lib/solargraph/convention/struct_definition/struct_assignment_node.rb +62 -61
  23. data/lib/solargraph/convention/struct_definition/struct_definition_node.rb +103 -102
  24. data/lib/solargraph/convention/struct_definition.rb +168 -164
  25. data/lib/solargraph/diagnostics/require_not_found.rb +54 -53
  26. data/lib/solargraph/diagnostics/rubocop.rb +119 -118
  27. data/lib/solargraph/diagnostics/rubocop_helpers.rb +70 -68
  28. data/lib/solargraph/diagnostics/type_check.rb +56 -55
  29. data/lib/solargraph/doc_map.rb +200 -439
  30. data/lib/solargraph/equality.rb +34 -34
  31. data/lib/solargraph/gem_pins.rb +97 -98
  32. data/lib/solargraph/language_server/host/dispatch.rb +131 -130
  33. data/lib/solargraph/language_server/host/message_worker.rb +113 -112
  34. data/lib/solargraph/language_server/host/sources.rb +100 -99
  35. data/lib/solargraph/language_server/host.rb +883 -878
  36. data/lib/solargraph/language_server/message/extended/check_gem_version.rb +109 -114
  37. data/lib/solargraph/language_server/message/extended/document.rb +24 -23
  38. data/lib/solargraph/language_server/message/text_document/completion.rb +58 -56
  39. data/lib/solargraph/language_server/message/text_document/definition.rb +42 -40
  40. data/lib/solargraph/language_server/message/text_document/document_symbol.rb +28 -26
  41. data/lib/solargraph/language_server/message/text_document/formatting.rb +150 -148
  42. data/lib/solargraph/language_server/message/text_document/hover.rb +60 -58
  43. data/lib/solargraph/language_server/message/text_document/signature_help.rb +25 -24
  44. data/lib/solargraph/language_server/message/text_document/type_definition.rb +27 -25
  45. data/lib/solargraph/language_server/message/workspace/workspace_symbol.rb +25 -23
  46. data/lib/solargraph/library.rb +729 -683
  47. data/lib/solargraph/location.rb +87 -82
  48. data/lib/solargraph/logging.rb +57 -37
  49. data/lib/solargraph/parser/comment_ripper.rb +76 -69
  50. data/lib/solargraph/parser/flow_sensitive_typing.rb +483 -255
  51. data/lib/solargraph/parser/node_processor/base.rb +122 -92
  52. data/lib/solargraph/parser/node_processor.rb +63 -62
  53. data/lib/solargraph/parser/parser_gem/class_methods.rb +167 -149
  54. data/lib/solargraph/parser/parser_gem/node_chainer.rb +191 -166
  55. data/lib/solargraph/parser/parser_gem/node_methods.rb +506 -486
  56. data/lib/solargraph/parser/parser_gem/node_processors/and_node.rb +22 -22
  57. data/lib/solargraph/parser/parser_gem/node_processors/args_node.rb +61 -59
  58. data/lib/solargraph/parser/parser_gem/node_processors/begin_node.rb +24 -15
  59. data/lib/solargraph/parser/parser_gem/node_processors/block_node.rb +46 -46
  60. data/lib/solargraph/parser/parser_gem/node_processors/def_node.rb +60 -53
  61. data/lib/solargraph/parser/parser_gem/node_processors/if_node.rb +53 -23
  62. data/lib/solargraph/parser/parser_gem/node_processors/ivasgn_node.rb +41 -40
  63. data/lib/solargraph/parser/parser_gem/node_processors/lvasgn_node.rb +30 -29
  64. data/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb +61 -59
  65. data/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb +98 -98
  66. data/lib/solargraph/parser/parser_gem/node_processors/or_node.rb +22 -0
  67. data/lib/solargraph/parser/parser_gem/node_processors/orasgn_node.rb +17 -17
  68. data/lib/solargraph/parser/parser_gem/node_processors/resbody_node.rb +39 -38
  69. data/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb +53 -52
  70. data/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +296 -291
  71. data/lib/solargraph/parser/parser_gem/node_processors/when_node.rb +23 -0
  72. data/lib/solargraph/parser/parser_gem/node_processors/while_node.rb +33 -29
  73. data/lib/solargraph/parser/parser_gem/node_processors.rb +74 -70
  74. data/lib/solargraph/parser/region.rb +75 -69
  75. data/lib/solargraph/parser/snippet.rb +17 -17
  76. data/lib/solargraph/pin/base.rb +761 -729
  77. data/lib/solargraph/pin/base_variable.rb +418 -126
  78. data/lib/solargraph/pin/block.rb +126 -104
  79. data/lib/solargraph/pin/breakable.rb +13 -9
  80. data/lib/solargraph/pin/callable.rb +278 -231
  81. data/lib/solargraph/pin/closure.rb +68 -72
  82. data/lib/solargraph/pin/common.rb +94 -79
  83. data/lib/solargraph/pin/compound_statement.rb +55 -0
  84. data/lib/solargraph/pin/conversions.rb +124 -123
  85. data/lib/solargraph/pin/delegated_method.rb +131 -120
  86. data/lib/solargraph/pin/documenting.rb +115 -114
  87. data/lib/solargraph/pin/instance_variable.rb +38 -34
  88. data/lib/solargraph/pin/keyword.rb +16 -20
  89. data/lib/solargraph/pin/local_variable.rb +31 -75
  90. data/lib/solargraph/pin/method.rb +720 -672
  91. data/lib/solargraph/pin/method_alias.rb +42 -34
  92. data/lib/solargraph/pin/namespace.rb +121 -115
  93. data/lib/solargraph/pin/parameter.rb +338 -275
  94. data/lib/solargraph/pin/proxy_type.rb +40 -39
  95. data/lib/solargraph/pin/reference/override.rb +47 -47
  96. data/lib/solargraph/pin/reference/superclass.rb +17 -15
  97. data/lib/solargraph/pin/reference.rb +41 -39
  98. data/lib/solargraph/pin/search.rb +62 -61
  99. data/lib/solargraph/pin/signature.rb +69 -61
  100. data/lib/solargraph/pin/symbol.rb +53 -53
  101. data/lib/solargraph/pin/until.rb +18 -18
  102. data/lib/solargraph/pin/while.rb +18 -18
  103. data/lib/solargraph/pin.rb +46 -44
  104. data/lib/solargraph/pin_cache.rb +665 -245
  105. data/lib/solargraph/position.rb +118 -119
  106. data/lib/solargraph/range.rb +112 -112
  107. data/lib/solargraph/rbs_map/conversions.rb +846 -823
  108. data/lib/solargraph/rbs_map/core_map.rb +65 -58
  109. data/lib/solargraph/rbs_map/stdlib_map.rb +72 -43
  110. data/lib/solargraph/rbs_map.rb +217 -163
  111. data/lib/solargraph/shell.rb +397 -352
  112. data/lib/solargraph/source/chain/call.rb +372 -337
  113. data/lib/solargraph/source/chain/constant.rb +28 -26
  114. data/lib/solargraph/source/chain/hash.rb +35 -34
  115. data/lib/solargraph/source/chain/if.rb +29 -28
  116. data/lib/solargraph/source/chain/instance_variable.rb +34 -13
  117. data/lib/solargraph/source/chain/literal.rb +53 -48
  118. data/lib/solargraph/source/chain/or.rb +31 -23
  119. data/lib/solargraph/source/chain.rb +294 -291
  120. data/lib/solargraph/source/change.rb +89 -82
  121. data/lib/solargraph/source/cursor.rb +172 -166
  122. data/lib/solargraph/source/source_chainer.rb +204 -194
  123. data/lib/solargraph/source/updater.rb +59 -55
  124. data/lib/solargraph/source.rb +524 -498
  125. data/lib/solargraph/source_map/clip.rb +237 -226
  126. data/lib/solargraph/source_map/data.rb +37 -34
  127. data/lib/solargraph/source_map/mapper.rb +282 -259
  128. data/lib/solargraph/source_map.rb +220 -212
  129. data/lib/solargraph/type_checker/problem.rb +34 -32
  130. data/lib/solargraph/type_checker/rules.rb +157 -84
  131. data/lib/solargraph/type_checker.rb +895 -814
  132. data/lib/solargraph/version.rb +1 -1
  133. data/lib/solargraph/workspace/config.rb +257 -255
  134. data/lib/solargraph/workspace/gemspecs.rb +367 -0
  135. data/lib/solargraph/workspace/require_paths.rb +98 -97
  136. data/lib/solargraph/workspace.rb +362 -220
  137. data/lib/solargraph/yard_map/helpers.rb +45 -44
  138. data/lib/solargraph/yard_map/mapper/to_method.rb +134 -130
  139. data/lib/solargraph/yard_map/mapper/to_namespace.rb +32 -31
  140. data/lib/solargraph/yard_map/mapper.rb +84 -79
  141. data/lib/solargraph/yardoc.rb +97 -87
  142. data/lib/solargraph.rb +126 -105
  143. data/rbs/fills/rubygems/0/dependency.rbs +193 -0
  144. data/rbs/fills/tuple/tuple.rbs +28 -0
  145. data/rbs/shims/ast/0/node.rbs +5 -0
  146. data/rbs/shims/diff-lcs/1.5/diff-lcs.rbs +11 -0
  147. data/rbs_collection.yaml +1 -1
  148. data/solargraph.gemspec +2 -1
  149. metadata +22 -17
  150. data/lib/solargraph/type_checker/checks.rb +0 -124
  151. data/lib/solargraph/type_checker/param_def.rb +0 -37
  152. data/lib/solargraph/yard_map/to_method.rb +0 -89
  153. data/sig/shims/ast/0/node.rbs +0 -5
  154. /data/{sig → rbs}/shims/ast/2.4/.rbs_meta.yaml +0 -0
  155. /data/{sig → rbs}/shims/ast/2.4/ast.rbs +0 -0
  156. /data/{sig → rbs}/shims/parser/3.2.0.1/builders/default.rbs +0 -0
  157. /data/{sig → rbs}/shims/parser/3.2.0.1/manifest.yaml +0 -0
  158. /data/{sig → rbs}/shims/parser/3.2.0.1/parser.rbs +0 -0
  159. /data/{sig → rbs}/shims/parser/3.2.0.1/polyfill.rbs +0 -0
  160. /data/{sig → rbs}/shims/thor/1.2.0.1/.rbs_meta.yaml +0 -0
  161. /data/{sig → rbs}/shims/thor/1.2.0.1/manifest.yaml +0 -0
  162. /data/{sig → rbs}/shims/thor/1.2.0.1/thor.rbs +0 -0
@@ -1,672 +1,720 @@
1
- # frozen_string_literal: true
2
-
3
- module Solargraph
4
- module Pin
5
- # The base class for method and attribute pins.
6
- #
7
- class Method < Callable
8
- include Solargraph::Parser::NodeMethods
9
-
10
- # @return [::Symbol] :public, :private, or :protected
11
- attr_reader :visibility
12
-
13
- attr_writer :signatures
14
-
15
- # @return [Parser::AST::Node]
16
- attr_reader :node
17
-
18
- # @param visibility [::Symbol] :public, :protected, or :private
19
- # @param explicit [Boolean]
20
- # @param block [Pin::Signature, nil, :undefined]
21
- # @param node [Parser::AST::Node, nil]
22
- # @param attribute [Boolean]
23
- # @param signatures [::Array<Signature>, nil]
24
- # @param anon_splat [Boolean]
25
- def initialize visibility: :public, explicit: true, block: :undefined, node: nil, attribute: false, signatures: nil, anon_splat: false,
26
- **splat
27
- super(**splat)
28
- @visibility = visibility
29
- @explicit = explicit
30
- @block = block
31
- @node = node
32
- @attribute = attribute
33
- @signatures = signatures
34
- @anon_splat = anon_splat
35
- end
36
-
37
- # @param signature_pins [Array<Pin::Signature>]
38
- # @return [Array<Pin::Signature>]
39
- def combine_all_signature_pins(*signature_pins)
40
- # @type [Hash{Array => Array<Pin::Signature>}]
41
- by_arity = {}
42
- signature_pins.each do |signature_pin|
43
- by_arity[signature_pin.arity] ||= []
44
- by_arity[signature_pin.arity] << signature_pin
45
- end
46
- by_arity.transform_values! do |same_arity_pins|
47
- # @param memo [Pin::Signature, nil]
48
- # @param signature [Pin::Signature]
49
- same_arity_pins.reduce(nil) do |memo, signature|
50
- next signature if memo.nil?
51
- memo.combine_with(signature)
52
- end
53
- end
54
- by_arity.values.flatten
55
- end
56
-
57
- # @param other [Pin::Method]
58
- # @return [::Symbol]
59
- def combine_visibility(other)
60
- if dodgy_visibility_source? && !other.dodgy_visibility_source?
61
- other.visibility
62
- elsif other.dodgy_visibility_source? && !dodgy_visibility_source?
63
- visibility
64
- else
65
- assert_same(other, :visibility)
66
- end
67
- end
68
-
69
- # @param other [Pin::Method]
70
- # @return [Array<Pin::Signature>]
71
- def combine_signatures(other)
72
- all_undefined = signatures.all? { |sig| sig.return_type.undefined? }
73
- other_all_undefined = other.signatures.all? { |sig| sig.return_type.undefined? }
74
- if all_undefined && !other_all_undefined
75
- other.signatures
76
- elsif other_all_undefined && !all_undefined
77
- signatures
78
- else
79
- combine_all_signature_pins(*signatures, *other.signatures)
80
- end
81
- end
82
-
83
- def combine_with(other, attrs = {})
84
- priority_choice = choose_priority(other)
85
- return priority_choice unless priority_choice.nil?
86
-
87
- sigs = combine_signatures(other)
88
- parameters = if sigs.length > 0
89
- [].freeze
90
- else
91
- choose(other, :parameters).clone.freeze
92
- end
93
- new_attrs = {
94
- visibility: combine_visibility(other),
95
- # @sg-ignore https://github.com/castwide/solargraph/pull/1050
96
- explicit: explicit? || other.explicit?,
97
- block: combine_blocks(other),
98
- node: choose_node(other, :node),
99
- attribute: prefer_rbs_location(other, :attribute?),
100
- parameters: parameters,
101
- signatures: sigs,
102
- anon_splat: assert_same(other, :anon_splat?),
103
- return_type: nil # pulled from signatures on first call
104
- }.merge(attrs)
105
- super(other, new_attrs)
106
- end
107
-
108
- # @param other [Pin::Method]
109
- def == other
110
- super && other.node == node
111
- end
112
-
113
- def transform_types(&transform)
114
- # @todo 'super' alone should work here I think, but doesn't typecheck at level typed
115
- m = super(&transform)
116
- m.signatures = m.signatures.map do |sig|
117
- sig.transform_types(&transform)
118
- end
119
- m.block = block&.transform_types(&transform)
120
- m.reset_generated!
121
- m
122
- end
123
-
124
- # @return [void]
125
- def reset_generated!
126
- super
127
- unless signatures.empty?
128
- return_type = nil
129
- @block = :undefined
130
- parameters = []
131
- end
132
- block&.reset_generated!
133
- @signatures&.each(&:reset_generated!)
134
- signature_help = nil
135
- documentation = nil
136
- end
137
-
138
- def all_rooted?
139
- super && parameters.all?(&:all_rooted?) && (!block || block&.all_rooted?) && signatures.all?(&:all_rooted?)
140
- end
141
-
142
- # @param signature [Pin::Signature]
143
- # @return [Pin::Method]
144
- def with_single_signature(signature)
145
- m = proxy signature.return_type
146
- m.reset_generated!
147
- # @todo populating the single parameters/return_type/block
148
- # arguments here seems to be needed for some specs to pass,
149
- # even though we have a signature with the same information.
150
- # Is this a problem for RBS-populated methods, which don't
151
- # populate these three?
152
- m.parameters = signature.parameters
153
- m.return_type = signature.return_type
154
- m.block = signature.block
155
- m.signatures = [signature]
156
- m
157
- end
158
-
159
- def block?
160
- !block.nil?
161
- end
162
-
163
- # @return [Pin::Signature, nil]
164
- def block
165
- return @block unless @block == :undefined
166
- @block = signatures.first&.block
167
- end
168
-
169
- def completion_item_kind
170
- attribute? ? Solargraph::LanguageServer::CompletionItemKinds::PROPERTY : Solargraph::LanguageServer::CompletionItemKinds::METHOD
171
- end
172
-
173
- def symbol_kind
174
- attribute? ? Solargraph::LanguageServer::SymbolKinds::PROPERTY : LanguageServer::SymbolKinds::METHOD
175
- end
176
-
177
- def return_type
178
- @return_type ||= ComplexType.new(signatures.map(&:return_type).flat_map(&:items))
179
- end
180
-
181
- # @param parameters [::Array<Parameter>]
182
- # @param return_type [ComplexType]
183
- # @return [Signature]
184
- def generate_signature(parameters, return_type)
185
- block = nil
186
- yieldparam_tags = docstring.tags(:yieldparam)
187
- yieldreturn_tags = docstring.tags(:yieldreturn)
188
- generics = docstring.tags(:generic).map(&:name)
189
- needs_block_param_signature =
190
- parameters.last&.block? || !yieldreturn_tags.empty? || !yieldparam_tags.empty?
191
- if needs_block_param_signature
192
- yield_parameters = yieldparam_tags.map do |p|
193
- name = p.name
194
- decl = :arg
195
- if name
196
- decl = select_decl(name, false)
197
- name = clean_param(name)
198
- end
199
- Pin::Parameter.new(
200
- location: location,
201
- closure: self,
202
- comments: p.text,
203
- name: name,
204
- decl: decl,
205
- presence: location ? location.range : nil,
206
- return_type: ComplexType.try_parse(*p.types),
207
- source: source
208
- )
209
- end
210
- yield_return_type = ComplexType.try_parse(*yieldreturn_tags.flat_map(&:types))
211
- block = Signature.new(generics: generics, parameters: yield_parameters, return_type: yield_return_type, source: source,
212
- closure: self, location: location, type_location: type_location)
213
- end
214
- signature = Signature.new(generics: generics, parameters: parameters, return_type: return_type, block: block, closure: self, source: source,
215
- location: location, type_location: type_location)
216
- block.closure = signature if block
217
- signature
218
- end
219
-
220
- # @return [::Array<Signature>]
221
- def signatures
222
- @signatures ||= begin
223
- top_type = generate_complex_type
224
- result = []
225
- result.push generate_signature(parameters, top_type) if top_type.defined?
226
- result.concat(overloads.map { |meth| generate_signature(meth.parameters, meth.return_type) }) unless overloads.empty?
227
- result.push generate_signature(parameters, @return_type || ComplexType::UNDEFINED) if result.empty?
228
- result
229
- end
230
- end
231
-
232
- # @param return_type [ComplexType]
233
- # @return [self]
234
- def proxy_with_signatures return_type
235
- out = proxy return_type
236
- out.signatures = out.signatures.map { |sig| sig.proxy return_type }
237
- out
238
- end
239
-
240
- # @return [String, nil]
241
- def detail
242
- # This property is not cached in an instance variable because it can
243
- # change when pins get proxied.
244
- detail = String.new
245
- detail += if signatures.length > 1
246
- "(*) "
247
- else
248
- "(#{signatures.first.parameters.map(&:full).join(', ')}) " unless signatures.first.parameters.empty?
249
- end.to_s
250
- detail += "=#{probed? ? '~' : (proxied? ? '^' : '>')} #{return_type.to_s}" unless return_type.undefined?
251
- detail.strip!
252
- return nil if detail.empty?
253
- detail
254
- end
255
-
256
- # @return [::Array<Hash>]
257
- def signature_help
258
- @signature_help ||= signatures.map do |sig|
259
- {
260
- label: name + '(' + sig.parameters.map(&:full).join(', ') + ')',
261
- documentation: documentation
262
- }
263
- end
264
- end
265
-
266
- def inner_desc
267
- # ensure the signatures line up when logged
268
- if signatures.length > 1
269
- path + " \n#{to_rbs}\n"
270
- else
271
- super
272
- end
273
- end
274
-
275
- def to_rbs
276
- return nil if signatures.empty?
277
-
278
- rbs = "def #{name}: #{signatures.first.to_rbs}"
279
- signatures[1..].each do |sig|
280
- rbs += "\n"
281
- rbs += (' ' * (4 + name.length))
282
- rbs += "| #{name}: #{sig.to_rbs}"
283
- end
284
- rbs
285
- end
286
-
287
- def path
288
- @path ||= "#{namespace}#{(scope == :instance ? '#' : '.')}#{name}"
289
- end
290
-
291
- # @return [String]
292
- def method_name
293
- name
294
- end
295
-
296
- def typify api_map
297
- logger.debug { "Method#typify(self=#{self}, binder=#{binder}, closure=#{closure}, context=#{context.rooted_tags}, return_type=#{return_type.rooted_tags}) - starting" }
298
- decl = super
299
- unless decl.undefined?
300
- logger.debug { "Method#typify(self=#{self}, binder=#{binder}, closure=#{closure}, context=#{context}) => #{decl.rooted_tags.inspect} - decl found" }
301
- return decl
302
- end
303
- type = see_reference(api_map) || typify_from_super(api_map)
304
- logger.debug { "Method#typify(self=#{self}) - type=#{type&.rooted_tags.inspect}" }
305
- unless type.nil?
306
- qualified = type.qualify(api_map, *closure.gates)
307
- logger.debug { "Method#typify(self=#{self}) => #{qualified.rooted_tags.inspect}" }
308
- return qualified
309
- end
310
- super
311
- end
312
-
313
- def documentation
314
- if @documentation.nil?
315
- method_docs ||= super || ''
316
- param_tags = docstring.tags(:param)
317
- unless param_tags.nil? or param_tags.empty?
318
- method_docs += "\n\n" unless method_docs.empty?
319
- method_docs += "Params:\n"
320
- lines = []
321
- param_tags.each do |p|
322
- l = "* #{p.name}"
323
- l += " [#{escape_brackets(p.types.join(', '))}]" unless p.types.nil? or p.types.empty?
324
- l += " #{p.text}"
325
- lines.push l
326
- end
327
- method_docs += lines.join("\n")
328
- end
329
- yieldparam_tags = docstring.tags(:yieldparam)
330
- unless yieldparam_tags.nil? or yieldparam_tags.empty?
331
- method_docs += "\n\n" unless method_docs.empty?
332
- method_docs += "Block Params:\n"
333
- lines = []
334
- yieldparam_tags.each do |p|
335
- l = "* #{p.name}"
336
- l += " [#{escape_brackets(p.types.join(', '))}]" unless p.types.nil? or p.types.empty?
337
- l += " #{p.text}"
338
- lines.push l
339
- end
340
- method_docs += lines.join("\n")
341
- end
342
- yieldreturn_tags = docstring.tags(:yieldreturn)
343
- unless yieldreturn_tags.empty?
344
- method_docs += "\n\n" unless method_docs.empty?
345
- method_docs += "Block Returns:\n"
346
- lines = []
347
- yieldreturn_tags.each do |r|
348
- l = "*"
349
- l += " [#{escape_brackets(r.types.join(', '))}]" unless r.types.nil? or r.types.empty?
350
- l += " #{r.text}"
351
- lines.push l
352
- end
353
- method_docs += lines.join("\n")
354
- end
355
- return_tags = docstring.tags(:return)
356
- unless return_tags.empty?
357
- method_docs += "\n\n" unless method_docs.empty?
358
- method_docs += "Returns:\n"
359
- lines = []
360
- return_tags.each do |r|
361
- l = "*"
362
- l += " [#{escape_brackets(r.types.join(', '))}]" unless r.types.nil? or r.types.empty?
363
- l += " #{r.text}"
364
- lines.push l
365
- end
366
- method_docs += lines.join("\n")
367
- end
368
- method_docs += "\n\n" unless method_docs.empty?
369
- method_docs += "Visibility: #{visibility}"
370
- @documentation = method_docs
371
- concat_example_tags
372
- end
373
- @documentation.to_s
374
- end
375
-
376
- def explicit?
377
- @explicit
378
- end
379
-
380
- def attribute?
381
- @attribute
382
- end
383
-
384
- # @parm other [self]
385
- def nearly? other
386
- super &&
387
- # @sg-ignore https://github.com/castwide/solargraph/pull/1050
388
- parameters == other.parameters &&
389
- # @sg-ignore https://github.com/castwide/solargraph/pull/1050
390
- scope == other.scope &&
391
- # @sg-ignore https://github.com/castwide/solargraph/pull/1050
392
- visibility == other.visibility
393
- end
394
-
395
- def probe api_map
396
- attribute? ? infer_from_iv(api_map) : infer_from_return_nodes(api_map)
397
- end
398
-
399
- # @return [::Array<Pin::Method>]
400
- def overloads
401
- # Ignore overload tags with nil parameters. If it's not an array, the
402
- # tag's source is likely malformed.
403
-
404
- # @param tag [YARD::Tags::OverloadTag]
405
- @overloads ||= docstring.tags(:overload).select(&:parameters).map do |tag|
406
- Pin::Signature.new(
407
- generics: generics,
408
- # @param src [Array(String, String)]
409
- parameters: tag.parameters.map do |src|
410
- name, decl = parse_overload_param(src.first)
411
- Pin::Parameter.new(
412
- location: location,
413
- closure: self,
414
- comments: tag.docstring.all.to_s,
415
- name: name,
416
- decl: decl,
417
- presence: location ? location.range : nil,
418
- return_type: param_type_from_name(tag, src.first),
419
- source: :overloads
420
- )
421
- end,
422
- closure: self,
423
- return_type: ComplexType.try_parse(*tag.docstring.tags(:return).flat_map(&:types)),
424
- source: :overloads,
425
- )
426
- end
427
- @overloads
428
- end
429
-
430
- def anon_splat?
431
- @anon_splat
432
- end
433
-
434
- # @param api_map [ApiMap]
435
- # @return [self]
436
- def resolve_ref_tag api_map
437
- return self if @resolved_ref_tag
438
-
439
- @resolved_ref_tag = true
440
- return self unless docstring.ref_tags.any?
441
- docstring.ref_tags.each do |tag|
442
- ref = if tag.owner.to_s.start_with?(/[#.]/)
443
- api_map.get_methods(namespace)
444
- .select { |pin| pin.path.end_with?(tag.owner.to_s) }
445
- .first
446
- else
447
- # @todo Resolve relative namespaces
448
- api_map.get_path_pins(tag.owner.to_s).first
449
- end
450
- next unless ref
451
-
452
- docstring.add_tag(*ref.docstring.tags(:param))
453
- end
454
- self
455
- end
456
-
457
- # @param api_map [ApiMap]
458
- # @return [Array<Pin::Method>]
459
- def rest_of_stack api_map
460
- api_map.get_method_stack(method_namespace, method_name, scope: scope).reject { |pin| pin.path == path }
461
- end
462
-
463
- protected
464
-
465
- attr_writer :block
466
-
467
- attr_writer :signature_help
468
-
469
- attr_writer :documentation
470
-
471
- def dodgy_visibility_source?
472
- # as of 2025-03-12, the RBS generator used for
473
- # e.g. activesupport did not understand 'private' markings
474
- # inside 'class << self' blocks, but YARD did OK at it
475
- source == :rbs && scope == :class && type_location&.filename&.include?('generated') && return_type.undefined? ||
476
- # YARD's RBS generator seems to miss a lot of should-be protected instance methods
477
- source == :rbs && scope == :instance && namespace.start_with?('YARD::') ||
478
- # private on attr_readers seems to be broken in Prism's auto-generator script
479
- source == :rbs && scope == :instance && namespace.start_with?('Prism::') ||
480
- # The RBS for the RBS gem itself seems to use private as a
481
- # 'is this a public API' concept, more aggressively than the
482
- # actual code. Let's respect that and ignore the actual .rb file.
483
- source == :yardoc && scope == :instance && namespace.start_with?('RBS::')
484
- end
485
-
486
- private
487
-
488
- # @param name [String]
489
- # @param asgn [Boolean]
490
- #
491
- # @return [::Symbol]
492
- def select_decl name, asgn
493
- if name.start_with?('**')
494
- :kwrestarg
495
- elsif name.start_with?('*')
496
- :restarg
497
- elsif name.start_with?('&')
498
- :blockarg
499
- elsif name.end_with?(':') && asgn
500
- :kwoptarg
501
- elsif name.end_with?(':')
502
- :kwarg
503
- elsif asgn
504
- :optarg
505
- else
506
- :arg
507
- end
508
- end
509
-
510
- # @param name [String]
511
- # @return [String]
512
- def clean_param name
513
- name.gsub(/[*&:]/, '')
514
- end
515
-
516
- # @param tag [YARD::Tags::OverloadTag]
517
- # @param name [String]
518
- #
519
- # @return [ComplexType]
520
- def param_type_from_name(tag, name)
521
- # @param t [YARD::Tags::Tag]
522
- param = tag.tags(:param).select { |t| t.name == name }.first
523
- return ComplexType::UNDEFINED unless param
524
- ComplexType.try_parse(*param.types)
525
- end
526
-
527
- # @return [ComplexType]
528
- def generate_complex_type
529
- tags = docstring.tags(:return).map(&:types).flatten.compact
530
- return ComplexType::UNDEFINED if tags.empty?
531
- ComplexType.try_parse *tags
532
- end
533
-
534
- # @param api_map [ApiMap]
535
- # @return [ComplexType, nil]
536
- def see_reference api_map
537
- # This should actually be an intersection type
538
- # @param ref [YARD::Tags::Tag, Solargraph::Yard::Tags::RefTag]
539
- docstring.ref_tags.each do |ref|
540
- # @sg-ignore ref should actually be an intersection type
541
- next unless ref.tag_name == 'return' && ref.owner
542
- # @sg-ignore ref should actually be an intersection type
543
- result = resolve_reference(ref.owner.to_s, api_map)
544
- return result unless result.nil?
545
- end
546
- match = comments.match(/^[ \t]*\(see (.*)\)/m)
547
- return nil if match.nil?
548
- resolve_reference match[1], api_map
549
- end
550
-
551
- # @return [String]
552
- def method_namespace
553
- namespace
554
- end
555
-
556
- # @param api_map [ApiMap]
557
- # @return [ComplexType, nil]
558
- def typify_from_super api_map
559
- stack = rest_of_stack api_map
560
- return nil if stack.empty?
561
- stack.each do |pin|
562
- return pin.return_type unless pin.return_type.undefined?
563
- end
564
- nil
565
- end
566
-
567
- # @param ref [String]
568
- # @param api_map [ApiMap]
569
- # @return [ComplexType, nil]
570
- def resolve_reference ref, api_map
571
- parts = ref.split(/[.#]/)
572
- if parts.first.empty? || parts.one?
573
- path = "#{namespace}#{ref}"
574
- else
575
- fqns = api_map.qualify(parts.first, *gates)
576
- return ComplexType::UNDEFINED if fqns.nil?
577
- path = fqns + ref[parts.first.length] + parts.last
578
- end
579
- pins = api_map.get_path_pins(path)
580
- pins.each do |pin|
581
- type = pin.typify(api_map)
582
- return type unless type.undefined?
583
- end
584
- nil
585
- end
586
-
587
- # @return [Parser::AST::Node, nil]
588
- def method_body_node
589
- return nil if node.nil?
590
- return node.children[1].children.last if node.type == :DEFN
591
- return node.children[2].children.last if node.type == :DEFS
592
- return node.children[2] if node.type == :def || node.type == :DEFS
593
- return node.children[3] if node.type == :defs
594
- nil
595
- end
596
-
597
- # @param api_map [ApiMap]
598
- # @return [ComplexType]
599
- def infer_from_return_nodes api_map
600
- return ComplexType::UNDEFINED if node.nil?
601
- result = []
602
- has_nil = false
603
- return ComplexType::NIL if method_body_node.nil?
604
- returns_from_method_body(method_body_node).each do |n|
605
- if n.nil? || [:NIL, :nil].include?(n.type)
606
- has_nil = true
607
- next
608
- end
609
- rng = Range.from_node(n)
610
- next unless rng
611
- clip = api_map.clip_at(
612
- location.filename,
613
- rng.ending
614
- )
615
- chain = Solargraph::Parser.chain(n, location.filename)
616
- type = chain.infer(api_map, self, clip.locals)
617
- result.push type unless type.undefined?
618
- end
619
- result.push ComplexType::NIL if has_nil
620
- return ComplexType::UNDEFINED if result.empty?
621
- ComplexType.new(result.uniq)
622
- end
623
-
624
- # @param [ApiMap] api_map
625
- # @return [ComplexType]
626
- def infer_from_iv api_map
627
- types = []
628
- varname = "@#{name.gsub(/=$/, '')}"
629
- pins = api_map.get_instance_variable_pins(binder.namespace, binder.scope).select { |iv| iv.name == varname }
630
- pins.each do |pin|
631
- type = pin.typify(api_map)
632
- type = pin.probe(api_map) if type.undefined?
633
- types.push type if type.defined?
634
- end
635
- return ComplexType::UNDEFINED if types.empty?
636
- ComplexType.new(types.uniq)
637
- end
638
-
639
- # When YARD parses an overload tag, it includes rest modifiers in the parameters names.
640
- #
641
- # @param name [String]
642
- # @return [::Array(String, ::Symbol)]
643
- def parse_overload_param(name)
644
- # @todo this needs to handle mandatory vs not args, kwargs, blocks, etc
645
- if name.start_with?('**')
646
- [name[2..-1], :kwrestarg]
647
- elsif name.start_with?('*')
648
- [name[1..-1], :restarg]
649
- else
650
- [name, :arg]
651
- end
652
- end
653
-
654
- # @return [void]
655
- def concat_example_tags
656
- example_tags = docstring.tags(:example)
657
- return if example_tags.empty?
658
- @documentation += "\n\nExamples:\n\n```ruby\n"
659
- @documentation += example_tags.map do |tag|
660
- (tag.name && !tag.name.empty? ? "# #{tag.name}\n" : '') +
661
- "#{tag.text}\n"
662
- end
663
- .join("\n")
664
- .concat("```\n")
665
- end
666
-
667
- protected
668
-
669
- attr_writer :return_type
670
- end
671
- end
672
- end
1
+ # frozen_string_literal: true
2
+
3
+ module Solargraph
4
+ module Pin
5
+ # The base class for method and attribute pins.
6
+ #
7
+ class Method < Callable
8
+ include Solargraph::Parser::NodeMethods
9
+
10
+ # @return [::Symbol] :public, :private, or :protected
11
+ attr_reader :visibility
12
+
13
+ attr_writer :signatures
14
+
15
+ # @return [Parser::AST::Node]
16
+ attr_reader :node
17
+
18
+ # @param visibility [::Symbol] :public, :protected, or :private
19
+ # @param explicit [Boolean]
20
+ # @param block [Pin::Signature, nil, :undefined]
21
+ # @param node [Parser::AST::Node, nil]
22
+ # @param attribute [Boolean]
23
+ # @param signatures [::Array<Signature>, nil]
24
+ # @param anon_splat [Boolean]
25
+ # @param context [ComplexType, ComplexType::UniqueType, nil]
26
+ def initialize visibility: :public, explicit: true, block: :undefined, node: nil, attribute: false, signatures: nil, anon_splat: false,
27
+ context: nil, **splat
28
+ super(**splat)
29
+ @visibility = visibility
30
+ @explicit = explicit
31
+ @block = block
32
+ @node = node
33
+ @attribute = attribute
34
+ @signatures = signatures
35
+ @anon_splat = anon_splat
36
+ @context = context if context
37
+ end
38
+
39
+ # @param other [Pin::Method]
40
+ # @return [::Symbol]
41
+ def combine_visibility(other)
42
+ if dodgy_visibility_source? && !other.dodgy_visibility_source?
43
+ other.visibility
44
+ elsif other.dodgy_visibility_source? && !dodgy_visibility_source?
45
+ visibility
46
+ else
47
+ assert_same(other, :visibility)
48
+ end
49
+ end
50
+
51
+ def combine_with(other, attrs = {})
52
+ priority_choice = choose_priority(other)
53
+ return priority_choice unless priority_choice.nil?
54
+
55
+ sigs = combine_signatures(other)
56
+ parameters = if sigs.length > 0
57
+ [].freeze
58
+ else
59
+ choose(other, :parameters).clone.freeze
60
+ end
61
+ new_attrs = {
62
+ visibility: combine_visibility(other),
63
+ explicit: explicit? || other.explicit?,
64
+ block: combine_blocks(other),
65
+ node: choose_node(other, :node),
66
+ attribute: prefer_rbs_location(other, :attribute?),
67
+ parameters: parameters,
68
+ signatures: sigs,
69
+ anon_splat: assert_same(other, :anon_splat?),
70
+ return_type: nil # pulled from signatures on first call
71
+ }.merge(attrs)
72
+ super(other, new_attrs)
73
+ end
74
+
75
+ # @param other [Pin::Method]
76
+ def == other
77
+ super && other.node == node
78
+ end
79
+
80
+ def transform_types(&transform)
81
+ # @todo 'super' alone should work here I think, but doesn't typecheck at level typed
82
+ m = super(&transform)
83
+ m.signatures = m.signatures.map do |sig|
84
+ sig.transform_types(&transform)
85
+ end
86
+ m.block = block&.transform_types(&transform)
87
+ m.reset_generated!
88
+ m
89
+ end
90
+
91
+ # @return [void]
92
+ def reset_generated!
93
+ super
94
+ unless signatures.empty?
95
+ return_type = nil
96
+ @block = :undefined
97
+ parameters = []
98
+ end
99
+ block&.reset_generated!
100
+ @signatures&.each(&:reset_generated!)
101
+ signature_help = nil
102
+ documentation = nil
103
+ end
104
+
105
+ def all_rooted?
106
+ super && parameters.all?(&:all_rooted?) && (!block || block&.all_rooted?) && signatures.all?(&:all_rooted?)
107
+ end
108
+
109
+ # @param signature [Pin::Signature]
110
+ # @return [Pin::Method]
111
+ def with_single_signature(signature)
112
+ m = proxy signature.return_type
113
+ m.reset_generated!
114
+ # @todo populating the single parameters/return_type/block
115
+ # arguments here seems to be needed for some specs to pass,
116
+ # even though we have a signature with the same information.
117
+ # Is this a problem for RBS-populated methods, which don't
118
+ # populate these three?
119
+ m.parameters = signature.parameters
120
+ m.return_type = signature.return_type
121
+ m.block = signature.block
122
+ m.signatures = [signature]
123
+ m
124
+ end
125
+
126
+ def block?
127
+ !block.nil?
128
+ end
129
+
130
+ # @sg-ignore flow sensitive typing needs to remove literal with
131
+ # this unless block
132
+ # @return [Pin::Signature, nil]
133
+ def block
134
+ return @block unless @block == :undefined
135
+ @block = signatures.first&.block
136
+ end
137
+
138
+ def completion_item_kind
139
+ attribute? ? Solargraph::LanguageServer::CompletionItemKinds::PROPERTY : Solargraph::LanguageServer::CompletionItemKinds::METHOD
140
+ end
141
+
142
+ def symbol_kind
143
+ attribute? ? Solargraph::LanguageServer::SymbolKinds::PROPERTY : LanguageServer::SymbolKinds::METHOD
144
+ end
145
+
146
+ def return_type
147
+ @return_type ||= ComplexType.new(signatures.map(&:return_type).flat_map(&:items))
148
+ end
149
+
150
+ # @param parameters [::Array<Parameter>]
151
+ # @param return_type [ComplexType, nil]
152
+ # @return [Signature]
153
+ def generate_signature(parameters, return_type)
154
+ # @type [Pin::Signature, nil]
155
+ block = nil
156
+ yieldparam_tags = docstring.tags(:yieldparam)
157
+ yieldreturn_tags = docstring.tags(:yieldreturn)
158
+ generics = docstring.tags(:generic).map(&:name)
159
+ needs_block_param_signature =
160
+ parameters.last&.block? || !yieldreturn_tags.empty? || !yieldparam_tags.empty?
161
+ if needs_block_param_signature
162
+ yield_parameters = yieldparam_tags.map do |p|
163
+ name = p.name
164
+ decl = :arg
165
+ if name
166
+ decl = select_decl(name, false)
167
+ name = clean_param(name)
168
+ end
169
+ Pin::Parameter.new(
170
+ location: location,
171
+ closure: self,
172
+ comments: p.text,
173
+ name: name,
174
+ decl: decl,
175
+ # @sg-ignore flow sensitive typing needs to handle attrs
176
+ presence: location ? location.range : nil,
177
+ return_type: ComplexType.try_parse(*p.types),
178
+ source: source
179
+ )
180
+ end
181
+ yield_return_type = ComplexType.try_parse(*yieldreturn_tags.flat_map(&:types))
182
+ block = Signature.new(generics: generics, parameters: yield_parameters, return_type: yield_return_type, source: source,
183
+ closure: self, location: location, type_location: type_location)
184
+ end
185
+ signature = Signature.new(generics: generics, parameters: parameters, return_type: return_type, block: block, closure: self, source: source,
186
+ location: location, type_location: type_location)
187
+ block.closure = signature if block
188
+ signature
189
+ end
190
+
191
+ # @return [::Array<Signature>]
192
+ def signatures
193
+ @signatures ||= begin
194
+ top_type = generate_complex_type
195
+ result = []
196
+ result.push generate_signature(parameters, top_type) if top_type.defined?
197
+ result.concat(overloads.map { |meth| generate_signature(meth.parameters, meth.return_type) }) unless overloads.empty?
198
+ result.push generate_signature(parameters, @return_type || ComplexType::UNDEFINED) if result.empty?
199
+ result
200
+ end
201
+ end
202
+
203
+ # @param return_type [ComplexType]
204
+ # @return [self]
205
+ def proxy_with_signatures return_type
206
+ out = proxy return_type
207
+ out.signatures = out.signatures.map { |sig| sig.proxy return_type }
208
+ out
209
+ end
210
+
211
+ # @return [String, nil]
212
+ def detail
213
+ # This property is not cached in an instance variable because it can
214
+ # change when pins get proxied.
215
+ detail = String.new
216
+ detail += if signatures.length > 1
217
+ "(*) "
218
+ else
219
+ "(#{signatures.first.parameters.map(&:full).join(', ')}) " unless signatures.first.parameters.empty?
220
+ end.to_s
221
+ # @sg-ignore Need to add nil check here
222
+ detail += "=#{probed? ? '~' : (proxied? ? '^' : '>')} #{return_type.to_s}" unless return_type.undefined?
223
+ detail.strip!
224
+ return nil if detail.empty?
225
+ detail
226
+ end
227
+
228
+ # @return [::Array<Hash>]
229
+ def signature_help
230
+ @signature_help ||= signatures.map do |sig|
231
+ {
232
+ label: name + '(' + sig.parameters.map(&:full).join(', ') + ')',
233
+ documentation: documentation
234
+ }
235
+ end
236
+ end
237
+
238
+ def inner_desc
239
+ # ensure the signatures line up when logged
240
+ if signatures.length > 1
241
+ path + " \n#{to_rbs}\n"
242
+ else
243
+ super
244
+ end
245
+ end
246
+
247
+ def to_rbs
248
+ return nil if signatures.empty?
249
+
250
+ rbs = "def #{name}: #{signatures.first.to_rbs}"
251
+ # @sg-ignore Need to add nil check here
252
+ signatures[1..].each do |sig|
253
+ rbs += "\n"
254
+ rbs += (' ' * (4 + name.length))
255
+ rbs += "| #{name}: #{sig.to_rbs}"
256
+ end
257
+ rbs
258
+ end
259
+
260
+ def path
261
+ @path ||= "#{namespace}#{(scope == :instance ? '#' : '.')}#{name}"
262
+ end
263
+
264
+ # @return [String]
265
+ def method_name
266
+ name
267
+ end
268
+
269
+ def typify api_map
270
+ # @sg-ignore Need to add nil check here
271
+ logger.debug { "Method#typify(self=#{self}, binder=#{binder}, closure=#{closure}, context=#{context.rooted_tags}, return_type=#{return_type.rooted_tags}) - starting" }
272
+ decl = super
273
+ unless decl.undefined?
274
+ logger.debug { "Method#typify(self=#{self}, binder=#{binder}, closure=#{closure}, context=#{context}) => #{decl.rooted_tags.inspect} - decl found" }
275
+ return decl
276
+ end
277
+ type = see_reference(api_map) || typify_from_super(api_map)
278
+ logger.debug { "Method#typify(self=#{self}) - type=#{type&.rooted_tags.inspect}" }
279
+ unless type.nil?
280
+ # @sg-ignore Need to add nil check here
281
+ qualified = type.qualify(api_map, *closure.gates)
282
+ logger.debug { "Method#typify(self=#{self}) => #{qualified.rooted_tags.inspect}" }
283
+ return qualified
284
+ end
285
+ super
286
+ end
287
+
288
+ def documentation
289
+ if @documentation.nil?
290
+ method_docs ||= super || ''
291
+ param_tags = docstring.tags(:param)
292
+ unless param_tags.nil? or param_tags.empty?
293
+ method_docs += "\n\n" unless method_docs.empty?
294
+ method_docs += "Params:\n"
295
+ lines = []
296
+ param_tags.each do |p|
297
+ l = "* #{p.name}"
298
+ l += " [#{escape_brackets(p.types.join(', '))}]" unless p.types.nil? or p.types.empty?
299
+ l += " #{p.text}"
300
+ lines.push l
301
+ end
302
+ method_docs += lines.join("\n")
303
+ end
304
+ yieldparam_tags = docstring.tags(:yieldparam)
305
+ unless yieldparam_tags.nil? or yieldparam_tags.empty?
306
+ method_docs += "\n\n" unless method_docs.empty?
307
+ method_docs += "Block Params:\n"
308
+ lines = []
309
+ yieldparam_tags.each do |p|
310
+ l = "* #{p.name}"
311
+ l += " [#{escape_brackets(p.types.join(', '))}]" unless p.types.nil? or p.types.empty?
312
+ l += " #{p.text}"
313
+ lines.push l
314
+ end
315
+ method_docs += lines.join("\n")
316
+ end
317
+ yieldreturn_tags = docstring.tags(:yieldreturn)
318
+ unless yieldreturn_tags.empty?
319
+ method_docs += "\n\n" unless method_docs.empty?
320
+ method_docs += "Block Returns:\n"
321
+ lines = []
322
+ yieldreturn_tags.each do |r|
323
+ l = "*"
324
+ l += " [#{escape_brackets(r.types.join(', '))}]" unless r.types.nil? or r.types.empty?
325
+ l += " #{r.text}"
326
+ lines.push l
327
+ end
328
+ method_docs += lines.join("\n")
329
+ end
330
+ return_tags = docstring.tags(:return)
331
+ unless return_tags.empty?
332
+ method_docs += "\n\n" unless method_docs.empty?
333
+ method_docs += "Returns:\n"
334
+ lines = []
335
+ return_tags.each do |r|
336
+ l = "*"
337
+ l += " [#{escape_brackets(r.types.join(', '))}]" unless r.types.nil? or r.types.empty?
338
+ l += " #{r.text}"
339
+ lines.push l
340
+ end
341
+ method_docs += lines.join("\n")
342
+ end
343
+ method_docs += "\n\n" unless method_docs.empty?
344
+ method_docs += "Visibility: #{visibility}"
345
+ @documentation = method_docs
346
+ concat_example_tags
347
+ end
348
+ @documentation.to_s
349
+ end
350
+
351
+ def explicit?
352
+ @explicit
353
+ end
354
+
355
+ def attribute?
356
+ @attribute
357
+ end
358
+
359
+ # @parm other [self]
360
+ def nearly? other
361
+ super &&
362
+ # @sg-ignore https://github.com/castwide/solargraph/pull/1050
363
+ parameters == other.parameters &&
364
+ # @sg-ignore https://github.com/castwide/solargraph/pull/1050
365
+ scope == other.scope &&
366
+ # @sg-ignore https://github.com/castwide/solargraph/pull/1050
367
+ visibility == other.visibility
368
+ end
369
+
370
+ def probe api_map
371
+ attribute? ? infer_from_iv(api_map) : infer_from_return_nodes(api_map)
372
+ end
373
+
374
+ # @return [::Array<Pin::Signature>]
375
+ def overloads
376
+ # Ignore overload tags with nil parameters. If it's not an array, the
377
+ # tag's source is likely malformed.
378
+
379
+ # @param tag [YARD::Tags::OverloadTag]
380
+ @overloads ||= docstring.tags(:overload).select(&:parameters).map do |tag|
381
+ Pin::Signature.new(
382
+ generics: generics,
383
+ # @param src [Array(String, String)]
384
+ parameters: tag.parameters.map do |src|
385
+ name, decl = parse_overload_param(src.first)
386
+ Pin::Parameter.new(
387
+ location: location,
388
+ closure: self,
389
+ comments: tag.docstring.all.to_s,
390
+ name: name,
391
+ decl: decl,
392
+ # @sg-ignore flow sensitive typing needs to handle attrs
393
+ presence: location ? location.range : nil,
394
+ return_type: param_type_from_name(tag, src.first),
395
+ source: :overloads
396
+ )
397
+ end,
398
+ closure: self,
399
+ return_type: ComplexType.try_parse(*tag.docstring.tags(:return).flat_map(&:types)),
400
+ source: :overloads,
401
+ )
402
+ end
403
+ @overloads
404
+ end
405
+
406
+ def anon_splat?
407
+ @anon_splat
408
+ end
409
+
410
+ # @param api_map [ApiMap]
411
+ # @return [self]
412
+ def resolve_ref_tag api_map
413
+ return self if @resolved_ref_tag
414
+
415
+ @resolved_ref_tag = true
416
+ return self unless docstring.ref_tags.any?
417
+ docstring.ref_tags.each do |tag|
418
+ ref = if tag.owner.to_s.start_with?(/[#.]/)
419
+ api_map.get_methods(namespace)
420
+ .select { |pin| pin.path.end_with?(tag.owner.to_s) }
421
+ .first
422
+ else
423
+ # @todo Resolve relative namespaces
424
+ api_map.get_path_pins(tag.owner.to_s).first
425
+ end
426
+ next unless ref
427
+
428
+ docstring.add_tag(*ref.docstring.tags(:param))
429
+ end
430
+ self
431
+ end
432
+
433
+ # @param api_map [ApiMap]
434
+ # @return [Array<Pin::Method>]
435
+ def rest_of_stack api_map
436
+ api_map.get_method_stack(method_namespace, method_name, scope: scope).reject { |pin| pin.path == path }
437
+ end
438
+
439
+ protected
440
+
441
+ attr_writer :block
442
+
443
+ attr_writer :signature_help
444
+
445
+ attr_writer :documentation
446
+
447
+ # @sg-ignore Need to add nil check here
448
+ def dodgy_visibility_source?
449
+ # as of 2025-03-12, the RBS generator used for
450
+ # e.g. activesupport did not understand 'private' markings
451
+ # inside 'class << self' blocks, but YARD did OK at it
452
+ # @sg-ignore Need to add nil check here
453
+ source == :rbs && scope == :class && type_location&.filename&.include?('generated') && return_type.undefined? ||
454
+ # YARD's RBS generator seems to miss a lot of should-be protected instance methods
455
+ source == :rbs && scope == :instance && namespace.start_with?('YARD::') ||
456
+ # private on attr_readers seems to be broken in Prism's auto-generator script
457
+ source == :rbs && scope == :instance && namespace.start_with?('Prism::') ||
458
+ # The RBS for the RBS gem itself seems to use private as a
459
+ # 'is this a public API' concept, more aggressively than the
460
+ # actual code. Let's respect that and ignore the actual .rb file.
461
+ source == :yardoc && scope == :instance && namespace.start_with?('RBS::')
462
+ end
463
+
464
+ private
465
+
466
+ # @param other [Pin::Method]
467
+ # @return [Array<Pin::Signature>]
468
+ def combine_signatures(other)
469
+ all_undefined = signatures.all? { |sig| !sig.return_type&.defined? }
470
+ other_all_undefined = other.signatures.all? { |sig| !sig.return_type&.defined? }
471
+ if all_undefined && !other_all_undefined
472
+ other.signatures
473
+ elsif other_all_undefined && !all_undefined
474
+ signatures
475
+ else
476
+ combine_signatures_by_type_arity(*signatures, *other.signatures)
477
+ end
478
+ end
479
+
480
+ # @param signature_pins [Array<Pin::Signature>]
481
+ #
482
+ # @return [Array<Pin::Signature>]
483
+ def combine_signatures_by_type_arity(*signature_pins)
484
+ # @type [Hash{Array => Array<Pin::Signature>}]
485
+ by_type_arity = {}
486
+ signature_pins.each do |signature_pin|
487
+ by_type_arity[signature_pin.type_arity] ||= []
488
+ by_type_arity[signature_pin.type_arity] << signature_pin
489
+ end
490
+
491
+ by_type_arity.transform_values! do |same_type_arity_signatures|
492
+ combine_same_type_arity_signatures same_type_arity_signatures
493
+ end
494
+ by_type_arity.values.flatten
495
+ end
496
+
497
+ # @param same_type_arity_signatures [Array<Pin::Signature>]
498
+ #
499
+ # @return [Array<Pin::Signature>]
500
+ def combine_same_type_arity_signatures(same_type_arity_signatures)
501
+ # This is an O(n^2) operation, so bail out if n is not small
502
+ return same_type_arity_signatures if same_type_arity_signatures.length > 10
503
+
504
+ # @param old_signatures [Array<Pin::Signature>]
505
+ # @param new_signature [Pin::Signature]
506
+ same_type_arity_signatures.reduce([]) do |old_signatures, new_signature|
507
+ next [new_signature] if old_signatures.empty?
508
+
509
+ found_merge = false
510
+ old_signatures.flat_map do |old_signature|
511
+ potential_new_signature = old_signature.combine_with(new_signature)
512
+
513
+ if potential_new_signature.type_arity == old_signature.type_arity
514
+ # the number of types in each parameter and return type
515
+ # match, so we found compatible signatures to merge. If
516
+ # we increased the number of types, we'd potentially
517
+ # have taken away the ability to use parameter types to
518
+ # choose the correct return type (while Ruby doesn't
519
+ # dispatch based on type, RBS does distinguish overloads
520
+ # based on types, not just arity, allowing for type
521
+ # information describing how methods behave based on
522
+ # their input types)
523
+ old_signatures - [old_signature] + [potential_new_signature]
524
+ else
525
+ old_signatures + [new_signature]
526
+ end
527
+ end
528
+ end
529
+ end
530
+
531
+ # @param name [String]
532
+ # @param asgn [Boolean]
533
+ #
534
+ # @return [::Symbol]
535
+ def select_decl name, asgn
536
+ if name.start_with?('**')
537
+ :kwrestarg
538
+ elsif name.start_with?('*')
539
+ :restarg
540
+ elsif name.start_with?('&')
541
+ :blockarg
542
+ elsif name.end_with?(':') && asgn
543
+ :kwoptarg
544
+ elsif name.end_with?(':')
545
+ :kwarg
546
+ elsif asgn
547
+ :optarg
548
+ else
549
+ :arg
550
+ end
551
+ end
552
+
553
+ # @param name [String]
554
+ # @return [String]
555
+ def clean_param name
556
+ name.gsub(/[*&:]/, '')
557
+ end
558
+
559
+ # @param tag [YARD::Tags::OverloadTag]
560
+ # @param name [String]
561
+ #
562
+ # @return [ComplexType]
563
+ def param_type_from_name(tag, name)
564
+ # @param t [YARD::Tags::Tag]
565
+ param = tag.tags(:param).select { |t| t.name == name }.first
566
+ return ComplexType::UNDEFINED unless param
567
+ ComplexType.try_parse(*param.types)
568
+ end
569
+
570
+ # @return [ComplexType]
571
+ def generate_complex_type
572
+ tags = docstring.tags(:return).map(&:types).flatten.compact
573
+ return ComplexType::UNDEFINED if tags.empty?
574
+ ComplexType.try_parse *tags
575
+ end
576
+
577
+ # @param api_map [ApiMap]
578
+ # @return [ComplexType, ComplexType::UniqueType, nil]
579
+ def see_reference api_map
580
+ # This should actually be an intersection type
581
+ # @param ref [YARD::Tags::Tag, YARD::Tags::RefTag]
582
+ docstring.ref_tags.each do |ref|
583
+ # @sg-ignore ref should actually be an intersection type
584
+ next unless ref.tag_name == 'return' && ref.owner
585
+ # @sg-ignore should actually be an intersection type
586
+ result = resolve_reference(ref.owner.to_s, api_map)
587
+ return result unless result.nil?
588
+ end
589
+ match = comments.match(/^[ \t]*\(see (.*)\)/m)
590
+ return nil if match.nil?
591
+ # @sg-ignore Need to add nil check here
592
+ resolve_reference match[1], api_map
593
+ end
594
+
595
+ # @return [String]
596
+ def method_namespace
597
+ namespace
598
+ end
599
+
600
+ # @param api_map [ApiMap]
601
+ # @return [ComplexType, nil]
602
+ def typify_from_super api_map
603
+ stack = rest_of_stack api_map
604
+ return nil if stack.empty?
605
+ stack.each do |pin|
606
+ # @sg-ignore Need to add nil check here
607
+ return pin.return_type unless pin.return_type.undefined?
608
+ end
609
+ nil
610
+ end
611
+
612
+ # @param ref [String]
613
+ # @param api_map [ApiMap]
614
+ # @return [ComplexType, ComplexType::UniqueType, nil]
615
+ def resolve_reference ref, api_map
616
+ parts = ref.split(/[.#]/)
617
+ if parts.first.empty? || parts.one?
618
+ path = "#{namespace}#{ref}"
619
+ else
620
+ fqns = api_map.qualify(parts.first, *gates)
621
+ return ComplexType::UNDEFINED if fqns.nil?
622
+ # @sg-ignore Need to add nil check here
623
+ path = fqns + ref[parts.first.length] + parts.last
624
+ end
625
+ pins = api_map.get_path_pins(path)
626
+ pins.each do |pin|
627
+ type = pin.typify(api_map)
628
+ return type unless type.undefined?
629
+ end
630
+ nil
631
+ end
632
+
633
+ # @return [Parser::AST::Node, nil]
634
+ def method_body_node
635
+ return nil if node.nil?
636
+ return node.children[1].children.last if node.type == :DEFN
637
+ return node.children[2].children.last if node.type == :DEFS
638
+ return node.children[2] if node.type == :def || node.type == :DEFS
639
+ return node.children[3] if node.type == :defs
640
+ nil
641
+ end
642
+
643
+ # @param api_map [ApiMap]
644
+ # @return [ComplexType]
645
+ def infer_from_return_nodes api_map
646
+ return ComplexType::UNDEFINED if node.nil?
647
+ result = []
648
+ has_nil = false
649
+ return ComplexType::NIL if method_body_node.nil?
650
+ returns_from_method_body(method_body_node).each do |n|
651
+ if n.nil? || [:NIL, :nil].include?(n.type)
652
+ has_nil = true
653
+ next
654
+ end
655
+ rng = Range.from_node(n)
656
+ next unless rng
657
+ clip = api_map.clip_at(
658
+ # @sg-ignore Need to add nil check here
659
+ location.filename,
660
+ rng.ending
661
+ )
662
+ # @sg-ignore Need to add nil check here
663
+ chain = Solargraph::Parser.chain(n, location.filename)
664
+ type = chain.infer(api_map, self, clip.locals)
665
+ result.push type unless type.undefined?
666
+ end
667
+ result.push ComplexType::NIL if has_nil
668
+ return ComplexType::UNDEFINED if result.empty?
669
+ ComplexType.new(result.uniq)
670
+ end
671
+
672
+ # @param [ApiMap] api_map
673
+ # @return [ComplexType]
674
+ def infer_from_iv api_map
675
+ types = []
676
+ varname = "@#{name.gsub(/=$/, '')}"
677
+ pins = api_map.get_instance_variable_pins(binder.namespace, binder.scope).select { |iv| iv.name == varname }
678
+ pins.each do |pin|
679
+ type = pin.typify(api_map)
680
+ type = pin.probe(api_map) if type.undefined?
681
+ types.push type if type.defined?
682
+ end
683
+ return ComplexType::UNDEFINED if types.empty?
684
+ ComplexType.new(types.uniq)
685
+ end
686
+
687
+ # When YARD parses an overload tag, it includes rest modifiers in the parameters names.
688
+ #
689
+ # @param name [String]
690
+ # @return [::Array(String, ::Symbol)]
691
+ def parse_overload_param(name)
692
+ # @todo this needs to handle mandatory vs not args, kwargs, blocks, etc
693
+ if name.start_with?('**')
694
+ [name[2..-1], :kwrestarg]
695
+ elsif name.start_with?('*')
696
+ [name[1..-1], :restarg]
697
+ else
698
+ [name, :arg]
699
+ end
700
+ end
701
+
702
+ # @return [void]
703
+ def concat_example_tags
704
+ example_tags = docstring.tags(:example)
705
+ return if example_tags.empty?
706
+ @documentation += "\n\nExamples:\n\n```ruby\n"
707
+ @documentation += example_tags.map do |tag|
708
+ (tag.name && !tag.name.empty? ? "# #{tag.name}\n" : '') +
709
+ "#{tag.text}\n"
710
+ end
711
+ .join("\n")
712
+ .concat("```\n")
713
+ end
714
+
715
+ protected
716
+
717
+ attr_writer :return_type
718
+ end
719
+ end
720
+ end