solargraph 0.58.1 → 0.59.0.dev.1

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 (162) hide show
  1. checksums.yaml +4 -4
  2. data/.envrc +3 -0
  3. data/.github/workflows/linting.yml +4 -5
  4. data/.github/workflows/plugins.yml +40 -36
  5. data/.github/workflows/rspec.yml +45 -13
  6. data/.github/workflows/typecheck.yml +2 -2
  7. data/.rubocop_todo.yml +27 -49
  8. data/README.md +3 -3
  9. data/Rakefile +1 -0
  10. data/lib/solargraph/api_map/cache.rb +110 -110
  11. data/lib/solargraph/api_map/constants.rb +289 -279
  12. data/lib/solargraph/api_map/index.rb +204 -193
  13. data/lib/solargraph/api_map/source_to_yard.rb +109 -97
  14. data/lib/solargraph/api_map/store.rb +387 -384
  15. data/lib/solargraph/api_map.rb +1000 -945
  16. data/lib/solargraph/complex_type/conformance.rb +176 -0
  17. data/lib/solargraph/complex_type/type_methods.rb +242 -228
  18. data/lib/solargraph/complex_type/unique_type.rb +632 -482
  19. data/lib/solargraph/complex_type.rb +549 -444
  20. data/lib/solargraph/convention/data_definition/data_definition_node.rb +93 -91
  21. data/lib/solargraph/convention/data_definition.rb +108 -105
  22. data/lib/solargraph/convention/struct_definition/struct_assignment_node.rb +62 -61
  23. data/lib/solargraph/convention/struct_definition/struct_definition_node.rb +103 -102
  24. data/lib/solargraph/convention/struct_definition.rb +168 -164
  25. data/lib/solargraph/diagnostics/require_not_found.rb +54 -53
  26. data/lib/solargraph/diagnostics/rubocop.rb +119 -118
  27. data/lib/solargraph/diagnostics/rubocop_helpers.rb +70 -68
  28. data/lib/solargraph/diagnostics/type_check.rb +56 -55
  29. data/lib/solargraph/doc_map.rb +200 -439
  30. data/lib/solargraph/equality.rb +34 -34
  31. data/lib/solargraph/gem_pins.rb +97 -98
  32. data/lib/solargraph/language_server/host/dispatch.rb +131 -130
  33. data/lib/solargraph/language_server/host/message_worker.rb +113 -112
  34. data/lib/solargraph/language_server/host/sources.rb +100 -99
  35. data/lib/solargraph/language_server/host.rb +883 -878
  36. data/lib/solargraph/language_server/message/extended/check_gem_version.rb +109 -114
  37. data/lib/solargraph/language_server/message/extended/document.rb +24 -23
  38. data/lib/solargraph/language_server/message/text_document/completion.rb +58 -56
  39. data/lib/solargraph/language_server/message/text_document/definition.rb +42 -40
  40. data/lib/solargraph/language_server/message/text_document/document_symbol.rb +28 -26
  41. data/lib/solargraph/language_server/message/text_document/formatting.rb +150 -148
  42. data/lib/solargraph/language_server/message/text_document/hover.rb +60 -58
  43. data/lib/solargraph/language_server/message/text_document/signature_help.rb +25 -24
  44. data/lib/solargraph/language_server/message/text_document/type_definition.rb +27 -25
  45. data/lib/solargraph/language_server/message/workspace/workspace_symbol.rb +25 -23
  46. data/lib/solargraph/library.rb +729 -683
  47. data/lib/solargraph/location.rb +87 -82
  48. data/lib/solargraph/logging.rb +57 -37
  49. data/lib/solargraph/parser/comment_ripper.rb +76 -69
  50. data/lib/solargraph/parser/flow_sensitive_typing.rb +483 -255
  51. data/lib/solargraph/parser/node_processor/base.rb +122 -92
  52. data/lib/solargraph/parser/node_processor.rb +63 -62
  53. data/lib/solargraph/parser/parser_gem/class_methods.rb +167 -149
  54. data/lib/solargraph/parser/parser_gem/node_chainer.rb +191 -166
  55. data/lib/solargraph/parser/parser_gem/node_methods.rb +506 -486
  56. data/lib/solargraph/parser/parser_gem/node_processors/and_node.rb +22 -22
  57. data/lib/solargraph/parser/parser_gem/node_processors/args_node.rb +61 -59
  58. data/lib/solargraph/parser/parser_gem/node_processors/begin_node.rb +24 -15
  59. data/lib/solargraph/parser/parser_gem/node_processors/block_node.rb +46 -46
  60. data/lib/solargraph/parser/parser_gem/node_processors/def_node.rb +60 -53
  61. data/lib/solargraph/parser/parser_gem/node_processors/if_node.rb +53 -23
  62. data/lib/solargraph/parser/parser_gem/node_processors/ivasgn_node.rb +41 -40
  63. data/lib/solargraph/parser/parser_gem/node_processors/lvasgn_node.rb +30 -29
  64. data/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb +61 -59
  65. data/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb +98 -98
  66. data/lib/solargraph/parser/parser_gem/node_processors/or_node.rb +22 -0
  67. data/lib/solargraph/parser/parser_gem/node_processors/orasgn_node.rb +17 -17
  68. data/lib/solargraph/parser/parser_gem/node_processors/resbody_node.rb +39 -38
  69. data/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb +53 -52
  70. data/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +296 -291
  71. data/lib/solargraph/parser/parser_gem/node_processors/when_node.rb +23 -0
  72. data/lib/solargraph/parser/parser_gem/node_processors/while_node.rb +33 -29
  73. data/lib/solargraph/parser/parser_gem/node_processors.rb +74 -70
  74. data/lib/solargraph/parser/region.rb +75 -69
  75. data/lib/solargraph/parser/snippet.rb +17 -17
  76. data/lib/solargraph/pin/base.rb +761 -729
  77. data/lib/solargraph/pin/base_variable.rb +418 -126
  78. data/lib/solargraph/pin/block.rb +126 -104
  79. data/lib/solargraph/pin/breakable.rb +13 -9
  80. data/lib/solargraph/pin/callable.rb +278 -231
  81. data/lib/solargraph/pin/closure.rb +68 -72
  82. data/lib/solargraph/pin/common.rb +94 -79
  83. data/lib/solargraph/pin/compound_statement.rb +55 -0
  84. data/lib/solargraph/pin/conversions.rb +124 -123
  85. data/lib/solargraph/pin/delegated_method.rb +131 -120
  86. data/lib/solargraph/pin/documenting.rb +115 -114
  87. data/lib/solargraph/pin/instance_variable.rb +38 -34
  88. data/lib/solargraph/pin/keyword.rb +16 -20
  89. data/lib/solargraph/pin/local_variable.rb +31 -75
  90. data/lib/solargraph/pin/method.rb +720 -672
  91. data/lib/solargraph/pin/method_alias.rb +42 -34
  92. data/lib/solargraph/pin/namespace.rb +121 -115
  93. data/lib/solargraph/pin/parameter.rb +338 -275
  94. data/lib/solargraph/pin/proxy_type.rb +40 -39
  95. data/lib/solargraph/pin/reference/override.rb +47 -47
  96. data/lib/solargraph/pin/reference/superclass.rb +17 -15
  97. data/lib/solargraph/pin/reference.rb +41 -39
  98. data/lib/solargraph/pin/search.rb +62 -61
  99. data/lib/solargraph/pin/signature.rb +69 -61
  100. data/lib/solargraph/pin/symbol.rb +53 -53
  101. data/lib/solargraph/pin/until.rb +18 -18
  102. data/lib/solargraph/pin/while.rb +18 -18
  103. data/lib/solargraph/pin.rb +46 -44
  104. data/lib/solargraph/pin_cache.rb +665 -245
  105. data/lib/solargraph/position.rb +118 -119
  106. data/lib/solargraph/range.rb +112 -112
  107. data/lib/solargraph/rbs_map/conversions.rb +846 -823
  108. data/lib/solargraph/rbs_map/core_map.rb +65 -58
  109. data/lib/solargraph/rbs_map/stdlib_map.rb +72 -43
  110. data/lib/solargraph/rbs_map.rb +217 -163
  111. data/lib/solargraph/shell.rb +397 -352
  112. data/lib/solargraph/source/chain/call.rb +372 -337
  113. data/lib/solargraph/source/chain/constant.rb +28 -26
  114. data/lib/solargraph/source/chain/hash.rb +35 -34
  115. data/lib/solargraph/source/chain/if.rb +29 -28
  116. data/lib/solargraph/source/chain/instance_variable.rb +34 -13
  117. data/lib/solargraph/source/chain/literal.rb +53 -48
  118. data/lib/solargraph/source/chain/or.rb +31 -23
  119. data/lib/solargraph/source/chain.rb +294 -291
  120. data/lib/solargraph/source/change.rb +89 -82
  121. data/lib/solargraph/source/cursor.rb +172 -166
  122. data/lib/solargraph/source/source_chainer.rb +204 -194
  123. data/lib/solargraph/source/updater.rb +59 -55
  124. data/lib/solargraph/source.rb +524 -498
  125. data/lib/solargraph/source_map/clip.rb +237 -226
  126. data/lib/solargraph/source_map/data.rb +37 -34
  127. data/lib/solargraph/source_map/mapper.rb +282 -259
  128. data/lib/solargraph/source_map.rb +220 -212
  129. data/lib/solargraph/type_checker/problem.rb +34 -32
  130. data/lib/solargraph/type_checker/rules.rb +157 -84
  131. data/lib/solargraph/type_checker.rb +895 -814
  132. data/lib/solargraph/version.rb +1 -1
  133. data/lib/solargraph/workspace/config.rb +257 -255
  134. data/lib/solargraph/workspace/gemspecs.rb +367 -0
  135. data/lib/solargraph/workspace/require_paths.rb +98 -97
  136. data/lib/solargraph/workspace.rb +362 -220
  137. data/lib/solargraph/yard_map/helpers.rb +45 -44
  138. data/lib/solargraph/yard_map/mapper/to_method.rb +134 -130
  139. data/lib/solargraph/yard_map/mapper/to_namespace.rb +32 -31
  140. data/lib/solargraph/yard_map/mapper.rb +84 -79
  141. data/lib/solargraph/yardoc.rb +97 -87
  142. data/lib/solargraph.rb +126 -105
  143. data/rbs/fills/rubygems/0/dependency.rbs +193 -0
  144. data/rbs/fills/tuple/tuple.rbs +28 -0
  145. data/rbs/shims/ast/0/node.rbs +5 -0
  146. data/rbs/shims/diff-lcs/1.5/diff-lcs.rbs +11 -0
  147. data/rbs_collection.yaml +1 -1
  148. data/solargraph.gemspec +2 -1
  149. metadata +22 -17
  150. data/lib/solargraph/type_checker/checks.rb +0 -124
  151. data/lib/solargraph/type_checker/param_def.rb +0 -37
  152. data/lib/solargraph/yard_map/to_method.rb +0 -89
  153. data/sig/shims/ast/0/node.rbs +0 -5
  154. /data/{sig → rbs}/shims/ast/2.4/.rbs_meta.yaml +0 -0
  155. /data/{sig → rbs}/shims/ast/2.4/ast.rbs +0 -0
  156. /data/{sig → rbs}/shims/parser/3.2.0.1/builders/default.rbs +0 -0
  157. /data/{sig → rbs}/shims/parser/3.2.0.1/manifest.yaml +0 -0
  158. /data/{sig → rbs}/shims/parser/3.2.0.1/parser.rbs +0 -0
  159. /data/{sig → rbs}/shims/parser/3.2.0.1/polyfill.rbs +0 -0
  160. /data/{sig → rbs}/shims/thor/1.2.0.1/.rbs_meta.yaml +0 -0
  161. /data/{sig → rbs}/shims/thor/1.2.0.1/manifest.yaml +0 -0
  162. /data/{sig → rbs}/shims/thor/1.2.0.1/thor.rbs +0 -0
