solargraph 0.58.1 → 0.58.2
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/.gitignore +1 -0
- data/CHANGELOG.md +7 -1
- data/lib/solargraph/api_map/cache.rb +110 -110
- data/lib/solargraph/api_map/constants.rb +279 -279
- data/lib/solargraph/api_map/index.rb +193 -193
- data/lib/solargraph/api_map/source_to_yard.rb +97 -97
- data/lib/solargraph/api_map/store.rb +384 -384
- data/lib/solargraph/api_map.rb +945 -945
- data/lib/solargraph/complex_type/type_methods.rb +228 -228
- data/lib/solargraph/complex_type/unique_type.rb +482 -482
- data/lib/solargraph/complex_type.rb +444 -444
- data/lib/solargraph/convention/data_definition/data_definition_node.rb +91 -91
- data/lib/solargraph/convention/data_definition.rb +105 -105
- data/lib/solargraph/convention/struct_definition/struct_assignment_node.rb +61 -61
- data/lib/solargraph/convention/struct_definition/struct_definition_node.rb +102 -102
- data/lib/solargraph/convention/struct_definition.rb +164 -164
- data/lib/solargraph/diagnostics/require_not_found.rb +53 -53
- data/lib/solargraph/diagnostics/rubocop.rb +118 -118
- data/lib/solargraph/diagnostics/rubocop_helpers.rb +68 -68
- data/lib/solargraph/diagnostics/type_check.rb +55 -55
- data/lib/solargraph/doc_map.rb +439 -439
- data/lib/solargraph/equality.rb +34 -34
- data/lib/solargraph/gem_pins.rb +98 -98
- data/lib/solargraph/language_server/host/diagnoser.rb +89 -89
- data/lib/solargraph/language_server/host/dispatch.rb +130 -130
- data/lib/solargraph/language_server/host/message_worker.rb +112 -112
- data/lib/solargraph/language_server/host/sources.rb +99 -99
- data/lib/solargraph/language_server/host.rb +878 -878
- data/lib/solargraph/language_server/message/extended/check_gem_version.rb +114 -114
- data/lib/solargraph/language_server/message/extended/document.rb +23 -23
- data/lib/solargraph/language_server/message/text_document/completion.rb +56 -56
- data/lib/solargraph/language_server/message/text_document/definition.rb +40 -40
- data/lib/solargraph/language_server/message/text_document/document_symbol.rb +26 -26
- data/lib/solargraph/language_server/message/text_document/formatting.rb +148 -148
- data/lib/solargraph/language_server/message/text_document/hover.rb +58 -58
- data/lib/solargraph/language_server/message/text_document/signature_help.rb +24 -24
- data/lib/solargraph/language_server/message/text_document/type_definition.rb +25 -25
- data/lib/solargraph/language_server/message/workspace/workspace_symbol.rb +23 -23
- data/lib/solargraph/library.rb +683 -683
- data/lib/solargraph/location.rb +82 -82
- data/lib/solargraph/logging.rb +37 -37
- data/lib/solargraph/parser/comment_ripper.rb +69 -69
- data/lib/solargraph/parser/flow_sensitive_typing.rb +255 -255
- data/lib/solargraph/parser/node_processor/base.rb +92 -92
- data/lib/solargraph/parser/node_processor.rb +62 -62
- data/lib/solargraph/parser/parser_gem/class_methods.rb +149 -149
- data/lib/solargraph/parser/parser_gem/node_chainer.rb +166 -166
- data/lib/solargraph/parser/parser_gem/node_methods.rb +486 -486
- data/lib/solargraph/parser/parser_gem/node_processors/and_node.rb +22 -22
- data/lib/solargraph/parser/parser_gem/node_processors/args_node.rb +59 -59
- data/lib/solargraph/parser/parser_gem/node_processors/begin_node.rb +15 -15
- data/lib/solargraph/parser/parser_gem/node_processors/block_node.rb +46 -46
- data/lib/solargraph/parser/parser_gem/node_processors/def_node.rb +53 -53
- data/lib/solargraph/parser/parser_gem/node_processors/if_node.rb +23 -23
- data/lib/solargraph/parser/parser_gem/node_processors/ivasgn_node.rb +40 -40
- data/lib/solargraph/parser/parser_gem/node_processors/lvasgn_node.rb +29 -29
- data/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb +59 -59
- data/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb +98 -98
- data/lib/solargraph/parser/parser_gem/node_processors/orasgn_node.rb +17 -17
- data/lib/solargraph/parser/parser_gem/node_processors/resbody_node.rb +38 -38
- data/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb +52 -52
- data/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +291 -291
- data/lib/solargraph/parser/parser_gem/node_processors/while_node.rb +29 -29
- data/lib/solargraph/parser/parser_gem/node_processors.rb +70 -70
- data/lib/solargraph/parser/region.rb +69 -69
- data/lib/solargraph/parser/snippet.rb +17 -17
- data/lib/solargraph/pin/base.rb +729 -729
- data/lib/solargraph/pin/base_variable.rb +126 -126
- data/lib/solargraph/pin/block.rb +104 -104
- data/lib/solargraph/pin/breakable.rb +9 -9
- data/lib/solargraph/pin/callable.rb +231 -231
- data/lib/solargraph/pin/closure.rb +72 -72
- data/lib/solargraph/pin/common.rb +79 -79
- data/lib/solargraph/pin/conversions.rb +123 -123
- data/lib/solargraph/pin/delegated_method.rb +120 -120
- data/lib/solargraph/pin/documenting.rb +114 -114
- data/lib/solargraph/pin/instance_variable.rb +34 -34
- data/lib/solargraph/pin/keyword.rb +20 -20
- data/lib/solargraph/pin/local_variable.rb +75 -75
- data/lib/solargraph/pin/method.rb +672 -672
- data/lib/solargraph/pin/method_alias.rb +34 -34
- data/lib/solargraph/pin/namespace.rb +115 -115
- data/lib/solargraph/pin/parameter.rb +275 -275
- data/lib/solargraph/pin/proxy_type.rb +39 -39
- data/lib/solargraph/pin/reference/override.rb +47 -47
- data/lib/solargraph/pin/reference/superclass.rb +15 -15
- data/lib/solargraph/pin/reference.rb +39 -39
- data/lib/solargraph/pin/search.rb +61 -61
- data/lib/solargraph/pin/signature.rb +61 -61
- data/lib/solargraph/pin/symbol.rb +53 -53
- data/lib/solargraph/pin/until.rb +18 -18
- data/lib/solargraph/pin/while.rb +18 -18
- data/lib/solargraph/pin.rb +44 -44
- data/lib/solargraph/pin_cache.rb +245 -245
- data/lib/solargraph/position.rb +132 -119
- data/lib/solargraph/range.rb +112 -112
- data/lib/solargraph/rbs_map/conversions.rb +823 -823
- data/lib/solargraph/rbs_map/core_map.rb +58 -58
- data/lib/solargraph/rbs_map/stdlib_map.rb +43 -43
- data/lib/solargraph/rbs_map.rb +163 -163
- data/lib/solargraph/shell.rb +352 -352
- data/lib/solargraph/source/chain/call.rb +337 -337
- data/lib/solargraph/source/chain/constant.rb +26 -26
- data/lib/solargraph/source/chain/hash.rb +34 -34
- data/lib/solargraph/source/chain/if.rb +28 -28
- data/lib/solargraph/source/chain/instance_variable.rb +13 -13
- data/lib/solargraph/source/chain/literal.rb +48 -48
- data/lib/solargraph/source/chain/or.rb +23 -23
- data/lib/solargraph/source/chain.rb +291 -291
- data/lib/solargraph/source/change.rb +82 -82
- data/lib/solargraph/source/cursor.rb +166 -166
- data/lib/solargraph/source/source_chainer.rb +194 -194
- data/lib/solargraph/source/updater.rb +55 -55
- data/lib/solargraph/source.rb +498 -498
- data/lib/solargraph/source_map/clip.rb +226 -226
- data/lib/solargraph/source_map/data.rb +34 -34
- data/lib/solargraph/source_map/mapper.rb +259 -259
- data/lib/solargraph/source_map.rb +212 -212
- data/lib/solargraph/type_checker/checks.rb +124 -124
- data/lib/solargraph/type_checker/param_def.rb +37 -37
- data/lib/solargraph/type_checker/problem.rb +32 -32
- data/lib/solargraph/type_checker/rules.rb +84 -84
- data/lib/solargraph/type_checker.rb +814 -814
- data/lib/solargraph/version.rb +1 -1
- data/lib/solargraph/workspace/config.rb +255 -255
- data/lib/solargraph/workspace/require_paths.rb +97 -97
- data/lib/solargraph/workspace.rb +220 -220
- data/lib/solargraph/yard_map/helpers.rb +44 -44
- data/lib/solargraph/yard_map/mapper/to_method.rb +130 -130
- data/lib/solargraph/yard_map/mapper/to_namespace.rb +31 -31
- data/lib/solargraph/yard_map/mapper.rb +79 -79
- data/lib/solargraph/yard_map/to_method.rb +89 -89
- data/lib/solargraph/yardoc.rb +87 -87
- data/lib/solargraph.rb +105 -105
- data/rbs_collection.yaml +1 -1
- metadata +12 -12
- /data/{sig → rbs}/shims/ast/0/node.rbs +0 -0
- /data/{sig → rbs}/shims/ast/2.4/.rbs_meta.yaml +0 -0
- /data/{sig → rbs}/shims/ast/2.4/ast.rbs +0 -0
- /data/{sig → rbs}/shims/parser/3.2.0.1/builders/default.rbs +0 -0
- /data/{sig → rbs}/shims/parser/3.2.0.1/manifest.yaml +0 -0
- /data/{sig → rbs}/shims/parser/3.2.0.1/parser.rbs +0 -0
- /data/{sig → rbs}/shims/parser/3.2.0.1/polyfill.rbs +0 -0
- /data/{sig → rbs}/shims/thor/1.2.0.1/.rbs_meta.yaml +0 -0
- /data/{sig → rbs}/shims/thor/1.2.0.1/manifest.yaml +0 -0
- /data/{sig → rbs}/shims/thor/1.2.0.1/thor.rbs +0 -0
|
@@ -1,255 +1,255 @@
|
|
|
1
|
-
module Solargraph
|
|
2
|
-
module Parser
|
|
3
|
-
class FlowSensitiveTyping
|
|
4
|
-
include Solargraph::Parser::NodeMethods
|
|
5
|
-
|
|
6
|
-
# @param locals [Array<Solargraph::Pin::LocalVariable, Solargraph::Pin::Parameter>]
|
|
7
|
-
# @param enclosing_breakable_pin [Solargraph::Pin::Breakable, nil]
|
|
8
|
-
def initialize(locals, enclosing_breakable_pin = nil)
|
|
9
|
-
@locals = locals
|
|
10
|
-
@enclosing_breakable_pin = enclosing_breakable_pin
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
# @param and_node [Parser::AST::Node]
|
|
14
|
-
# @param true_ranges [Array<Range>]
|
|
15
|
-
#
|
|
16
|
-
# @return [void]
|
|
17
|
-
def process_and(and_node, true_ranges = [])
|
|
18
|
-
# @type [Parser::AST::Node]
|
|
19
|
-
lhs = and_node.children[0]
|
|
20
|
-
# @type [Parser::AST::Node]
|
|
21
|
-
rhs = and_node.children[1]
|
|
22
|
-
|
|
23
|
-
before_rhs_loc = rhs.location.expression.adjust(begin_pos: -1)
|
|
24
|
-
before_rhs_pos = Position.new(before_rhs_loc.line, before_rhs_loc.column)
|
|
25
|
-
|
|
26
|
-
rhs_presence = Range.new(before_rhs_pos,
|
|
27
|
-
get_node_end_position(rhs))
|
|
28
|
-
process_isa(lhs, true_ranges + [rhs_presence])
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
# @param if_node [Parser::AST::Node]
|
|
32
|
-
#
|
|
33
|
-
# @return [void]
|
|
34
|
-
def process_if(if_node)
|
|
35
|
-
#
|
|
36
|
-
# See if we can refine a type based on the result of 'if foo.nil?'
|
|
37
|
-
#
|
|
38
|
-
# [3] pry(main)> require 'parser/current'; Parser::CurrentRuby.parse("if foo.is_a? Baz; then foo; else bar; end")
|
|
39
|
-
# => s(:if,
|
|
40
|
-
# s(:send,
|
|
41
|
-
# s(:send, nil, :foo), :is_a?,
|
|
42
|
-
# s(:const, nil, :Baz)),
|
|
43
|
-
# s(:send, nil, :foo),
|
|
44
|
-
# s(:send, nil, :bar))
|
|
45
|
-
# [4] pry(main)>
|
|
46
|
-
conditional_node = if_node.children[0]
|
|
47
|
-
# @type [Parser::AST::Node]
|
|
48
|
-
then_clause = if_node.children[1]
|
|
49
|
-
# @type [Parser::AST::Node]
|
|
50
|
-
else_clause = if_node.children[2]
|
|
51
|
-
|
|
52
|
-
true_ranges = []
|
|
53
|
-
if always_breaks?(else_clause)
|
|
54
|
-
unless enclosing_breakable_pin.nil?
|
|
55
|
-
rest_of_breakable_body = Range.new(get_node_end_position(if_node),
|
|
56
|
-
get_node_end_position(enclosing_breakable_pin.node))
|
|
57
|
-
true_ranges << rest_of_breakable_body
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
unless then_clause.nil?
|
|
62
|
-
#
|
|
63
|
-
# Add specialized locals for the then clause range
|
|
64
|
-
#
|
|
65
|
-
before_then_clause_loc = then_clause.location.expression.adjust(begin_pos: -1)
|
|
66
|
-
before_then_clause_pos = Position.new(before_then_clause_loc.line, before_then_clause_loc.column)
|
|
67
|
-
true_ranges << Range.new(before_then_clause_pos,
|
|
68
|
-
get_node_end_position(then_clause))
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
process_conditional(conditional_node, true_ranges)
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
class << self
|
|
75
|
-
include Logging
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
# Find a variable pin by name and where it is used.
|
|
79
|
-
#
|
|
80
|
-
# Resolves our most specific view of this variable's type by
|
|
81
|
-
# preferring pins created by flow-sensitive typing when we have
|
|
82
|
-
# them based on the Closure and Location.
|
|
83
|
-
#
|
|
84
|
-
# @param pins [Array<Pin::LocalVariable>]
|
|
85
|
-
# @param name [String]
|
|
86
|
-
# @param closure [Pin::Closure]
|
|
87
|
-
# @param location [Location]
|
|
88
|
-
#
|
|
89
|
-
# @return [Array<Pin::LocalVariable>]
|
|
90
|
-
def self.visible_pins(pins, name, closure, location)
|
|
91
|
-
logger.debug { "FlowSensitiveTyping#visible_pins(name=#{name}, closure=#{closure}, location=#{location})" }
|
|
92
|
-
pins_with_name = pins.select { |p| p.name == name }
|
|
93
|
-
if pins_with_name.empty?
|
|
94
|
-
logger.debug { "FlowSensitiveTyping#visible_pins(name=#{name}, closure=#{closure}, location=#{location}) => [] - no pins with name" }
|
|
95
|
-
return []
|
|
96
|
-
end
|
|
97
|
-
pins_with_specific_visibility = pins.select { |p| p.name == name && p.presence && p.visible_at?(closure, location) }
|
|
98
|
-
if pins_with_specific_visibility.empty?
|
|
99
|
-
logger.debug { "FlowSensitiveTyping#visible_pins(name=#{name}, closure=#{closure}, location=#{location}) => #{pins_with_name} - no pins with specific visibility" }
|
|
100
|
-
return pins_with_name
|
|
101
|
-
end
|
|
102
|
-
visible_pins_specific_to_this_closure = pins_with_specific_visibility.select { |p| p.closure == closure }
|
|
103
|
-
if visible_pins_specific_to_this_closure.empty?
|
|
104
|
-
logger.debug { "FlowSensitiveTyping#visible_pins(name=#{name}, closure=#{closure}, location=#{location}) => #{pins_with_specific_visibility} - no visible pins specific to this closure (#{closure})}" }
|
|
105
|
-
return pins_with_specific_visibility
|
|
106
|
-
end
|
|
107
|
-
flow_defined_pins = pins_with_specific_visibility.select { |p| p.presence_certain? }
|
|
108
|
-
if flow_defined_pins.empty?
|
|
109
|
-
logger.debug { "FlowSensitiveTyping#visible_pins(name=#{name}, closure=#{closure}, location=#{location}) => #{visible_pins_specific_to_this_closure} - no flow-defined pins" }
|
|
110
|
-
return visible_pins_specific_to_this_closure
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
logger.debug { "FlowSensitiveTyping#visible_pins(name=#{name}, closure=#{closure}, location=#{location}) => #{flow_defined_pins}" }
|
|
114
|
-
|
|
115
|
-
flow_defined_pins
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
include Logging
|
|
119
|
-
|
|
120
|
-
private
|
|
121
|
-
|
|
122
|
-
# @param pin [Pin::LocalVariable]
|
|
123
|
-
# @param downcast_type_name [String]
|
|
124
|
-
# @param presence [Range]
|
|
125
|
-
#
|
|
126
|
-
# @return [void]
|
|
127
|
-
def add_downcast_local(pin, downcast_type_name, presence)
|
|
128
|
-
# @todo Create pin#update method
|
|
129
|
-
new_pin = Solargraph::Pin::LocalVariable.new(
|
|
130
|
-
location: pin.location,
|
|
131
|
-
closure: pin.closure,
|
|
132
|
-
name: pin.name,
|
|
133
|
-
assignment: pin.assignment,
|
|
134
|
-
comments: pin.comments,
|
|
135
|
-
presence: presence,
|
|
136
|
-
return_type: ComplexType.try_parse(downcast_type_name),
|
|
137
|
-
presence_certain: true,
|
|
138
|
-
source: :flow_sensitive_typing
|
|
139
|
-
)
|
|
140
|
-
locals.push(new_pin)
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
# @param facts_by_pin [Hash{Pin::LocalVariable => Array<Hash{Symbol => String}>}]
|
|
144
|
-
# @param presences [Array<Range>]
|
|
145
|
-
#
|
|
146
|
-
# @return [void]
|
|
147
|
-
def process_facts(facts_by_pin, presences)
|
|
148
|
-
#
|
|
149
|
-
# Add specialized locals for the rest of the block
|
|
150
|
-
#
|
|
151
|
-
facts_by_pin.each_pair do |pin, facts|
|
|
152
|
-
facts.each do |fact|
|
|
153
|
-
downcast_type_name = fact.fetch(:type)
|
|
154
|
-
presences.each do |presence|
|
|
155
|
-
add_downcast_local(pin, downcast_type_name, presence)
|
|
156
|
-
end
|
|
157
|
-
end
|
|
158
|
-
end
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
# @param conditional_node [Parser::AST::Node]
|
|
162
|
-
# @param true_ranges [Array<Range>]
|
|
163
|
-
#
|
|
164
|
-
# @return [void]
|
|
165
|
-
def process_conditional(conditional_node, true_ranges)
|
|
166
|
-
if conditional_node.type == :send
|
|
167
|
-
process_isa(conditional_node, true_ranges)
|
|
168
|
-
elsif conditional_node.type == :and
|
|
169
|
-
process_and(conditional_node, true_ranges)
|
|
170
|
-
end
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
# @param isa_node [Parser::AST::Node]
|
|
174
|
-
# @return [Array(String, String), nil]
|
|
175
|
-
def parse_isa(isa_node)
|
|
176
|
-
return unless isa_node&.type == :send && isa_node.children[1] == :is_a?
|
|
177
|
-
# Check if conditional node follows this pattern:
|
|
178
|
-
# s(:send,
|
|
179
|
-
# s(:send, nil, :foo), :is_a?,
|
|
180
|
-
# s(:const, nil, :Baz)),
|
|
181
|
-
isa_receiver = isa_node.children[0]
|
|
182
|
-
isa_type_name = type_name(isa_node.children[2])
|
|
183
|
-
return unless isa_type_name
|
|
184
|
-
|
|
185
|
-
# check if isa_receiver looks like this:
|
|
186
|
-
# s(:send, nil, :foo)
|
|
187
|
-
# and set variable_name to :foo
|
|
188
|
-
if isa_receiver&.type == :send && isa_receiver.children[0].nil? && isa_receiver.children[1].is_a?(Symbol)
|
|
189
|
-
variable_name = isa_receiver.children[1].to_s
|
|
190
|
-
end
|
|
191
|
-
# or like this:
|
|
192
|
-
# (lvar :repr)
|
|
193
|
-
variable_name = isa_receiver.children[0].to_s if isa_receiver&.type == :lvar
|
|
194
|
-
return unless variable_name
|
|
195
|
-
|
|
196
|
-
[isa_type_name, variable_name]
|
|
197
|
-
end
|
|
198
|
-
|
|
199
|
-
# @param variable_name [String]
|
|
200
|
-
# @param position [Position]
|
|
201
|
-
#
|
|
202
|
-
# @return [Solargraph::Pin::LocalVariable, nil]
|
|
203
|
-
def find_local(variable_name, position)
|
|
204
|
-
pins = locals.select { |pin| pin.name == variable_name && pin.presence.include?(position) }
|
|
205
|
-
return unless pins.length == 1
|
|
206
|
-
pins.first
|
|
207
|
-
end
|
|
208
|
-
|
|
209
|
-
# @param isa_node [Parser::AST::Node]
|
|
210
|
-
# @param true_presences [Array<Range>]
|
|
211
|
-
#
|
|
212
|
-
# @return [void]
|
|
213
|
-
def process_isa(isa_node, true_presences)
|
|
214
|
-
isa_type_name, variable_name = parse_isa(isa_node)
|
|
215
|
-
return if variable_name.nil? || variable_name.empty?
|
|
216
|
-
isa_position = Range.from_node(isa_node).start
|
|
217
|
-
|
|
218
|
-
pin = find_local(variable_name, isa_position)
|
|
219
|
-
return unless pin
|
|
220
|
-
|
|
221
|
-
if_true = {}
|
|
222
|
-
if_true[pin] ||= []
|
|
223
|
-
if_true[pin] << { type: isa_type_name }
|
|
224
|
-
process_facts(if_true, true_presences)
|
|
225
|
-
end
|
|
226
|
-
|
|
227
|
-
# @param node [Parser::AST::Node]
|
|
228
|
-
#
|
|
229
|
-
# @return [String, nil]
|
|
230
|
-
def type_name(node)
|
|
231
|
-
# e.g.,
|
|
232
|
-
# s(:const, nil, :Baz)
|
|
233
|
-
return unless node&.type == :const
|
|
234
|
-
module_node = node.children[0]
|
|
235
|
-
class_node = node.children[1]
|
|
236
|
-
|
|
237
|
-
return class_node.to_s if module_node.nil?
|
|
238
|
-
|
|
239
|
-
module_type_name = type_name(module_node)
|
|
240
|
-
return unless module_type_name
|
|
241
|
-
|
|
242
|
-
"#{module_type_name}::#{class_node}"
|
|
243
|
-
end
|
|
244
|
-
|
|
245
|
-
# @param clause_node [Parser::AST::Node]
|
|
246
|
-
def always_breaks?(clause_node)
|
|
247
|
-
clause_node&.type == :break
|
|
248
|
-
end
|
|
249
|
-
|
|
250
|
-
attr_reader :locals
|
|
251
|
-
|
|
252
|
-
attr_reader :enclosing_breakable_pin
|
|
253
|
-
end
|
|
254
|
-
end
|
|
255
|
-
end
|
|
1
|
+
module Solargraph
|
|
2
|
+
module Parser
|
|
3
|
+
class FlowSensitiveTyping
|
|
4
|
+
include Solargraph::Parser::NodeMethods
|
|
5
|
+
|
|
6
|
+
# @param locals [Array<Solargraph::Pin::LocalVariable, Solargraph::Pin::Parameter>]
|
|
7
|
+
# @param enclosing_breakable_pin [Solargraph::Pin::Breakable, nil]
|
|
8
|
+
def initialize(locals, enclosing_breakable_pin = nil)
|
|
9
|
+
@locals = locals
|
|
10
|
+
@enclosing_breakable_pin = enclosing_breakable_pin
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# @param and_node [Parser::AST::Node]
|
|
14
|
+
# @param true_ranges [Array<Range>]
|
|
15
|
+
#
|
|
16
|
+
# @return [void]
|
|
17
|
+
def process_and(and_node, true_ranges = [])
|
|
18
|
+
# @type [Parser::AST::Node]
|
|
19
|
+
lhs = and_node.children[0]
|
|
20
|
+
# @type [Parser::AST::Node]
|
|
21
|
+
rhs = and_node.children[1]
|
|
22
|
+
|
|
23
|
+
before_rhs_loc = rhs.location.expression.adjust(begin_pos: -1)
|
|
24
|
+
before_rhs_pos = Position.new(before_rhs_loc.line, before_rhs_loc.column)
|
|
25
|
+
|
|
26
|
+
rhs_presence = Range.new(before_rhs_pos,
|
|
27
|
+
get_node_end_position(rhs))
|
|
28
|
+
process_isa(lhs, true_ranges + [rhs_presence])
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @param if_node [Parser::AST::Node]
|
|
32
|
+
#
|
|
33
|
+
# @return [void]
|
|
34
|
+
def process_if(if_node)
|
|
35
|
+
#
|
|
36
|
+
# See if we can refine a type based on the result of 'if foo.nil?'
|
|
37
|
+
#
|
|
38
|
+
# [3] pry(main)> require 'parser/current'; Parser::CurrentRuby.parse("if foo.is_a? Baz; then foo; else bar; end")
|
|
39
|
+
# => s(:if,
|
|
40
|
+
# s(:send,
|
|
41
|
+
# s(:send, nil, :foo), :is_a?,
|
|
42
|
+
# s(:const, nil, :Baz)),
|
|
43
|
+
# s(:send, nil, :foo),
|
|
44
|
+
# s(:send, nil, :bar))
|
|
45
|
+
# [4] pry(main)>
|
|
46
|
+
conditional_node = if_node.children[0]
|
|
47
|
+
# @type [Parser::AST::Node]
|
|
48
|
+
then_clause = if_node.children[1]
|
|
49
|
+
# @type [Parser::AST::Node]
|
|
50
|
+
else_clause = if_node.children[2]
|
|
51
|
+
|
|
52
|
+
true_ranges = []
|
|
53
|
+
if always_breaks?(else_clause)
|
|
54
|
+
unless enclosing_breakable_pin.nil?
|
|
55
|
+
rest_of_breakable_body = Range.new(get_node_end_position(if_node),
|
|
56
|
+
get_node_end_position(enclosing_breakable_pin.node))
|
|
57
|
+
true_ranges << rest_of_breakable_body
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
unless then_clause.nil?
|
|
62
|
+
#
|
|
63
|
+
# Add specialized locals for the then clause range
|
|
64
|
+
#
|
|
65
|
+
before_then_clause_loc = then_clause.location.expression.adjust(begin_pos: -1)
|
|
66
|
+
before_then_clause_pos = Position.new(before_then_clause_loc.line, before_then_clause_loc.column)
|
|
67
|
+
true_ranges << Range.new(before_then_clause_pos,
|
|
68
|
+
get_node_end_position(then_clause))
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
process_conditional(conditional_node, true_ranges)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
class << self
|
|
75
|
+
include Logging
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Find a variable pin by name and where it is used.
|
|
79
|
+
#
|
|
80
|
+
# Resolves our most specific view of this variable's type by
|
|
81
|
+
# preferring pins created by flow-sensitive typing when we have
|
|
82
|
+
# them based on the Closure and Location.
|
|
83
|
+
#
|
|
84
|
+
# @param pins [Array<Pin::LocalVariable>]
|
|
85
|
+
# @param name [String]
|
|
86
|
+
# @param closure [Pin::Closure]
|
|
87
|
+
# @param location [Location]
|
|
88
|
+
#
|
|
89
|
+
# @return [Array<Pin::LocalVariable>]
|
|
90
|
+
def self.visible_pins(pins, name, closure, location)
|
|
91
|
+
logger.debug { "FlowSensitiveTyping#visible_pins(name=#{name}, closure=#{closure}, location=#{location})" }
|
|
92
|
+
pins_with_name = pins.select { |p| p.name == name }
|
|
93
|
+
if pins_with_name.empty?
|
|
94
|
+
logger.debug { "FlowSensitiveTyping#visible_pins(name=#{name}, closure=#{closure}, location=#{location}) => [] - no pins with name" }
|
|
95
|
+
return []
|
|
96
|
+
end
|
|
97
|
+
pins_with_specific_visibility = pins.select { |p| p.name == name && p.presence && p.visible_at?(closure, location) }
|
|
98
|
+
if pins_with_specific_visibility.empty?
|
|
99
|
+
logger.debug { "FlowSensitiveTyping#visible_pins(name=#{name}, closure=#{closure}, location=#{location}) => #{pins_with_name} - no pins with specific visibility" }
|
|
100
|
+
return pins_with_name
|
|
101
|
+
end
|
|
102
|
+
visible_pins_specific_to_this_closure = pins_with_specific_visibility.select { |p| p.closure == closure }
|
|
103
|
+
if visible_pins_specific_to_this_closure.empty?
|
|
104
|
+
logger.debug { "FlowSensitiveTyping#visible_pins(name=#{name}, closure=#{closure}, location=#{location}) => #{pins_with_specific_visibility} - no visible pins specific to this closure (#{closure})}" }
|
|
105
|
+
return pins_with_specific_visibility
|
|
106
|
+
end
|
|
107
|
+
flow_defined_pins = pins_with_specific_visibility.select { |p| p.presence_certain? }
|
|
108
|
+
if flow_defined_pins.empty?
|
|
109
|
+
logger.debug { "FlowSensitiveTyping#visible_pins(name=#{name}, closure=#{closure}, location=#{location}) => #{visible_pins_specific_to_this_closure} - no flow-defined pins" }
|
|
110
|
+
return visible_pins_specific_to_this_closure
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
logger.debug { "FlowSensitiveTyping#visible_pins(name=#{name}, closure=#{closure}, location=#{location}) => #{flow_defined_pins}" }
|
|
114
|
+
|
|
115
|
+
flow_defined_pins
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
include Logging
|
|
119
|
+
|
|
120
|
+
private
|
|
121
|
+
|
|
122
|
+
# @param pin [Pin::LocalVariable]
|
|
123
|
+
# @param downcast_type_name [String]
|
|
124
|
+
# @param presence [Range]
|
|
125
|
+
#
|
|
126
|
+
# @return [void]
|
|
127
|
+
def add_downcast_local(pin, downcast_type_name, presence)
|
|
128
|
+
# @todo Create pin#update method
|
|
129
|
+
new_pin = Solargraph::Pin::LocalVariable.new(
|
|
130
|
+
location: pin.location,
|
|
131
|
+
closure: pin.closure,
|
|
132
|
+
name: pin.name,
|
|
133
|
+
assignment: pin.assignment,
|
|
134
|
+
comments: pin.comments,
|
|
135
|
+
presence: presence,
|
|
136
|
+
return_type: ComplexType.try_parse(downcast_type_name),
|
|
137
|
+
presence_certain: true,
|
|
138
|
+
source: :flow_sensitive_typing
|
|
139
|
+
)
|
|
140
|
+
locals.push(new_pin)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# @param facts_by_pin [Hash{Pin::LocalVariable => Array<Hash{Symbol => String}>}]
|
|
144
|
+
# @param presences [Array<Range>]
|
|
145
|
+
#
|
|
146
|
+
# @return [void]
|
|
147
|
+
def process_facts(facts_by_pin, presences)
|
|
148
|
+
#
|
|
149
|
+
# Add specialized locals for the rest of the block
|
|
150
|
+
#
|
|
151
|
+
facts_by_pin.each_pair do |pin, facts|
|
|
152
|
+
facts.each do |fact|
|
|
153
|
+
downcast_type_name = fact.fetch(:type)
|
|
154
|
+
presences.each do |presence|
|
|
155
|
+
add_downcast_local(pin, downcast_type_name, presence)
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# @param conditional_node [Parser::AST::Node]
|
|
162
|
+
# @param true_ranges [Array<Range>]
|
|
163
|
+
#
|
|
164
|
+
# @return [void]
|
|
165
|
+
def process_conditional(conditional_node, true_ranges)
|
|
166
|
+
if conditional_node.type == :send
|
|
167
|
+
process_isa(conditional_node, true_ranges)
|
|
168
|
+
elsif conditional_node.type == :and
|
|
169
|
+
process_and(conditional_node, true_ranges)
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# @param isa_node [Parser::AST::Node]
|
|
174
|
+
# @return [Array(String, String), nil]
|
|
175
|
+
def parse_isa(isa_node)
|
|
176
|
+
return unless isa_node&.type == :send && isa_node.children[1] == :is_a?
|
|
177
|
+
# Check if conditional node follows this pattern:
|
|
178
|
+
# s(:send,
|
|
179
|
+
# s(:send, nil, :foo), :is_a?,
|
|
180
|
+
# s(:const, nil, :Baz)),
|
|
181
|
+
isa_receiver = isa_node.children[0]
|
|
182
|
+
isa_type_name = type_name(isa_node.children[2])
|
|
183
|
+
return unless isa_type_name
|
|
184
|
+
|
|
185
|
+
# check if isa_receiver looks like this:
|
|
186
|
+
# s(:send, nil, :foo)
|
|
187
|
+
# and set variable_name to :foo
|
|
188
|
+
if isa_receiver&.type == :send && isa_receiver.children[0].nil? && isa_receiver.children[1].is_a?(Symbol)
|
|
189
|
+
variable_name = isa_receiver.children[1].to_s
|
|
190
|
+
end
|
|
191
|
+
# or like this:
|
|
192
|
+
# (lvar :repr)
|
|
193
|
+
variable_name = isa_receiver.children[0].to_s if isa_receiver&.type == :lvar
|
|
194
|
+
return unless variable_name
|
|
195
|
+
|
|
196
|
+
[isa_type_name, variable_name]
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# @param variable_name [String]
|
|
200
|
+
# @param position [Position]
|
|
201
|
+
#
|
|
202
|
+
# @return [Solargraph::Pin::LocalVariable, nil]
|
|
203
|
+
def find_local(variable_name, position)
|
|
204
|
+
pins = locals.select { |pin| pin.name == variable_name && pin.presence.include?(position) }
|
|
205
|
+
return unless pins.length == 1
|
|
206
|
+
pins.first
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# @param isa_node [Parser::AST::Node]
|
|
210
|
+
# @param true_presences [Array<Range>]
|
|
211
|
+
#
|
|
212
|
+
# @return [void]
|
|
213
|
+
def process_isa(isa_node, true_presences)
|
|
214
|
+
isa_type_name, variable_name = parse_isa(isa_node)
|
|
215
|
+
return if variable_name.nil? || variable_name.empty?
|
|
216
|
+
isa_position = Range.from_node(isa_node).start
|
|
217
|
+
|
|
218
|
+
pin = find_local(variable_name, isa_position)
|
|
219
|
+
return unless pin
|
|
220
|
+
|
|
221
|
+
if_true = {}
|
|
222
|
+
if_true[pin] ||= []
|
|
223
|
+
if_true[pin] << { type: isa_type_name }
|
|
224
|
+
process_facts(if_true, true_presences)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# @param node [Parser::AST::Node]
|
|
228
|
+
#
|
|
229
|
+
# @return [String, nil]
|
|
230
|
+
def type_name(node)
|
|
231
|
+
# e.g.,
|
|
232
|
+
# s(:const, nil, :Baz)
|
|
233
|
+
return unless node&.type == :const
|
|
234
|
+
module_node = node.children[0]
|
|
235
|
+
class_node = node.children[1]
|
|
236
|
+
|
|
237
|
+
return class_node.to_s if module_node.nil?
|
|
238
|
+
|
|
239
|
+
module_type_name = type_name(module_node)
|
|
240
|
+
return unless module_type_name
|
|
241
|
+
|
|
242
|
+
"#{module_type_name}::#{class_node}"
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# @param clause_node [Parser::AST::Node]
|
|
246
|
+
def always_breaks?(clause_node)
|
|
247
|
+
clause_node&.type == :break
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
attr_reader :locals
|
|
251
|
+
|
|
252
|
+
attr_reader :enclosing_breakable_pin
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
end
|