solargraph 0.60.3 → 0.60.4

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0a62c1ebc82583e5c0ee905b75e3f065a1c6185c8268e7b529115fe445afb1a6
4
- data.tar.gz: 8b4cc8722f4eb6b3aecb58aaec22d2db54c49a3b2f6477bc7019331de86fbfc7
3
+ metadata.gz: 7c7397d5a9bbee07b12baa9372ce14f61216e5a310aa0f68605acabf8a9430e2
4
+ data.tar.gz: aeecf5c5d1ea7b1040fd3ae4bd9d1bff1685e468bd92b30dedfdd8ded2f1cd04
5
5
  SHA512:
6
- metadata.gz: 8882c0dffd4a6ac9c4d25c53fdb1789840951fa00860120a25237353c9aa4b2c3454bf04cd94921e56f52871a97978f3ea5b4ed38fba34841d8395eb1223a650
7
- data.tar.gz: 5ec51b9ab0cb3d3f445e3282c0c132641a7c9bf52643598a4476ddc5eb23d5527442f0bd434f772b9fcc94a437a956b38607f13d9dc74752df69fae75017963b
6
+ metadata.gz: f4abae37c4fc8b57efe4a7b3550a2edbfc64455e8852f81660220f4a67facf101eafd058805b58cf0be6f9d5b9d16317cd21f6cd3b47f0c666c48418ee0d54c2
7
+ data.tar.gz: e14586769600dd63e2a96b8bcb4a5e4aa9bb045948e87e8c06b6606e711fed6816347051505eb756df55fed375a0419431dd02a44c2082d6d1ec0e2b13c6564f
@@ -129,12 +129,8 @@ jobs:
129
129
  - name: clone https://github.com/lekemula/solargraph-rspec/
130
130
  run: |
131
131
  cd ..
132
- # git clone https://github.com/lekemula/solargraph-rspec.git
133
-
134
- # pending https://github.com/lekemula/solargraph-rspec/pull/31
135
- git clone https://github.com/apiology/solargraph-rspec.git
132
+ git clone https://github.com/lekemula/solargraph-rspec.git
136
133
  cd solargraph-rspec
137
- git checkout test_solargraph_prereleases
138
134
  - name: Set up Ruby
139
135
  uses: ruby/setup-ruby@v1
