solargraph 0.58.0 → 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 (166) hide show
  1. checksums.yaml +4 -4
  2. data/.envrc +3 -0
  3. data/.gitattributes +2 -0
  4. data/.github/workflows/linting.yml +4 -5
  5. data/.github/workflows/plugins.yml +40 -36
  6. data/.github/workflows/rspec.yml +45 -13
  7. data/.github/workflows/typecheck.yml +2 -2
  8. data/.rubocop_todo.yml +27 -49
  9. data/CHANGELOG.md +3 -0
  10. data/README.md +3 -3
  11. data/Rakefile +1 -0
  12. data/bin/solargraph +8 -8
  13. data/lib/solargraph/api_map/cache.rb +110 -110
  14. data/lib/solargraph/api_map/constants.rb +289 -279
  15. data/lib/solargraph/api_map/index.rb +204 -193
  16. data/lib/solargraph/api_map/source_to_yard.rb +109 -97
  17. data/lib/solargraph/api_map/store.rb +387 -384
  18. data/lib/solargraph/api_map.rb +1000 -945
  19. data/lib/solargraph/complex_type/conformance.rb +176 -0
  20. data/lib/solargraph/complex_type/type_methods.rb +242 -228
  21. data/lib/solargraph/complex_type/unique_type.rb +632 -482
  22. data/lib/solargraph/complex_type.rb +549 -444
  23. data/lib/solargraph/convention/data_definition/data_definition_node.rb +93 -91
  24. data/lib/solargraph/convention/data_definition.rb +108 -105
  25. data/lib/solargraph/convention/struct_definition/struct_assignment_node.rb +62 -61
  26. data/lib/solargraph/convention/struct_definition/struct_definition_node.rb +103 -102
  27. data/lib/solargraph/convention/struct_definition.rb +168 -164
  28. data/lib/solargraph/diagnostics/require_not_found.rb +54 -53
  29. data/lib/solargraph/diagnostics/rubocop.rb +119 -118
  30. data/lib/solargraph/diagnostics/rubocop_helpers.rb +70 -68
  31. data/lib/solargraph/diagnostics/type_check.rb +56 -55
  32. data/lib/solargraph/doc_map.rb +200 -439
  33. data/lib/solargraph/equality.rb +34 -34
  34. data/lib/solargraph/gem_pins.rb +97 -98
  35. data/lib/solargraph/language_server/host/dispatch.rb +131 -130
  36. data/lib/solargraph/language_server/host/message_worker.rb +113 -112
  37. data/lib/solargraph/language_server/host/sources.rb +100 -99
  38. data/lib/solargraph/language_server/host.rb +883 -878
  39. data/lib/solargraph/language_server/message/extended/check_gem_version.rb +109 -114
  40. data/lib/solargraph/language_server/message/extended/document.rb +24 -23
  41. data/lib/solargraph/language_server/message/text_document/completion.rb +58 -56
  42. data/lib/solargraph/language_server/message/text_document/definition.rb +42 -40
  43. data/lib/solargraph/language_server/message/text_document/document_symbol.rb +28 -26
  44. data/lib/solargraph/language_server/message/text_document/formatting.rb +150 -148
  45. data/lib/solargraph/language_server/message/text_document/hover.rb +60 -58
  46. data/lib/solargraph/language_server/message/text_document/signature_help.rb +25 -24
  47. data/lib/solargraph/language_server/message/text_document/type_definition.rb +27 -25
  48. data/lib/solargraph/language_server/message/workspace/workspace_symbol.rb +25 -23
  49. data/lib/solargraph/library.rb +729 -683
  50. data/lib/solargraph/location.rb +87 -82
  51. data/lib/solargraph/logging.rb +57 -37
  52. data/lib/solargraph/parser/comment_ripper.rb +76 -69
  53. data/lib/solargraph/parser/flow_sensitive_typing.rb +483 -255
  54. data/lib/solargraph/parser/node_processor/base.rb +122 -92
  55. data/lib/solargraph/parser/node_processor.rb +63 -62
  56. data/lib/solargraph/parser/parser_gem/class_methods.rb +167 -149
  57. data/lib/solargraph/parser/parser_gem/node_chainer.rb +191 -166
  58. data/lib/solargraph/parser/parser_gem/node_methods.rb +506 -486
  59. data/lib/solargraph/parser/parser_gem/node_processors/and_node.rb +22 -22
  60. data/lib/solargraph/parser/parser_gem/node_processors/args_node.rb +61 -59
  61. data/lib/solargraph/parser/parser_gem/node_processors/begin_node.rb +24 -15
  62. data/lib/solargraph/parser/parser_gem/node_processors/block_node.rb +46 -46
  63. data/lib/solargraph/parser/parser_gem/node_processors/def_node.rb +60 -53
  64. data/lib/solargraph/parser/parser_gem/node_processors/if_node.rb +53 -23
  65. data/lib/solargraph/parser/parser_gem/node_processors/ivasgn_node.rb +41 -40
  66. data/lib/solargraph/parser/parser_gem/node_processors/lvasgn_node.rb +30 -29
  67. data/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb +61 -59
  68. data/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb +98 -98
  69. data/lib/solargraph/parser/parser_gem/node_processors/or_node.rb +22 -0
  70. data/lib/solargraph/parser/parser_gem/node_processors/orasgn_node.rb +17 -17
  71. data/lib/solargraph/parser/parser_gem/node_processors/resbody_node.rb +39 -38
  72. data/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb +53 -52
  73. data/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +296 -291
  74. data/lib/solargraph/parser/parser_gem/node_processors/when_node.rb +23 -0
  75. data/lib/solargraph/parser/parser_gem/node_processors/while_node.rb +33 -29
  76. data/lib/solargraph/parser/parser_gem/node_processors.rb +74 -70
  77. data/lib/solargraph/parser/region.rb +75 -69
  78. data/lib/solargraph/parser/snippet.rb +17 -17
  79. data/lib/solargraph/pin/base.rb +761 -729
  80. data/lib/solargraph/pin/base_variable.rb +418 -126
  81. data/lib/solargraph/pin/block.rb +126 -104
  82. data/lib/solargraph/pin/breakable.rb +13 -9
  83. data/lib/solargraph/pin/callable.rb +278 -231
  84. data/lib/solargraph/pin/closure.rb +68 -72
  85. data/lib/solargraph/pin/common.rb +94 -79
  86. data/lib/solargraph/pin/compound_statement.rb +55 -0
  87. data/lib/solargraph/pin/conversions.rb +124 -123
  88. data/lib/solargraph/pin/delegated_method.rb +131 -120
  89. data/lib/solargraph/pin/documenting.rb +115 -114
  90. data/lib/solargraph/pin/instance_variable.rb +38 -34
  91. data/lib/solargraph/pin/keyword.rb +16 -20
  92. data/lib/solargraph/pin/local_variable.rb +31 -75
  93. data/lib/solargraph/pin/method.rb +720 -672
  94. data/lib/solargraph/pin/method_alias.rb +42 -34
  95. data/lib/solargraph/pin/namespace.rb +121 -115
  96. data/lib/solargraph/pin/parameter.rb +338 -275
  97. data/lib/solargraph/pin/proxy_type.rb +40 -39
  98. data/lib/solargraph/pin/reference/override.rb +47 -47
  99. data/lib/solargraph/pin/reference/superclass.rb +17 -15
  100. data/lib/solargraph/pin/reference.rb +41 -39
  101. data/lib/solargraph/pin/search.rb +62 -61
  102. data/lib/solargraph/pin/signature.rb +69 -61
  103. data/lib/solargraph/pin/symbol.rb +53 -53
  104. data/lib/solargraph/pin/until.rb +18 -18
  105. data/lib/solargraph/pin/while.rb +18 -18
  106. data/lib/solargraph/pin.rb +46 -44
  107. data/lib/solargraph/pin_cache.rb +665 -245
  108. data/lib/solargraph/position.rb +118 -119
  109. data/lib/solargraph/range.rb +112 -112
  110. data/lib/solargraph/rbs_map/conversions.rb +846 -823
  111. data/lib/solargraph/rbs_map/core_map.rb +65 -58
  112. data/lib/solargraph/rbs_map/stdlib_map.rb +72 -43
  113. data/lib/solargraph/rbs_map.rb +217 -163
  114. data/lib/solargraph/shell.rb +397 -352
  115. data/lib/solargraph/source/chain/call.rb +372 -337
  116. data/lib/solargraph/source/chain/constant.rb +28 -26
  117. data/lib/solargraph/source/chain/hash.rb +35 -34
  118. data/lib/solargraph/source/chain/if.rb +29 -28
  119. data/lib/solargraph/source/chain/instance_variable.rb +34 -13
  120. data/lib/solargraph/source/chain/literal.rb +53 -48
  121. data/lib/solargraph/source/chain/or.rb +31 -23
  122. data/lib/solargraph/source/chain.rb +294 -291
  123. data/lib/solargraph/source/change.rb +89 -82
  124. data/lib/solargraph/source/cursor.rb +172 -166
  125. data/lib/solargraph/source/encoding_fixes.rb +23 -23
  126. data/lib/solargraph/source/source_chainer.rb +204 -194
  127. data/lib/solargraph/source/updater.rb +59 -55
  128. data/lib/solargraph/source.rb +524 -498
  129. data/lib/solargraph/source_map/clip.rb +237 -226
  130. data/lib/solargraph/source_map/data.rb +37 -34
  131. data/lib/solargraph/source_map/mapper.rb +282 -259
  132. data/lib/solargraph/source_map.rb +220 -212
  133. data/lib/solargraph/type_checker/problem.rb +34 -32
  134. data/lib/solargraph/type_checker/rules.rb +157 -84
  135. data/lib/solargraph/type_checker.rb +895 -814
  136. data/lib/solargraph/version.rb +5 -5
  137. data/lib/solargraph/workspace/config.rb +257 -255
  138. data/lib/solargraph/workspace/gemspecs.rb +367 -0
  139. data/lib/solargraph/workspace/require_paths.rb +98 -97
  140. data/lib/solargraph/workspace.rb +362 -220
  141. data/lib/solargraph/yard_map/helpers.rb +45 -44
  142. data/lib/solargraph/yard_map/mapper/to_method.rb +134 -130
  143. data/lib/solargraph/yard_map/mapper/to_namespace.rb +32 -31
  144. data/lib/solargraph/yard_map/mapper.rb +84 -79
  145. data/lib/solargraph/yardoc.rb +97 -87
  146. data/lib/solargraph.rb +126 -105
  147. data/rbs/fills/rubygems/0/dependency.rbs +193 -0
  148. data/rbs/fills/tuple/tuple.rbs +28 -0
  149. data/rbs/shims/ast/0/node.rbs +5 -0
  150. data/rbs/shims/diff-lcs/1.5/diff-lcs.rbs +11 -0
  151. data/rbs_collection.yaml +1 -1
  152. data/solargraph.gemspec +2 -1
  153. metadata +23 -17
  154. data/lib/solargraph/type_checker/checks.rb +0 -124
  155. data/lib/solargraph/type_checker/param_def.rb +0 -37
  156. data/lib/solargraph/yard_map/to_method.rb +0 -89
  157. data/sig/shims/ast/0/node.rbs +0 -5
  158. /data/{sig → rbs}/shims/ast/2.4/.rbs_meta.yaml +0 -0
  159. /data/{sig → rbs}/shims/ast/2.4/ast.rbs +0 -0
  160. /data/{sig → rbs}/shims/parser/3.2.0.1/builders/default.rbs +0 -0
  161. /data/{sig → rbs}/shims/parser/3.2.0.1/manifest.yaml +0 -0
  162. /data/{sig → rbs}/shims/parser/3.2.0.1/parser.rbs +0 -0
  163. /data/{sig → rbs}/shims/parser/3.2.0.1/polyfill.rbs +0 -0
  164. /data/{sig → rbs}/shims/thor/1.2.0.1/.rbs_meta.yaml +0 -0
  165. /data/{sig → rbs}/shims/thor/1.2.0.1/manifest.yaml +0 -0
  166. /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