solargraph 0.59.2 → 0.60.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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rspec.yml +4 -1
  3. data/.rubocop.yml +1 -0
  4. data/.rubocop_todo.yml +1 -1
  5. data/CHANGELOG.md +11 -0
  6. data/Gemfile +3 -0
  7. data/lib/solargraph/api_map/index.rb +13 -2
  8. data/lib/solargraph/api_map/store.rb +21 -6
  9. data/lib/solargraph/api_map.rb +34 -2
  10. data/lib/solargraph/complex_type/unique_type.rb +4 -0
  11. data/lib/solargraph/complex_type.rb +4 -0
  12. data/lib/solargraph/doc_map.rb +1 -0
  13. data/lib/solargraph/parser/parser_gem/node_methods.rb +42 -0
  14. data/lib/solargraph/parser/parser_gem/node_processors/namespace_node.rb +29 -5
  15. data/lib/solargraph/pin/base.rb +31 -3
  16. data/lib/solargraph/pin/callable.rb +2 -2
  17. data/lib/solargraph/pin/common.rb +12 -0
  18. data/lib/solargraph/pin/method.rb +56 -16
  19. data/lib/solargraph/rbs_map/conversions.rb +96 -145
  20. data/lib/solargraph/rbs_translator.rb +206 -0
  21. data/lib/solargraph/shell.rb +130 -63
  22. data/lib/solargraph/source/chain/call.rb +8 -76
  23. data/lib/solargraph/source_map/mapper.rb +5 -135
  24. data/lib/solargraph/source_map.rb +14 -0
  25. data/lib/solargraph/version.rb +19 -1
  26. data/lib/solargraph/yard_map/directives/attribute_directive.rb +65 -0
  27. data/lib/solargraph/yard_map/directives/domain_directive.rb +30 -0
  28. data/lib/solargraph/yard_map/directives/method_directive.rb +51 -0
  29. data/lib/solargraph/yard_map/directives/override_directive.rb +30 -0
  30. data/lib/solargraph/yard_map/directives/parse_directive.rb +53 -0
  31. data/lib/solargraph/yard_map/directives/visibility_directive.rb +70 -0
  32. data/lib/solargraph/yard_map/directives.rb +35 -0
  33. data/lib/solargraph/yard_map/macro.rb +113 -0
  34. data/lib/solargraph/yard_map/mapper.rb +19 -1
  35. data/lib/solargraph/yard_map.rb +2 -0
  36. data/lib/solargraph.rb +1 -0
  37. data/solargraph.gemspec +1 -0
  38. metadata +24 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d71c58a0d918c5c6c034e7011a8bf543e0bc4adc5046936fa57cc4f698ccda05
4
- data.tar.gz: c16b441272fb5aa6f908fdf643d524ff3e5f955dc8b636fd7bf7927836085047
3
+ metadata.gz: e3eff86e8759fbaaa9526cf9cceb70827c701f3b1925d58e1575870989d60e60
4
+ data.tar.gz: a60b5c8e9598afdca808c94fa68e2551d17f970b812ca64c3f00d530d0498fff
5
5
  SHA512:
6
- metadata.gz: e7f83cd6982410d73623b44dd7b69bb5513266f778aeb33574b30155ccea7a4f53d9649150df2685efee70bdc5ae8227bd02ccdabbf9b633b66809be7c838307
7
- data.tar.gz: 83b5822ecf5528f3196e1fbd283ac474d27cbc603f2bb81b8d8bff1b0b99d32023fec6e50b691bb390e5c8873875376d28f182ccb12498513f34a0fa4cb575e8
6
+ metadata.gz: 201861185888f275b0c7690887cca60a76d0bb90955c514ba4336e23f2adb9907d305f5e930b08f03c3c388d81bfd40996cd54c0f854035aab5a60703c40f954
7
+ data.tar.gz: b37cc2ed827560a2365b77af0921c0240498dcb287e1a1ba064f0879a748c2d365f0e61cec610233b11521206ab402e83aebbdec5c897a21eb3e9f7313fba602
@@ -21,7 +21,10 @@ jobs:
21
21
  runs-on: ubuntu-latest
22
22
  strategy:
23
23
  matrix:
