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