@@ -1,255 +1,483 @@
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>]
7
+ # @param ivars [Array<Solargraph::Pin::InstanceVariable>]
8
+ # @param enclosing_breakable_pin [Solargraph::Pin::Breakable, nil]
9
+ # @param enclosing_compound_statement_pin [Solargraph::Pin::CompoundStatement, nil]
10
+ def initialize(locals, ivars, enclosing_breakable_pin, enclosing_compound_statement_pin)
11
+ @locals = locals
12
+ @ivars = ivars
13
+ @enclosing_breakable_pin = enclosing_breakable_pin
14
+ @enclosing_compound_statement_pin = enclosing_compound_statement_pin
15
+ end
16
+
17
+ # @param and_node [Parser::AST::Node]
18
+ # @param true_ranges [Array<Range>]
19
+ # @param false_ranges [Array<Range>]
20
+ #
21
+ # @return [void]
22
+ def process_and(and_node, true_ranges = [], false_ranges = [])
23
+ return unless and_node.type == :and
24
+
25
+ # @type [Parser::AST::Node]
26
+ lhs = and_node.children[0]
27
+ # @type [Parser::AST::Node]
28
+ rhs = and_node.children[1]
29
+
30
+ before_rhs_loc = rhs.location.expression.adjust(begin_pos: -1)
31
+ before_rhs_pos = Position.new(before_rhs_loc.line, before_rhs_loc.column)
32
+
33
+ rhs_presence = Range.new(before_rhs_pos,
34
+ get_node_end_position(rhs))
35
+
36
+ # can't assume if an and is false that every single condition
37
+ # is false, so don't provide any false ranges to assert facts
38
+ # on
39
+ process_expression(lhs, true_ranges + [rhs_presence], [])
40
+ process_expression(rhs, true_ranges, [])
41
+ end
42
+
43
+ # @param or_node [Parser::AST::Node]
44
+ # @param true_ranges [Array<Range>]
45
+ # @param false_ranges [Array<Range>]
46
+ #
47
+ # @return [void]
48
+ def process_or(or_node, true_ranges = [], false_ranges = [])
49
+ return unless or_node.type == :or
50
+
51
+ # @type [Parser::AST::Node]
52
+ lhs = or_node.children[0]
53
+ # @type [Parser::AST::Node]
54
+ rhs = or_node.children[1]
55
+
56
+ before_rhs_loc = rhs.location.expression.adjust(begin_pos: -1)
57
+ before_rhs_pos = Position.new(before_rhs_loc.line, before_rhs_loc.column)
58
+
59
+ rhs_presence = Range.new(before_rhs_pos,
60
+ get_node_end_position(rhs))
61
+
62
+ # can assume if an or is false that every single condition is
63
+ # false, so provide false ranges to assert facts on
64
+
65
+ # can't assume if an or is true that every single condition is
66
+ # true, so don't provide true ranges to assert facts on
67
+
68
+ process_expression(lhs, [], false_ranges + [rhs_presence])
69
+ process_expression(rhs, [], false_ranges)
70
+ end
71
+
72
+ # @param node [Parser::AST::Node]
73
+ # @param true_presences [Array<Range>]
74
+ # @param false_presences [Array<Range>]
75
+ #
76
+ # @return [void]
77
+ def process_calls(node, true_presences, false_presences)
78
+ return unless node.type == :send
79
+
80
+ process_isa(node, true_presences, false_presences)
81
+ process_nilp(node, true_presences, false_presences)
82
+ process_bang(node, true_presences, false_presences)
83
+ end
84
+
85
+ # @param if_node [Parser::AST::Node]
86
+ # @param true_ranges [Array<Range>]
87
+ # @param false_ranges [Array<Range>]
88
+ #
89
+ # @return [void]
90
+ def process_if(if_node, true_ranges = [], false_ranges = [])
91
+ return if if_node.type != :if
92
+
93
+ #
94
+ # See if we can refine a type based on the result of 'if foo.nil?'
95
+ #
96
+ # [3] pry(main)> require 'parser/current'; Parser::CurrentRuby.parse("if foo.is_a? Baz; then foo; else bar; end")
97
+ # => s(:if,
98
+ # s(:send,
99
+ # s(:send, nil, :foo), :is_a?,
100
+ # s(:const, nil, :Baz)),
101
+ # s(:send, nil, :foo),
102
+ # s(:send, nil, :bar))
103
+ # [4] pry(main)>
104
+ conditional_node = if_node.children[0]
105
+ # @type [Parser::AST::Node, nil]
106
+ then_clause = if_node.children[1]
107
+ # @type [Parser::AST::Node, nil]
108
+ else_clause = if_node.children[2]
109
+
110
+ unless enclosing_breakable_pin.nil?
111
+ rest_of_breakable_body = Range.new(get_node_end_position(if_node),
112
+ get_node_end_position(enclosing_breakable_pin.node))
113
+
114
+ if always_breaks?(then_clause)
115
+ false_ranges << rest_of_breakable_body
116
+ end
117
+
118
+ if always_breaks?(else_clause)
119
+ true_ranges << rest_of_breakable_body
120
+ end
121
+ end
122
+
123
+ unless enclosing_compound_statement_pin.node.nil?
124
+ rest_of_returnable_body = Range.new(get_node_end_position(if_node),
125
+ get_node_end_position(enclosing_compound_statement_pin.node))
126
+
127
+ #
128
+ # if one of the clauses always leaves the compound
129
+ # statement, we can assume things about the rest of the
130
+ # compound statement
131
+ #
132
+ if always_leaves_compound_statement?(then_clause)
133
+ false_ranges << rest_of_returnable_body
134
+ end
135
+
136
+ if always_leaves_compound_statement?(else_clause)
137
+ true_ranges << rest_of_returnable_body
138
+ end
139
+ end
140
+
141
+ unless then_clause.nil?
142
+ #
143
+ # If the condition is true we can assume things about the then clause
144
+ #
145
+ before_then_clause_loc = then_clause.location.expression.adjust(begin_pos: -1)
146
+ before_then_clause_pos = Position.new(before_then_clause_loc.line, before_then_clause_loc.column)
147
+ true_ranges << Range.new(before_then_clause_pos,
148
+ get_node_end_position(then_clause))
149
+ end
150
+
151
+ unless else_clause.nil?
152
+ #
153
+ # If the condition is true we can assume things about the else clause
154
+ #
155
+ before_else_clause_loc = else_clause.location.expression.adjust(begin_pos: -1)
156
+ before_else_clause_pos = Position.new(before_else_clause_loc.line, before_else_clause_loc.column)
157
+ false_ranges << Range.new(before_else_clause_pos,
158
+ get_node_end_position(else_clause))
159
+ end
160
+
161
+ process_expression(conditional_node, true_ranges, false_ranges)
162
+ end
163
+
164
+ # @param while_node [Parser::AST::Node]
165
+ # @param true_ranges [Array<Range>]
166
+ # @param false_ranges [Array<Range>]
167
+ #
168
+ # @return [void]
169
+ def process_while(while_node, true_ranges = [], false_ranges = [])
170
+ return if while_node.type != :while
171
+
172
+ #
173
+ # See if we can refine a type based on the result of 'if foo.nil?'
174
+ #
175
+ # [3] pry(main)> Parser::CurrentRuby.parse("while a; b; c; end")
176
+ # => s(:while,
177
+ # s(:send, nil, :a),
178
+ # s(:begin,
179
+ # s(:send, nil, :b),
180
+ # s(:send, nil, :c)))
181
+ # [4] pry(main)>
182
+ conditional_node = while_node.children[0]
183
+ # @type [Parser::AST::Node, nil]
184
+ do_clause = while_node.children[1]
185
+
186
+ unless do_clause.nil?
187
+ #
188
+ # If the condition is true we can assume things about the do clause
189
+ #
190
+ before_do_clause_loc = do_clause.location.expression.adjust(begin_pos: -1)
191
+ before_do_clause_pos = Position.new(before_do_clause_loc.line, before_do_clause_loc.column)
192
+ true_ranges << Range.new(before_do_clause_pos,
193
+ get_node_end_position(do_clause))
194
+ end
195
+
196
+ process_expression(conditional_node, true_ranges, false_ranges)
197
+ end
198
+
199
+ class << self
200
+ include Logging
201
+ end
202
+
203
+ include Logging
204
+
205
+ private
206
+
207
+ # @param pin [Pin::BaseVariable]
208
+ # @param presence [Range]
209
+ # @param downcast_type [ComplexType, nil]
210
+ # @param downcast_not_type [ComplexType, nil]
211
+ #
212
+ # @return [void]
213
+ def add_downcast_var(pin, presence:, downcast_type:, downcast_not_type:)
214
+ new_pin = pin.downcast(exclude_return_type: downcast_not_type,
215
+ intersection_return_type: downcast_type,
216
+ source: :flow_sensitive_typing,
217
+ presence: presence)
218
+ if pin.is_a?(Pin::LocalVariable)
219
+ locals.push(new_pin)
220
+ elsif pin.is_a?(Pin::InstanceVariable)
221
+ ivars.push(new_pin)
222
+ else
223
+ raise "Tried to add invalid pin type #{pin.class} in FlowSensitiveTyping"
224
+ end
225
+ end
226
+
227
+ # @param facts_by_pin [Hash{Pin::BaseVariable => Array<Hash{:type, :not_type => ComplexType}>}]
228
+ # @param presences [Array<Range>]
229
+ #
230
+ # @return [void]
231
+ def process_facts(facts_by_pin, presences)
232
+ #
233
+ # Add specialized vars for the rest of the block
234
+ #
235
+ facts_by_pin.each_pair do |pin, facts|
236
+ facts.each do |fact|
237
+ downcast_type = fact.fetch(:type, nil)
238
+ downcast_not_type = fact.fetch(:not_type, nil)
239
+ presences.each do |presence|
240
+ add_downcast_var(pin,
241
+ presence: presence,
242
+ downcast_type: downcast_type,
243
+ downcast_not_type: downcast_not_type)
244
+ end
245
+ end
246
+ end
247
+ end
248
+
249
+ # @param expression_node [Parser::AST::Node]
250
+ # @param true_ranges [Array<Range>]
251
+ # @param false_ranges [Array<Range>]
252
+ #
253
+ # @return [void]
254
+ def process_expression(expression_node, true_ranges, false_ranges)
255
+ process_calls(expression_node, true_ranges, false_ranges)
256
+ process_and(expression_node, true_ranges, false_ranges)
257
+ process_or(expression_node, true_ranges, false_ranges)
258
+ process_variable(expression_node, true_ranges, false_ranges)
259
+ end
260
+
261
+ # @param call_node [Parser::AST::Node]
262
+ # @param method_name [Symbol]
263
+ # @return [Array(String, String), nil] Tuple of rgument to
264
+ # function, then receiver of function if it's a variable,
265
+ # otherwise nil if no simple variable receiver
266
+ def parse_call(call_node, method_name)
267
+ return unless call_node&.type == :send && call_node.children[1] == method_name
268
+ # Check if conditional node follows this pattern:
269
+ # s(:send,
270
+ # s(:send, nil, :foo), :is_a?,
271
+ # s(:const, nil, :Baz)),
272
+ #
273
+ call_receiver = call_node.children[0]
274
+ call_arg = type_name(call_node.children[2])
275
+
276
+ # check if call_receiver looks like this:
277
+ # s(:send, nil, :foo)
278
+ # and set variable_name to :foo
279
+ if call_receiver&.type == :send && call_receiver.children[0].nil? && call_receiver.children[1].is_a?(Symbol)
280
+ variable_name = call_receiver.children[1].to_s
281
+ end
282
+ # or like this:
283
+ # (lvar :repr)
284
+ # @sg-ignore Need to look at Tuple#include? handling
285
+ variable_name = call_receiver.children[0].to_s if [:lvar, :ivar].include?(call_receiver&.type)
286
+ return unless variable_name
287
+
288
+ [call_arg, variable_name]
289
+ end
290
+
291
+ # @param isa_node [Parser::AST::Node]
292
+ # @return [Array(String, String), nil]
293
+ def parse_isa(isa_node)
294
+ call_type_name, variable_name = parse_call(isa_node, :is_a?)
295
+
296
+ return unless call_type_name
297
+
298
+ [call_type_name, variable_name]
299
+ end
300
+
301
+ # @param variable_name [String]
302
+ # @param position [Position]
303
+ #
304
+ # @sg-ignore Solargraph::Parser::FlowSensitiveTyping#find_var
305
+ # return type could not be inferred
306
+ # @return [Solargraph::Pin::LocalVariable, Solargraph::Pin::InstanceVariable, nil]
307
+ def find_var(variable_name, position)
308
+ if variable_name.start_with?('@')
309
+ # @sg-ignore flow sensitive typing needs to handle attrs
310
+ ivars.find { |ivar| ivar.name == variable_name && (!ivar.presence || ivar.presence.include?(position)) }
311
+ else
312
+ # @sg-ignore flow sensitive typing needs to handle attrs
313
+ locals.find { |pin| pin.name == variable_name && (!pin.presence || pin.presence.include?(position)) }
314
+ end
315
+ end
316
+
317
+ # @param isa_node [Parser::AST::Node]
318
+ # @param true_presences [Array<Range>]
319
+ # @param false_presences [Array<Range>]
320
+ #
321
+ # @return [void]
322
+ def process_isa(isa_node, true_presences, false_presences)
323
+ isa_type_name, variable_name = parse_isa(isa_node)
324
+ return if variable_name.nil? || variable_name.empty?
325
+ # @sg-ignore Need to add nil check here
326
+ isa_position = Range.from_node(isa_node).start
327
+
328
+ pin = find_var(variable_name, isa_position)
329
+ return unless pin
330
+
331
+ # @type Hash{Pin::BaseVariable => Array<Hash{Symbol => ComplexType}>}
332
+ if_true = {}
333
+ if_true[pin] ||= []
334
+ if_true[pin] << { type: ComplexType.parse(isa_type_name) }
335
+ process_facts(if_true, true_presences)
336
+
337
+ # @type Hash{Pin::BaseVariable => Array<Hash{Symbol => ComplexType}>}
338
+ if_false = {}
339
+ if_false[pin] ||= []
340
+ if_false[pin] << { not_type: ComplexType.parse(isa_type_name) }
341
+ process_facts(if_false, false_presences)
342
+ end
343
+
344
+ # @param nilp_node [Parser::AST::Node]
345
+ # @return [Array(String, String), nil]
346
+ def parse_nilp(nilp_node)
347
+ parse_call(nilp_node, :nil?)
348
+ end
349
+
350
+ # @param nilp_node [Parser::AST::Node]
351
+ # @param true_presences [Array<Range>]
352
+ # @param false_presences [Array<Range>]
353
+ #
354
+ # @return [void]
355
+ def process_nilp(nilp_node, true_presences, false_presences)
356
+ nilp_arg, variable_name = parse_nilp(nilp_node)
357
+ return if variable_name.nil? || variable_name.empty?
358
+ # if .nil? got an argument, move on, this isn't the situation
359
+ # we're looking for and typechecking will cover any invalid
360
+ # ones
361
+ return unless nilp_arg.nil?
362
+ # @sg-ignore Need to add nil check here
363
+ nilp_position = Range.from_node(nilp_node).start
364
+
365
+ pin = find_var(variable_name, nilp_position)
366
+ return unless pin
367
+
368
+ # @type Hash{Pin::LocalVariable => Array<Hash{Symbol => ComplexType}>}
369
+ if_true = {}
370
+ if_true[pin] ||= []
371
+ if_true[pin] << { type: ComplexType::NIL }
372
+ process_facts(if_true, true_presences)
373
+
374
+ # @type Hash{Pin::LocalVariable => Array<Hash{Symbol => ComplexType}>}
375
+ if_false = {}
376
+ if_false[pin] ||= []
377
+ if_false[pin] << { not_type: ComplexType::NIL }
378
+ process_facts(if_false, false_presences)
379
+ end
380
+
381
+ # @param bang_node [Parser::AST::Node]
382
+ # @return [Array(String, String), nil]
383
+ def parse_bang(bang_node)
384
+ parse_call(bang_node, :!)
385
+ end
386
+
387
+ # @param bang_node [Parser::AST::Node]
388
+ # @param true_presences [Array<Range>]
389
+ # @param false_presences [Array<Range>]
390
+ #
391
+ # @return [void]
392
+ def process_bang(bang_node, true_presences, false_presences)
393
+ # pry(main)> require 'parser/current'; Parser::CurrentRuby.parse("!2")
394
+ # => s(:send,
395
+ # s(:int, 2), :!)
396
+ # end
397
+ return unless bang_node.type == :send && bang_node.children[1] == :!
398
+
399
+ receiver = bang_node.children[0]
400
+
401
+ # swap the two presences
402
+ process_expression(receiver, false_presences, true_presences)
403
+ end
404
+
405
+ # @param var_node [Parser::AST::Node]
406
+ #
407
+ # @return [String, nil] Variable name referenced
408
+ def parse_variable(var_node)
409
+ return if var_node.children.length != 1
410
+
411
+ var_node.children[0]&.to_s
412
+ end
413
+
414
+ # @return [void]
415
+ # @param node [Parser::AST::Node]
416
+ # @param true_presences [Array<Range>]
417
+ # @param false_presences [Array<Range>]
418
+ def process_variable(node, true_presences, false_presences)
419
+ return unless [:lvar, :ivar, :cvar, :gvar].include?(node.type)
420
+
421
+ variable_name = parse_variable(node)
422
+ return if variable_name.nil?
423
+
424
+ # @sg-ignore Need to add nil check here
425
+ var_position = Range.from_node(node).start
426
+
427
+ pin = find_var(variable_name, var_position)
428
+ return unless pin
429
+
430
+ # @type Hash{Pin::LocalVariable => Array<Hash{Symbol => ComplexType}>}
431
+ if_true = {}
432
+ if_true[pin] ||= []
433
+ if_true[pin] << { not_type: ComplexType::NIL }
434
+ process_facts(if_true, true_presences)
435
+
436
+ # @type Hash{Pin::LocalVariable => Array<Hash{Symbol => ComplexType}>}
437
+ if_false = {}
438
+ if_false[pin] ||= []
439
+ if_false[pin] << { type: ComplexType.parse('nil, false') }
440
+ process_facts(if_false, false_presences)
441
+ end
442
+
443
+ # @param node [Parser::AST::Node]
444
+ #
445
+ # @return [String, nil]
446
+ def type_name(node)
447
+ # e.g.,
448
+ # s(:const, nil, :Baz)
449
+ return unless node&.type == :const
450
+ # @type [Parser::AST::Node, nil]
451
+ module_node = node.children[0]
452
+ # @type [Parser::AST::Node, nil]
453
+ class_node = node.children[1]
454
+
455
+ return class_node.to_s if module_node.nil?
456
+
457
+ module_type_name = type_name(module_node)
458
+ return unless module_type_name
459
+
460
+ "#{module_type_name}::#{class_node}"
461
+ end
462
+
463
+ # @param clause_node [Parser::AST::Node, nil]
464
+ # @sg-ignore need boolish support for ? methods
465
+ def always_breaks?(clause_node)
466
+ clause_node&.type == :break
467
+ end
468
+
469
+ # @param clause_node [Parser::AST::Node, nil]
470
+ def always_leaves_compound_statement?(clause_node)
471
+ # https://docs.ruby-lang.org/en/2.2.0/keywords_rdoc.html
472
+ # @sg-ignore Need to look at Tuple#include? handling
473
+ [:return, :raise, :next, :redo, :retry].include?(clause_node&.type)
474
+ end
475
+
476
+ attr_reader :locals
477
+
478
+ attr_reader :ivars
479
+
480
+ attr_reader :enclosing_breakable_pin, :enclosing_compound_statement_pin
481
+ end
482
+ end
483
+ end