24
- ruby-version: ['3.1', '3.2', '3.3', '3.4', '4.0', 'head']
24
+ # @todo Restore 'head' once ruby/setup-ruby publishes it for ubuntu-24.04.
25
+ # It currently 404s ("Unavailable version head for ruby"), failing CI:
26
+ # https://github.com/castwide/solargraph/actions/runs/25863741955/job/76000137015?pr=1187
27
+ ruby-version: ['3.1', '3.2', '3.3', '3.4', '4.0']
25
28
  rbs-version: ['3.10.0', '4.0.0', '4.0.1', '4.0.2']
26
29
  exclude:
27
30
  - ruby-version: '3.1'
data/.rubocop.yml CHANGED
@@ -17,6 +17,7 @@ AllCops:
17
17
  - "spec/fixtures/invalid_byte.rb"
18
18
  - "spec/fixtures/invalid_node_comment.rb"
19
19
  - "spec/fixtures/invalid_utf8.rb"
20
+ - "spec/fixtures/gem-with-yard-macros/**/*"
20
21
  - "vendor/**/*"
21
22
  - "vendor/**/.*"
22
23
  TargetRubyVersion: 3.1
data/.rubocop_todo.yml CHANGED
@@ -118,7 +118,7 @@ Metrics/MethodLength:
118
118
 
119
119
  # Configuration parameters: CountComments, CountAsOne.
120
120
  Metrics/ModuleLength:
121
- Max: 167
121
+ Max: 195
122
122
 
123
123
  # Configuration parameters: Max, CountKeywordArgs, MaxOptionalParameters.
124
124
  Metrics/ParameterLists:
