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 +4 -4
- data/.github/workflows/plugins.yml +1 -5
- data/CHANGELOG.md +10 -0
- data/lib/solargraph/complex_type.rb +18 -5
- data/lib/solargraph/parser/parser_gem/node_processors/namespace_node.rb +14 -5
- data/lib/solargraph/pin/method.rb +7 -4
- data/lib/solargraph/source/chain/call.rb +164 -66
- data/lib/solargraph/source/chain.rb +36 -5
- data/lib/solargraph/version.rb +1 -1
- data/lib/solargraph/yard_map/mapper.rb +36 -3
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7c7397d5a9bbee07b12baa9372ce14f61216e5a310aa0f68605acabf8a9430e2
|
|
4
|
+
data.tar.gz: aeecf5c5d1ea7b1040fd3ae4bd9d1bff1685e468bd92b30dedfdd8ded2f1cd04
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
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
|
-
|
|
255
|
-
return false
|
|
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
|
-
#
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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 =
|
|
156
|
-
yieldreturn_tags =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
203
|
-
|
|
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
|
|
data/lib/solargraph/version.rb
CHANGED
|
@@ -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.
|
|
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 =
|
|
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
|
-
|
|
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
|