140
136
  with:
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## 0.60.4 - August 30, 2026
2
+ - Fix @yieldparam type lost on multi-overload block-form methods (#1289) (#1290)
3
+ - Read inline RBS superclass params only on the class line (#1301)
4
+ - Stop recording YARD's implicit Object superclass (#1315)
5
+ - Improve overload resolution and macro handling in Chain::Call (#1247)
6
+ - Fix Chain#nullable? leaking nil from earlier &. into later calls (#1271)
7
+ - Handle YARD bug with open singleton classes (#1279)
8
+ - Fix duck_types_match? to check the inferred type's own duck interface (#1295)
9
+ - Use the original repo (run_solargraph_rspec_specs job) (#1291)
10
+
1
11
  ## 0.60.3 - August 5, 2026
2
12
  - Pin rdoc to ~> 7.0 (#1216)
3
13
  - (tag: v0.60.2) Release 0.60.2 (rdoc fix)
@@ -227,7 +227,7 @@ module Solargraph
227
227
  expected = expected.downcast_to_literal_if_possible
228
228
  inferred = downcast_to_literal_if_possible
229
229
 
230
- return duck_types_match?(api_map, expected, inferred) if expected.duck_type?
230
+ return duck_types_match?(api_map, expected, inferred, rules) if expected.duck_type?
231
231
 
232
232
  if rules.include? :allow_any_match
233
233
  inferred.any? do |inf|
@@ -245,18 +245,31 @@ module Solargraph
245
245
  # @param api_map [ApiMap]
246
246
  # @param expected [ComplexType, UniqueType]
247
247
  # @param inferred [ComplexType, UniqueType]
248
+ # @param rules [Array<Symbol>]
248
249
  # @return [Boolean]
249
- def duck_types_match? api_map, expected, inferred
250
+ def duck_types_match? api_map, expected, inferred, rules = []
250
251
  raise ArgumentError, 'Expected type must be duck type' unless expected.duck_type?
252
+ allow_any_match = rules.include?(:allow_any_match)
251
253
  expected.each do |exp|
252
254
  next unless exp.duck_type?
253
- quack = exp.to_s[1..]
254
- # @sg-ignore Need to add nil check here
255
- return false if api_map.get_method_stack(inferred.namespace, quack, scope: inferred.scope).empty?
255
+ quack = exp.to_s[1..] || ''
256
+ matched = allow_any_match ? inferred.any? { |inf| duck_type_provides?(api_map, inf, quack) } : inferred.all? { |inf| duck_type_provides?(api_map, inf, quack) }
257
+ return false unless matched
256
258
  end
257
259
  true
258
260
  end
259
261
 
262
+ # @param api_map [ApiMap]
263
+ # @param inf [UniqueType]
264
+ # @param quack [String]
265
+ # @return [Boolean]
266
+ def duck_type_provides? api_map, inf, quack
267
+ return true if inf.duck_type? && inf.to_s[1..] == quack
268
+
269
+ !api_map.get_method_stack(inf.namespace, quack, scope: inf.scope).empty?
270
+ end
271
+ private :duck_type_provides?
272
+
260
273
  # @return [String]
261
274
  def rooted_tags
262
275
  map(&:rooted_tag).join(', ')
@@ -27,7 +27,6 @@ module Solargraph
27
27
  source: :parser
28
28
  )
29
29
  pins.push nspin
30
- Solargraph.logger.warn "Superclass: #{superclass_name}" if superclass_name&.start_with?('Array')
31
30
  if superclass_name
32
31
  pins.push Pin::Reference::Superclass.new(
33
32
  location: loc,
@@ -41,12 +40,22 @@ module Solargraph
41
40
 
42
41
  private
43
42
 
44
- # @param comments [String]
43
+ # Type arguments for a generic superclass in inline RBS syntax, e.g.,
44
+ # `class Foo < Array #[String]`. Only recognized where RBS defines
45
+ # it: directly after the superclass, on the same line, with no space
46
+ # between `#` and `[`. Anything else is an ordinary comment.
47
+ #
45
48
  # @return [String, nil]
46
49
  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
+ superclass = node.children[1]
51
+ return unless superclass
52
+
53
+ source = region.source.code
54
+ pos = get_node_end_position(superclass)
55
+ offset = Position.line_char_to_offset(source, pos.line, pos.character)
56
+ eol = source.index("\n", offset) || source.length
57
+ match = source[offset...eol].to_s.match(/\A\s*#\[([^\]]*)\]/)
58
+ return unless match
50
59
 
51
60
  code = match[1].strip
52
61
  return if code.empty?
@@ -148,12 +148,13 @@ module Solargraph
148
148
 
149
149
  # @param parameters [::Array<Parameter>]
150
150
  # @param return_type [ComplexType, nil]
151
+ # @param tag_docstring [YARD::Docstring] source of @yieldparam/@yieldreturn tags; pass an @overload tag's own docstring here
151
152
  # @return [Signature]
152
- def generate_signature parameters, return_type
153
+ def generate_signature parameters, return_type, tag_docstring = docstring
153
154
  # @type [Pin::Signature, nil]
154
155
  block = nil
155
- yieldparam_tags = docstring.tags(:yieldparam)
156
- yieldreturn_tags = docstring.tags(:yieldreturn)
156
+ yieldparam_tags = tag_docstring.tags(:yieldparam)
157
+ yieldreturn_tags = tag_docstring.tags(:yieldreturn)
157
158
  generics = docstring.tags(:generic).map(&:name)
158
159
  needs_block_param_signature =
159
160
  parameters.last&.block? || !yieldreturn_tags.empty? || !yieldparam_tags.empty?
@@ -747,7 +748,9 @@ module Solargraph
747
748
  top_type = generate_complex_type
748
749
  result = []
749
750
  result.push generate_signature(parameters, top_type) if top_type.defined?
750
- result.concat(overloads.map { |meth| generate_signature(meth.parameters, meth.return_type) }) unless overloads.empty?
751
+ # @param meth [Pin::Signature]
752
+ # @param tag [YARD::Tags::OverloadTag]
753
+ result.concat(overloads.zip(docstring.tags(:overload).select(&:parameters)).map { |meth, tag| generate_signature(meth.parameters, meth.return_type, tag.docstring) }) unless overloads.empty?
751
754
  result.push generate_signature(parameters, @return_type || ComplexType::UNDEFINED) if result.empty?
752
755
  result
753
756
  end
@@ -71,6 +71,95 @@ module Solargraph
71
71
 
72
72
  private
73
73
 
74
+ # Checks whether a single overload signature matches the call's
75
+ # arguments/block and, if so, resolves its return type. Threaded
76
+ # through the caller's accumulator so behavior matches the
77
+ # original inline loop: if the overload doesn't match, the
78
+ # incoming type/signature are returned unchanged.
79
+ #
80
+ # @param overload [Pin::Signature]
81
+ # @param pin [Pin::Method]
82
+ # @param api_map [ApiMap]
83
+ # @param name_pin [Pin::Base]
84
+ # @param locals [::Array<Solargraph::Pin::LocalVariable, Solargraph::Pin::Parameter>]
85
+ # @param type [ComplexType]
86
+ # @param new_signature_pin [Pin::Signature, nil]
87
+ # @return [::Array(ComplexType, Pin::Signature)]
88
+ def match_overload_type overload, pin, api_map, name_pin, locals, type, new_signature_pin
89
+ return [type, new_signature_pin] unless overload.arity_matches?(arguments, with_block?)
90
+
91
+ match = true
92
+ atypes = []
93
+ arguments.each_with_index do |arg, idx|
94
+ param = overload.parameters[idx]
95
+ if param.nil?
96
+ match = overload.parameters.any?(&:restarg?)
97
+ break
98
+ end
99
+ arg_name_pin = Pin::ProxyType.anonymous(name_pin.context,
100
+ closure: name_pin.closure,
101
+ gates: name_pin.gates,
102
+ source: :chain)
103
+ atype = atypes[idx] ||= arg.infer(api_map, arg_name_pin, locals)
104
+ # @sg-ignore flow sensitive typing should handle is_a? and next
105
+ unless param.compatible_arg?(atype, api_map) || param.restarg?
106
+ match = false
107
+ break
108
+ end
109
+ end
110
+ return [type, new_signature_pin] unless match
111
+
112
+ if overload.block && with_block?
113
+ block_atypes = overload.block.parameters.map(&:return_type)
114
+ # @todo Need to add nil check here
115
+ # @sg-ignore Need to add nil check here
116
+ blocktype = if block.links.map(&:class) == [BlockSymbol]
117
+ # like the bar in foo(&:bar)
118
+ block_symbol_call_type(api_map, name_pin.context, block_atypes, locals)
119
+ else
120
+ block_call_type(api_map, name_pin, locals)
121
+ end
122
+ end
123
+ new_signature_pin = overload.resolve_generics_from_context_until_complete(overload.generics, atypes, nil, nil,
124
+ blocktype)
125
+ # @todo It shouldn't be necessary to choose either generics or macros
126
+ # @sg-ignore Need to add nil check here
127
+ new_return_type = if new_signature_pin.return_type.defined?
128
+ # @sg-ignore Need to add nil check here
129
+ new_signature_pin.return_type
130
+ else
131
+ # @sg-ignore Need to add nil check here
132
+ named_types = pin.parameter_names.zip(arguments.map { |arg| ComplexType.try_parse(simple_convert(arg.node).to_s) }).to_h
133
+ pin.typify(api_map).expand(named_types)
134
+ end
135
+ self_type = if head?
136
+ # If we're at the head of the chain, we called a
137
+ # method somewhere that marked itself as returning
138
+ # self. Given we didn't invoke this on an object,
139
+ # this must be a method in this same class - so we
140
+ # use our own self type
141
+ name_pin.context
142
+ else
143
+ # if we're past the head in the chain, whatever the
144
+ # type of the lhs side is what 'self' will be in its
145
+ # declaration - we can't just use the type of the
146
+ # method pin, as this might be a subclass of the
147
+ # place where the method is defined
148
+ name_pin.binder
149
+ end
150
+ # This same logic applies to the YARD work done by
151
+ # 'with_params()'.
152
+ #
153
+ # qualify(), however, happens in the namespace where
154
+ # the docs were written - from the method pin.
155
+ # @todo Need to add nil check here
156
+ if new_return_type.defined?
157
+ type = with_params(new_return_type.self_to_type(self_type), self_type).qualify(api_map, *pin.gates)
158
+ end
159
+ type ||= ComplexType::UNDEFINED
160
+ [type, new_signature_pin]
161
+ end
162
+
74
163
  # @param pins [::Enumerable<Pin::Base>]
75
164
  # @param api_map [ApiMap]
76
165
  # @param name_pin [Pin::Base]
@@ -96,76 +185,20 @@ module Solargraph
96
185
  # @sg-ignore flow sensitive typing should handle is_a? and next
97
186
  # @param ol [Pin::Signature]
98
187
  sorted_overloads.each do |ol|
99
- next unless ol.arity_matches?(arguments, with_block?)
100
- match = true
101
-
102
- atypes = []
103
- arguments.each_with_index do |arg, idx|
104
- param = ol.parameters[idx]
105
- if param.nil?
106
- match = ol.parameters.any?(&:restarg?)
107
- break
108
- end
109
- arg_name_pin = Pin::ProxyType.anonymous(name_pin.context,
110
- closure: name_pin.closure,
111
- gates: name_pin.gates,
112
- source: :chain)
113
- atype = atypes[idx] ||= arg.infer(api_map, arg_name_pin, locals)
114
- unless param.compatible_arg?(atype, api_map) || param.restarg?
115
- match = false
116
- break
117
- end
118
- end
119
- if match
120
- if ol.block && with_block?
121
- block_atypes = ol.block.parameters.map(&:return_type)
122
- # @todo Need to add nil check here
123
- blocktype = if block.links.map(&:class) == [BlockSymbol]
124
- # like the bar in foo(&:bar)
125
- block_symbol_call_type(api_map, name_pin.context, block_atypes, locals)
126
- else
127
- block_call_type(api_map, name_pin, locals)
128
- end
129
- end
130
- new_signature_pin = ol.resolve_generics_from_context_until_complete(ol.generics, atypes, nil, nil,
131
- blocktype)
132
- # @todo It shouldn't be necessary to choose either generics or macros
133
- new_return_type = if new_signature_pin.return_type.defined?
134
- new_signature_pin.return_type
135
- else
136
- named_types = p.parameter_names.zip(arguments.map { |arg| ComplexType.try_parse(simple_convert(arg.node).to_s) }).to_h
137
- p.typify(api_map).expand(named_types)
138
- end
139
- self_type = if head?
140
- # If we're at the head of the chain, we called a
141
- # method somewhere that marked itself as returning
142
- # self. Given we didn't invoke this on an object,
143
- # this must be a method in this same class - so we
144
- # use our own self type
145
- name_pin.context
146
- else
147
- # if we're past the head in the chain, whatever the
148
- # type of the lhs side is what 'self' will be in its
149
- # declaration - we can't just use the type of the
150
- # method pin, as this might be a subclass of the
151
- # place where the method is defined
152
- name_pin.binder
153
- end
154
- # This same logic applies to the YARD work done by
155
- # 'with_params()'.
156
- #
157
- # qualify(), however, happens in the namespace where
158
- # the docs were written - from the method pin.
159
- # @todo Need to add nil check here
160
- if new_return_type.defined?
161
- type = with_params(new_return_type.self_to_type(self_type), self_type).qualify(api_map, *p.gates)
162
- end
163
- type ||= ComplexType::UNDEFINED
164
- end
188
+ type, new_signature_pin = match_overload_type(ol, p, api_map, name_pin, locals, type, new_signature_pin)
165
189
  break if type.defined?
166
190
  end
167
191
  p = p.with_single_signature(new_signature_pin) unless new_signature_pin.nil?
168
192
  next p.proxy(type) if type.defined?
193
+ if !p.macros.empty?
194
+ result = process_macro(p, api_map, name_pin.context, locals)
195
+ # @sg-ignore flow sensitive typing should be able to handle redefinition
196
+ next result unless result.return_type.undefined?
197
+ elsif !p.directives.empty?
198
+ result = process_directive(p, api_map, name_pin.context, locals)
199
+ # @sg-ignore flow sensitive typing should be able to handle redefinition
200
+ next result unless result.return_type.undefined?
201
+ end
169
202
  p
170
203
  end
171
204
  logger.debug do
@@ -186,6 +219,71 @@ module Solargraph
186
219
  end
187
220
  end
188
221
 
222
+ # @param pin [Pin::Base]
223
+ # @param api_map [ApiMap]
224
+ # @param context [ComplexType, ComplexType::UniqueType]
225
+ # @param locals [::Array<Solargraph::Pin::LocalVariable, Solargraph::Pin::Parameter>]
226
+ # @return [Pin::Base]
227
+ def process_macro pin, api_map, context, locals
228
+ pin.macros.each do |macro|
229
+ # @todo 'Wrong argument type for
230
+ # Solargraph::Source::Chain::Call#inner_process_macro:
231
+ # macro expected YARD::Tags::MacroDirective, received
232
+ # generic<Elem>' is because we lose 'rooted' information
233
+ # in the 'Chain::Array' class internally, leaving
234
+ # ::Array#each shadowed when it shouldn't be.
235
+ # @sg-ignore macro is Solargraph::YardMap::Macro, wraps a YARD::Tags::MacroDirective
236
+ result = inner_process_macro(pin, macro, api_map, context, locals)
237
+ return result unless result.return_type.undefined?
238
+ end
239
+ Pin::ProxyType.anonymous(ComplexType::UNDEFINED, source: :chain)
240
+ end
241
+
242
+ # @param pin [Pin::Method]
243
+ # @param api_map [ApiMap]
244
+ # @param context [ComplexType, ComplexType::UniqueType]
245
+ # @param locals [::Array<Solargraph::Pin::LocalVariable, Solargraph::Pin::Parameter>]
246
+ # @return [Pin::ProxyType]
247
+ def process_directive pin, api_map, context, locals
248
+ pin.directives.each do |dir|
249
+ macro = api_map.named_macro(dir.tag.name)
250
+ next if macro.nil?
251
+ # @sg-ignore macro is Solargraph::YardMap::Macro, wraps a YARD::Tags::MacroDirective
252
+ result = inner_process_macro(pin, macro, api_map, context, locals)
253
+ return result unless result.return_type.undefined?
254
+ end
255
+ Pin::ProxyType.anonymous ComplexType::UNDEFINED, source: :chain
256
+ end
257
+
258
+ # @param pin [Pin::Base]
259
+ # @param macro [YARD::Tags::MacroDirective]
260
+ # @param api_map [ApiMap]
261
+ # @param context [ComplexType, ComplexType::UniqueType]
262
+ # @param locals [::Array<Pin::LocalVariable, Pin::Parameter>]
263
+ # @return [Pin::ProxyType]
264
+ def inner_process_macro pin, macro, api_map, context, locals
265
+ vals = arguments.map { |c| Pin::ProxyType.anonymous(c.infer(api_map, pin, locals), source: :chain) }
266
+ txt = macro.tag.text.clone
267
+ # @sg-ignore Need to add nil check here
268
+ if txt.empty? && macro.tag.name
269
+ named = api_map.named_macro(macro.tag.name)
270
+ txt = named.tag.text.clone if named
271
+ end
272
+ i = 1
273
+ vals.each do |v|
274
+ # @sg-ignore Need to add nil check here
275
+ txt.gsub!(/\$#{i}/, v.context.namespace)
276
+ i += 1
277
+ end
278
+ # @sg-ignore Need to add nil check here
279
+ docstring = Solargraph::Source.parse_docstring(txt).to_docstring
280
+ tag = docstring.tag(:return)
281
+ unless tag.nil? || tag.types.nil?
282
+ return Pin::ProxyType.anonymous(ComplexType.try_parse(*tag.types), source: :chain)
283
+ end
284
+ Pin::ProxyType.anonymous(ComplexType::UNDEFINED, source: :chain)
285
+ end
286
+
189
287
  # @param docstring [YARD::Docstring]
190
288
  # @param context [ComplexType]
191
289
  # @return [ComplexType, nil]
@@ -168,7 +168,7 @@ module Solargraph
168
168
  return ComplexType::UNDEFINED
169
169
  end
170
170
  type = infer_from_definitions(pins, links.last.last_context, api_map, locals)
171
- out = maybe_nil(type)
171
+ out = maybe_nil(type, api_map)
172
172
  logger.debug do
173
173
  "Chain#infer_uncached(links=#{links.map(&:desc)}, locals=#{locals.map(&:desc)}, " \
174
174
  "name_pin=#{name_pin}, name_pin.closure=#{name_pin&.closure&.inspect}, " \
@@ -199,8 +199,38 @@ module Solargraph
199
199
  @splat
200
200
  end
201
201
 
202
- def nullable?
203
- links.any?(&:nullable?)
202
+ # Whether this chain's inferred type should have nil added to it
203
+ # because a `&.` earlier in the chain might have short-circuited
204
+ # evaluation to nil.
205
+ #
206
+ # A `&.` only makes the *immediate* call nil-or-normal-result; a
207
+ # later, non-safe-navigated call in the same chain is still
208
+ # invoked on whatever that produced. If receiver is nil at that
209
+ # point, Ruby calls the method on nil itself instead of skipping
210
+ # it - so nil only continues to flow through subsequent links
211
+ # that NilClass defines as returning `self` (e.g., #tap,
212
+ # #itself). Any other subsequent call resolves to a concrete,
213
+ # non-nil-including type (e.g. NilClass#== returns Boolean), and
214
+ # nil no longer needs to be tracked past that point (unless a
215
+ # later `&.` reintroduces it).
216
+ #
217
+ # @param api_map [ApiMap]
218
+ # @return [Boolean]
219
+ def nullable? api_map
220
+ currently_nullable = false
221
+ links.each do |link|
222
+ if link.nullable?
223
+ currently_nullable = true
224
+ next
225
+ end
226
+ next unless currently_nullable
227
+ next unless link.is_a?(Chain::Call)
228
+
229
+ pin = api_map.get_method_stack('NilClass', link.word, scope: :instance).first
230
+ # @sg-ignore Need to add nil check here
231
+ currently_nullable = pin.nil? || pin.return_type.tag == 'self'
232
+ end
233
+ currently_nullable
204
234
  end
205
235
 
206
236
  include Logging
@@ -286,10 +316,11 @@ module Solargraph
286
316
  end
287
317
 
288
318
  # @param type [ComplexType, ComplexType::UniqueType]
319
+ # @param api_map [ApiMap]
289
320
  # @return [ComplexType, ComplexType::UniqueType]
290
- def maybe_nil type
321
+ def maybe_nil type, api_map
291
322
  return type if type.undefined? || type.void? || type.nullable?
292
- return type unless nullable?
323
+ return type unless nullable?(api_map)
293
324
  ComplexType.new(type.items + [ComplexType::NIL])
294
325
  end
295
326
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Solargraph
4
- VERSION = ENV.fetch('SOLARGRAPH_FORCE_VERSION', '0.60.3')
4
+ VERSION = ENV.fetch('SOLARGRAPH_FORCE_VERSION', '0.60.4')
5
5
  end
@@ -11,7 +11,7 @@ module Solargraph
11
11
  # @param spec [Gem::Specification, nil]
12
12
  def initialize code_objects, spec = nil
13
13
  @code_objects = code_objects
14
- @macro_code_objects = code_objects.select { |co| co.is_a?(YARD::CodeObjects::MacroObject) }
14
+ @macro_code_objects = code_objects.grep(YARD::CodeObjects::MacroObject)
15
15
  @spec = spec
16
16
  # @type [Array<Solargraph::Pin::Base>]
17
17
  @pins = []
@@ -32,13 +32,17 @@ module Solargraph
32
32
 
33
33
  private
34
34
 
35
+ def core_store
36
+ @core_store ||= ApiMap::Store.new(RbsMap::CoreMap.new.pins)
37
+ end
38
+
35
39
  # @param code_object [YARD::CodeObjects::Base]
36
40
  # @return [Array<Pin::Base>]
37
41
  def generate_pins code_object
38
42
  result = []
39
43
  case code_object
40
44
  when YARD::CodeObjects::NamespaceObject
41
- nspin = ToNamespace.make(code_object, @spec, @namespace_pins[code_object.namespace.to_s])
45
+ nspin = namespace_with_bug_fix(code_object)
42
46
  @namespace_pins[code_object.path] = nspin
43
47
  result.push nspin
44
48
  if code_object.is_a?(YARD::CodeObjects::ClassObject) && !code_object.superclass.nil?
@@ -50,7 +54,16 @@ module Solargraph
50
54
  else
51
55
  code_object.superclass.to_s
52
56
  end
53
- result.push Solargraph::Pin::Reference::Superclass.new(name: superclass, closure: nspin, source: :yard_map)
57
+ # YARD fills in `Object` for every class whose definition it never
58
+ # saw a superclass clause on - including a bare `class Foo`
59
+ # reopening, which asserts nothing about ancestry - and keeps no
60
+ # record of which case it was. A class with no superclass reference
61
+ # already resolves to Object, so recording this one adds nothing and
62
+ # can shadow a superclass declared in another source.
63
+ unless superclass.delete_prefix('::') == 'Object'
64
+ result.push Solargraph::Pin::Reference::Superclass.new(name: superclass, closure: nspin,
65
+ source: :yard_map)
66
+ end
54
67
  end
55
68
  # @sg-ignore flow sensitive typing ought to be able to handle 'when ClassName'
56
69
  code_object.class_mixins.each do |m|
@@ -72,6 +85,8 @@ module Solargraph
72
85
  # @todo Check the visibility of <Class>.new
73
86
  result.push ToMethod.make(code_object, 'new', :class, :public, closure, @spec)
74
87
  result.push ToMethod.make(code_object, 'initialize', :instance, :private, closure, @spec)
88
+ elsif closure&.source == :yard_map_hack
89
+ result.push ToMethod.make(code_object, nil, :instance, nil, closure, @spec)
75
90
  else
76
91
  result.push ToMethod.make(code_object, nil, nil, nil, closure, @spec)
77
92
  end
@@ -97,6 +112,24 @@ module Solargraph
97
112
  def macros_for_method_object method_object
98
113
  attached_macros_by_method_object[method_object]
99
114
  end
115
+
116
+ # Handle a bug in YARD where code that opens a constant's singleton class
117
+ # (e.g., `class << ENV`) creates a pin that incorrectly overrides the
118
+ # original constant's value.
119
+ #
120
+ # @param code_object [YARD::CodeObjects::NamespaceObject]
121
+ # @return [Pin::Namespace]
122
+ def namespace_with_bug_fix code_object
123
+ core_pin = core_store.get_path_pins(code_object.path)
124
+ .find { |pin| pin.is_a?(Pin::Constant) }
125
+ if core_pin
126
+ location = Helpers.object_location(code_object, @spec)
127
+ Solargraph.logger.warn "Adjusting YARD pin that conflicts with RBS core (#{code_object.inspect} #{location.inspect})"
128
+ Pin::Namespace.new(name: core_pin.return_type.namespace, location: location, source: :yard_map_hack)
129
+ else
130
+ ToNamespace.make(code_object, @spec, @namespace_pins[code_object.namespace.to_s])
131
+ end
132
+ end
100
133
  end
101
134
  end
102
135
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solargraph
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.60.3
4
+ version: 0.60.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fred Snyder