data/CHANGELOG.md CHANGED
@@ -1,3 +1,14 @@
1
+ ## 0.60.0 - June 15, 2026
2
+ - Add YARD macro support for DSL methods (#1187)
3
+ - YARD macros - More typecheck fixes (#1188)
4
+ - Macro fixes (#1189)
5
+ - Unused macro methods (#1191)
6
+ - Transitive macros (#1203)
7
+ - Allow CTRL-C interruption of profile command + macro debug logs (#1206)
8
+ - Support for Inline RBS (#1173)
9
+ - Make typechecker errors vim quickfix friendly (#1072)
10
+ - Generate RBS (#812)
11
+
1
12
  ## 0.59.2 - May 22, 2026
2
13
  - Convert RBS implicit nil annotations (#1197)
3
14
  - Temporary job stubs (#1200)
data/Gemfile CHANGED
@@ -4,6 +4,9 @@ source 'https://rubygems.org'
4
4
 
5
5
  gemspec name: 'solargraph'
6
6
 
7
+ # Test fixture gems
8
+ gem 'gem-with-yard-macros', path: 'spec/fixtures/gem-with-yard-macros'
9
+
7
10
  # Local gemfile for development tools, etc.
8
11
  local_gemfile = File.expand_path('.Gemfile', __dir__)
9
12
  instance_eval File.read local_gemfile if File.exist? local_gemfile
@@ -5,6 +5,12 @@ module Solargraph
5
5
  class Index
6
6
  include Logging
7
7
 
8
+ # @return [Array<String>]
9
+ attr_reader :macro_method_names
10
+
11
+ # @return [Hash{String => Array<Pin::Method>}]
12
+ attr_reader :macro_method_name_pins
13
+
8
14
  # @param pins [Array<Pin::Base>]
9
15
  def initialize pins = []
10
16
  catalog pins
@@ -95,16 +101,18 @@ module Solargraph
95
101
  protected
96
102
 
97
103
  attr_writer :pins, :pin_select_cache, :namespace_hash, :pin_class_hash, :path_pin_hash, :include_references,
98
- :extend_references, :prepend_references, :superclass_references
104
+ :extend_references, :prepend_references, :superclass_references, :macro_method_names,
105
+ :macro_method_name_pins
99
106
 
100
107
  # @return [self]
101
108
  def deep_clone
102
109
  Index.allocate.tap do |copy|
103
110
  copy.pin_select_cache = {}
104
111
  copy.pins = pins.clone
112
+ copy.macro_method_names = macro_method_names
105
113
  %i[
106
114
  namespace_hash pin_class_hash path_pin_hash include_references extend_references prepend_references
107
- superclass_references
115
+ superclass_references macro_method_name_pins
108
116
  ].each do |sym|
109
117
  copy.send("#{sym}=", send(sym).clone)
110
118
  copy.send(sym)&.transform_values!(&:clone)
@@ -137,6 +145,9 @@ module Solargraph
137
145
  map_references Pin::Reference::Prepend, prepend_references
138
146
  map_references Pin::Reference::Extend, extend_references
139
147
  map_references Pin::Reference::Superclass, superclass_references
148
+ macro_pins = pins_by_class(Pin::Method).select { |pin| pin.macros.any? }
149
+ @macro_method_names = macro_pins.to_set(&:name)
150
+ @macro_method_name_pins = macro_pins.to_set.classify(&:name)
140
151
  map_overrides
141
152
  pins_by_class(Pin::Reference::TypeAlias).each { |pin| alias_hash[pin.name] = pin.return_type }
142
153
  self
@@ -21,11 +21,11 @@ module Solargraph
21
21
  # - pinsets[0] = core Ruby pins
22
22
  # - pinsets[1] = documentation/gem pins
23
23
  # - pinsets[2] = convention pins
24
- # - pinsets[3] = workspace source pins
24
+ # - pinsets[3] = workspace source pins (aka. "iced_pins")
25
25
  # - pinsets[4] = currently open file pins
26
26
  # @return [Boolean] True if the index was updated
27
- def update *pinsets
28
- return catalog(pinsets) if pinsets.length != @pinsets.length
27
+ def update *pinsets, &block
28
+ return catalog(pinsets, &block) if pinsets.length != @pinsets.length
29
29
 
30
30
  changed = pinsets.find_index.with_index { |pinset, idx| @pinsets[idx] != pinset }
31
31
  return false unless changed
@@ -43,6 +43,9 @@ module Solargraph
43
43
  @indexes[changed + idx - 1].merge(pins)
44
44
  end
45
45
  end
46
+ # @type [Index]
47
+ @index = @indexes.last.clone
48
+ @index = @index.merge(block.call) if block
46
49
  constants.clear
47
50
  cached_qualify_superclass.clear
48
51
  true
@@ -174,7 +177,7 @@ module Solargraph
174
177
  result
175
178
  end
176
179
 
177
- # @return [Hash{String => YARD::Tags::MacroDirective}]
180
+ # @return [Hash{String => YardMap::Macro}]
178
181
  def named_macros
179
182
  @named_macros ||= begin
180
183
  result = {}
@@ -276,17 +279,27 @@ module Solargraph
276
279
  index.alias_hash[name]
277
280
  end
278
281
 
282
+ # @return [Array<String>]
283
+ def macro_method_names
284
+ index.macro_method_names
285
+ end
286
+
287
+ # @return [Hash{String => Array<Pin::Method>}]
288
+ def macro_method_name_pins
289
+ index.macro_method_name_pins
290
+ end
291
+
279
292
  private
280
293
 
281
294
  # @return [Index]
282
295
  def index
283
- @indexes.last
296
+ @index ||= Index.new
284
297
  end
285
298
 
286
299
  # @param pinsets [Array<Array<Pin::Base>>]
287
300
  #
288
301
  # @return [true]
289
- def catalog pinsets
302
+ def catalog pinsets, &block
290
303
  @pinsets = pinsets
291
304
  # @type [Array<Index>]
292
305
  @indexes = []
@@ -297,6 +310,8 @@ module Solargraph
297
310
  @indexes.push(@indexes.last&.merge(pins) || Solargraph::ApiMap::Index.new(pins))
298
311
  end
299
312
  end
313
+ @index = @indexes.last.clone
314
+ @index = @index.merge(block.call) if block
300
315
  constants.clear
301
316
  cached_qualify_superclass.clear
302
317
  true
@@ -107,6 +107,7 @@ module Solargraph
107
107
  # @return [self]
108
108
  def catalog bench
109
109
  @source_map_hash = bench.source_map_hash
110
+ # @type [Array<Pin::Base>]
110
111
  iced_pins = bench.icebox.flat_map(&:pins)
111
112
  live_pins = bench.live_map&.all_pins || []
112
113
  conventions_environ.clear
@@ -123,11 +124,42 @@ module Solargraph
123
124
  @doc_map = DocMap.new(unresolved_requires, bench.workspace, out: nil) # @todo Implement gem preferences
124
125
  @unresolved_requires = @doc_map.unresolved_requires
125
126
  end
126
- @cache.clear if store.update(@@core_map.pins, @doc_map.pins, conventions_environ.pins, iced_pins, live_pins)
127
+ start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
128
+ Solargraph.logger.info 'Cataloging ApiMap started'
129
+ @cache.clear if store.update(@@core_map.pins, @doc_map.pins, conventions_environ.pins, iced_pins, live_pins) { process_macros }
127
130
  @missing_docs = [] # @todo Implement missing docs
131
+ Solargraph.logger.info "Cataloging ApiMap finished in #{Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time} seconds"
128
132
  self
129
133
  end
130
134
 
135
+ # @return [Array<Pin::Base>]
136
+ def process_macros
137
+ macro_pins = []
138
+ Solargraph.logger.debug { "ApiMap#process_macros: processing macros for #{source_maps.size} source maps" }
139
+ Solargraph.logger.debug { "ApiMap#process_macros: store has #{store.macro_method_name_pins.size} macro method name pins" }
140
+ Solargraph.logger.debug { "ApiMap#process_macros: named macros: #{store.named_macros.keys.join(', ')}" }
141
+ source_maps.each do |source_map|
142
+ method_candidates = source_map.macro_method_candidates(store.macro_method_names)
143
+ Solargraph.logger.debug { "ApiMap#process_macros: processing source map for #{source_map.filename} with #{method_candidates.size} macro method candidates" }
144
+ method_candidates.each do |node|
145
+ closure = source_map.locate_closure_pin(node.location.line, node.location.column)
146
+ chain = Solargraph::Parser::ParserGem::NodeChainer.chain(node)
147
+ if node.children[0].nil? && store.macro_method_name_pins.key?(node.children[1].to_s)
148
+ match = store.macro_method_name_pins[node.children[1].to_s].find do |pin|
149
+ super_and_sub?(pin.namespace, closure.name)
150
+ end
151
+ if match
152
+ match.macros.each do |macro|
153
+ macro_pins.concat macro.generate_pins_from(chain, match, source_map)
154
+ end
155
+ next
156
+ end
157
+ end
158
+ end
159
+ end
160
+ macro_pins
161
+ end
162
+
131
163
  # @return [DocMap]
132
164
  def doc_map
133
165
  @doc_map ||= DocMap.new([], Workspace.new('.'))
@@ -154,7 +186,7 @@ module Solargraph
154
186
  end
155
187
 
156
188
  # @param name [String, nil]
157
- # @return [YARD::Tags::MacroDirective, nil]
189
+ # @return [Solargraph::YardMap::Macro, nil]
158
190
  def named_macro name
159
191
  # @sg-ignore Need to add nil check here
160
192
  store.named_macros[name]
@@ -547,6 +547,10 @@ module Solargraph
547
547
  yield new_type
548
548
  end
549
549
 
550
+ def expand named_types
551
+ named_types[name] || self
552
+ end
553
+
550
554
  # Generate a ComplexType that fully qualifies this type's namespaces.
551
555
  #
552
556
  # @param api_map [ApiMap] The ApiMap that performs qualification
@@ -298,6 +298,10 @@ module Solargraph
298
298
  ComplexType.new(map { |ut| ut.transform(new_name, &transform_type) })
299
299
  end
300
300
 
301
+ def expand named_types
302
+ ComplexType.new(map { |ut| ut.expand(named_types) })
303
+ end
304
+
301
305
  # @return [self]
302
306
  def force_rooted
303
307
  transform do |t|
@@ -317,6 +317,7 @@ module Solargraph
317
317
  gemspec = Gem::Specification.find_by_path(path)
318
318
  if gemspec.nil?
319
319
  gem_name_guess = path.split('/').first
320
+ return nil if gem_name_guess.to_s.empty?
320
321
  begin
321
322
  # this can happen when the gem is included via a local path in
322
323
  # a Gemfile; Gem doesn't try to index the paths in that case.
@@ -102,6 +102,48 @@ module Solargraph
102
102
  signature
103
103
  end
104
104
 
105
+ # Convert a DSL method call argument with directly inferrable simple params.
106
+ # @param node [Parser::AST::Node]
107
+ # @return [String, Integer, Float, Symbol, Array, Hash, Source::Chain, nil]
108
+ # @sg-ignore "does not match inferred type ::String, ::Parser::AST::Node" - this probably comes from the
109
+ # `.children[0]` call, which is not recognized as returning a literal value.
110
+ def simple_convert node
111
+ return nil unless Parser.is_ast_node?(node)
112
+
113
+ case node.type
114
+ when :const
115
+ unpack_name(node)
116
+ when :str, :dstr, :int, :float, :sym, true, false
117
+ node.children[0]
118
+ when :array
119
+ simple_convert_array(node)
120
+ when :hash
121
+ simple_convert_hash(node)
122
+ else
123
+ Solargraph::Parser.chain(node)
124
+ end
125
+ end
126
+
127
+ # @param node [Parser::AST::Node]
128
+ # @return [Array]
129
+ def simple_convert_array node
130
+ return [] unless Parser.is_ast_node?(node) && node.type == :array
131
+ node.children.compact.map { |c| simple_convert(c) }
132
+ end
133
+
134
+ # @param node [Parser::AST::Node]
135
+ # @return [Hash]
136
+ def simple_convert_hash node
137
+ return {} unless Parser.is_ast_node?(node) && node.type == :hash
138
+ result = {}
139
+ # @param pair [Parser::AST::Node]
140
+ node.children.each do |pair|
141
+ next unless Parser.is_ast_node?(pair) && pair.children[0]
142
+ result[pair.children[0].children[0]] = simple_convert(pair.children[1])
143
+ end
144
+ result
145
+ end
146
+
105
147
  # @param node [Parser::AST::Node, nil]
106
148
  # @return [Hash{Symbol => Chain}]
107
149
  def convert_hash node
@@ -8,22 +8,27 @@ module Solargraph
8
8
  include ParserGem::NodeMethods
9
9
 
10
10
  def process
11
- superclass_name = nil
12
- superclass_name = unpack_name(node.children[1]) if node.type == :class && node.children[1]&.type == :const
11
+ name = unpack_name(node.children[0])
12
+ comments = comments_for(node)
13
+
14
+ superclass_name = if node.type == :class && node.children[1]&.type == :const
15
+ "#{type_from_node}#{parameters_from_inline_rbs}"
16
+ end
13
17
 
14
18
  loc = get_node_location(node)
15
19
  nspin = Solargraph::Pin::Namespace.new(
16
20
  type: node.type,
17
21
  location: loc,
18
22
  closure: region.closure,
19
- name: unpack_name(node.children[0]),
20
- comments: comments_for(node),
23
+ name: name,
24
+ comments: comments,
21
25
  visibility: :public,
22
26
  gates: region.closure.gates.freeze,
23
27
  source: :parser
24
28
  )
25
29
  pins.push nspin
26
- unless superclass_name.nil?
30
+ Solargraph.logger.warn "Superclass: #{superclass_name}" if superclass_name&.start_with?('Array')
31
+ if superclass_name
27
32
  pins.push Pin::Reference::Superclass.new(
28
33
  location: loc,
29
34
  closure: pins.last,
@@ -33,6 +38,25 @@ module Solargraph
33
38
  end
34
39
  process_children region.update(closure: nspin, visibility: :public)
35
40
  end
41
+
42
+ private
43
+
44
+ # @param comments [String]
45
+ # @return [String, nil]
46
+ def parameters_from_inline_rbs
47
+ source = region.source.code_for(node)
48
+ match = source.match(/[^\n]*?#\s?+\[([^\]]*)/)
49
+ return unless match && match[1]
50
+
51
+ code = match[1].strip
52
+ return if code.empty?
53
+
54
+ "<#{code}>"
55
+ end
56
+
57
+ def type_from_node
58
+ unpack_name(node.children[1]) if node.children[1]&.type == :const
59
+ end
36
60
  end
37
61
  end
38
62
  end
@@ -534,11 +534,20 @@ module Solargraph
534
534
  @directives
535
535
  end
536
536
 
537
- # @return [::Array<YARD::Tags::MacroDirective>]
537
+ # @return [::Array<Solargraph::YardMap::Macro>]
538
538
  def macros
539
539
  @macros ||= collect_macros
540
540
  end
541
541
 
542
+ def macro_names
543
+ parse_comments unless @macro_names
544
+ @macro_names ||= collect_macro_names
545
+ end
546
+
547
+ def macro_names?
548
+ macro_names.any?
549
+ end
550
+
542
551
  # Perform a quick check to see if this pin possibly includes YARD
543
552
  # directives. This method does not require parsing the comments.
544
553
  #
@@ -569,6 +578,15 @@ module Solargraph
569
578
  return_type.qualify(api_map, *(closure&.gates || ['']))
570
579
  end
571
580
 
581
+ # @todo Candidate for deprecation (see ApiMap#process_macros)
582
+ def maybe_macros?
583
+ comments.include?('@macro')
584
+ end
585
+
586
+ def macros_names?
587
+ macro_names.any?
588
+ end
589
+
572
590
  # Infer the pin's return type via static code analysis.
573
591
  #
574
592
  # @param api_map [ApiMap]
@@ -619,6 +637,8 @@ module Solargraph
619
637
  result = dup
620
638
  result.return_type = return_type
621
639
  result.proxied = true
640
+ # Macros should have been processed already
641
+ result.macro_names.clear
622
642
  result
623
643
  end
624
644
 
@@ -717,12 +737,14 @@ module Solargraph
717
737
  if comments.nil? || comments.empty? || comments.strip.end_with?('@overload')
718
738
  @docstring = nil
719
739
  @directives = []
740
+ @macro_names = []
720
741
  else
721
742
  # HACK: Pass a dummy code object to the parser for plugins that
722
743
  # expect it not to be nil
723
744
  parse = Solargraph::Source.parse_docstring(comments)
724
745
  @docstring = parse.to_docstring
725
746
  @directives = parse.directives
747
+ @macro_names = collect_macro_names
726
748
  end
727
749
  end
728
750
 
@@ -762,11 +784,17 @@ module Solargraph
762
784
  tag1.types == tag2.types
763
785
  end
764
786
 
765
- # @return [::Array<YARD::Tags::Handlers::Directive>]
787
+ # @return [::Array<Solargraph::YardMap::Macro>]
766
788
  def collect_macros
767
789
  return [] unless maybe_directives?
768
790
  parse = Solargraph::Source.parse_docstring(comments)
769
- parse.directives.select { |d| d.tag.tag_name == 'macro' }
791
+ parse.directives.select { |d| d.tag.tag_name == 'macro' }.map do |macro_directive|
792
+ Solargraph::YardMap::Macro.from_directive macro_directive, self
793
+ end
794
+ end
795
+
796
+ def collect_macro_names
797
+ "#{comments}\n".scan(/\s*?@macro +(\S+).*?[\n]/).map { |match| match[0] }
770
798
  end
771
799
  end
772
800
  end
@@ -161,8 +161,8 @@ module Solargraph
161
161
  end
162
162
 
163
163
  def typify api_map
164
- type = super
165
- return type if type.defined?
164
+ type = return_type
165
+ return type.qualify(api_map, *gates) if type.defined?
166
166
  if method_name.end_with?('?')
167
167
  logger.debug { "Callable#typify(self=#{self}) => Boolean (? suffix)" }
168
168
  ComplexType::BOOLEAN
@@ -72,6 +72,18 @@ module Solargraph
72
72
  @path ||= name.empty? ? context.namespace : "#{context.namespace}::#{name}"
73
73
  end
74
74
 
75
+ # @yieldparam [Pin::Base]
76
+ # @return [void, Enumerator<Pin::Base>]
77
+ def each_closure &block
78
+ return enum_for(:each_closure) unless block_given?
79
+
80
+ here = closure
81
+ until here.nil?
82
+ yield here
83
+ here = here&.closure
84
+ end
85
+ end
86
+
75
87
  protected
76
88
 
77
89
  attr_writer :context
@@ -143,7 +143,7 @@ module Solargraph
143
143
  end
144
144
 
145
145
  def return_type
146
- @return_type ||= ComplexType.new(signatures.map(&:return_type).flat_map(&:items))
146
+ @return_type ||= return_type_from_inline_rbs || ComplexType.new(signatures.map(&:return_type).flat_map(&:items))
147
147
  end
148
148
 
149
149
  # @param parameters [::Array<Parameter>]
@@ -188,18 +188,11 @@ module Solargraph
188
188
 
189
189
  # @return [::Array<Signature>]
190
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
191
+ @signatures ||= if inline_rbs.empty?
192
+ signatures_from_yard
193
+ else
194
+ signatures_from_inline_rbs
195
+ end
203
196
  end
204
197
 
205
198
  # @param return_type [ComplexType]
@@ -279,7 +272,19 @@ module Solargraph
279
272
  # @sg-ignore Need to add nil check here
280
273
  "Method#typify(self=#{self}, binder=#{binder}, closure=#{closure}, context=#{context.rooted_tags}, return_type=#{return_type.rooted_tags}) - starting"
281
274
  end
282
- decl = super
275
+ decl = if macro_names?
276
+ types = macro_names.flat_map do |mac|
277
+ directive = api_map.named_macro(mac)
278
+ next unless directive
279
+ macro = Solargraph::YardMap::Macro.from_directive(directive, self)
280
+ expanded = macro.macro_object.expand([name, *parameter_names])
281
+ docstring = Solargraph::Source.parse_docstring(expanded).to_docstring
282
+ docstring.tags(:return).flat_map(&:types)
283
+ end
284
+ ComplexType.try_parse(*types)
285
+ else
286
+ super
287
+ end
283
288
  unless decl.undefined?
284
289
  logger.debug do
285
290
  "Method#typify(self=#{self}, binder=#{binder}, closure=#{closure}, context=#{context}) => #{decl.rooted_tags.inspect} - decl found"
@@ -451,7 +456,6 @@ module Solargraph
451
456
 
452
457
  attr_writer :block, :signature_help, :documentation, :return_type
453
458
 
454
- # @sg-ignore Need to add nil check here
455
459
  def dodgy_visibility_source?
456
460
  # as of 2025-03-12, the RBS generator used for
457
461
  # e.g. activesupport did not understand 'private' markings
@@ -505,7 +509,8 @@ module Solargraph
505
509
  #
506
510
  # @return [Array<Pin::Signature>]
507
511
  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
512
+ # @todo Stubbing this method while we debug an infinite loop bug in Ruby 3.x.
513
+ # The body below is intentionally preserved for when the stub is removed.
509
514
  return same_type_arity_signatures
510
515
 
511
516
  # rubocop:disable Lint/UnreachableCode
@@ -721,6 +726,41 @@ module Solargraph
721
726
  .join("\n")
722
727
  .concat("```\n")
723
728
  end
729
+
730
+ # @return [ComplexType, nil]
731
+ def return_type_from_inline_rbs
732
+ return nil if inline_rbs.empty?
733
+ method_type = RBS::Parser.parse_method_type(inline_rbs)
734
+ RbsTranslator.to_complex_type(method_type.type.return_type)
735
+ rescue RBS::ParsingError
736
+ nil
737
+ end
738
+
739
+ # @return [Array<Pin::Signature>]
740
+ def signatures_from_inline_rbs
741
+ method_type = RBS::Parser.parse_method_type(inline_rbs)
742
+ [RbsTranslator.to_signature(method_type, self, parameter_names)]
743
+ rescue RBS::ParsingError
744
+ signatures_from_yard
745
+ end
746
+
747
+ # @return [Array<Pin::Signature>]
748
+ def signatures_from_yard
749
+ top_type = generate_complex_type
750
+ result = []
751
+ result.push generate_signature(parameters, top_type) if top_type.defined?
752
+ result.concat(overloads.map { |meth| generate_signature(meth.parameters, meth.return_type) }) unless overloads.empty?
753
+ result.push generate_signature(parameters, @return_type || ComplexType::UNDEFINED) if result.empty?
754
+ result
755
+ end
756
+
757
+ # @return [String]
758
+ def inline_rbs
759
+ comments.lines
760
+ .select { |line| line.start_with?(': ') }
761
+ .map { |line| line[2..].strip }
762
+ .join("\n")
763
+ end
724
764
  end
725
765
  end
726
766
  end