solargraph 0.58.2 → 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 (154) 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/.gitignore +0 -1
  8. data/.rubocop_todo.yml +27 -49
  9. data/CHANGELOG.md +1 -7
  10. data/README.md +3 -3
  11. data/Rakefile +1 -0
  12. data/lib/solargraph/api_map/cache.rb +3 -3
  13. data/lib/solargraph/api_map/constants.rb +13 -3
  14. data/lib/solargraph/api_map/index.rb +22 -11
  15. data/lib/solargraph/api_map/source_to_yard.rb +13 -1
  16. data/lib/solargraph/api_map/store.rb +11 -8
  17. data/lib/solargraph/api_map.rb +105 -50
  18. data/lib/solargraph/complex_type/conformance.rb +176 -0
  19. data/lib/solargraph/complex_type/type_methods.rb +16 -2
  20. data/lib/solargraph/complex_type/unique_type.rb +170 -20
  21. data/lib/solargraph/complex_type.rb +119 -14
  22. data/lib/solargraph/convention/data_definition/data_definition_node.rb +3 -1
  23. data/lib/solargraph/convention/data_definition.rb +4 -1
  24. data/lib/solargraph/convention/struct_definition/struct_assignment_node.rb +1 -0
  25. data/lib/solargraph/convention/struct_definition/struct_definition_node.rb +1 -0
  26. data/lib/solargraph/convention/struct_definition.rb +5 -1
  27. data/lib/solargraph/diagnostics/require_not_found.rb +1 -0
  28. data/lib/solargraph/diagnostics/rubocop.rb +1 -0
  29. data/lib/solargraph/diagnostics/rubocop_helpers.rb +2 -0
  30. data/lib/solargraph/diagnostics/type_check.rb +1 -0
  31. data/lib/solargraph/doc_map.rb +134 -373
  32. data/lib/solargraph/equality.rb +1 -1
  33. data/lib/solargraph/gem_pins.rb +14 -15
  34. data/lib/solargraph/language_server/host/diagnoser.rb +89 -89
  35. data/lib/solargraph/language_server/host/dispatch.rb +1 -0
  36. data/lib/solargraph/language_server/host/message_worker.rb +2 -1
  37. data/lib/solargraph/language_server/host/sources.rb +1 -0
  38. data/lib/solargraph/language_server/host.rb +6 -1
  39. data/lib/solargraph/language_server/message/extended/check_gem_version.rb +2 -7
  40. data/lib/solargraph/language_server/message/extended/document.rb +1 -0
  41. data/lib/solargraph/language_server/message/text_document/completion.rb +2 -0
  42. data/lib/solargraph/language_server/message/text_document/definition.rb +2 -0
  43. data/lib/solargraph/language_server/message/text_document/document_symbol.rb +2 -0
  44. data/lib/solargraph/language_server/message/text_document/formatting.rb +2 -0
  45. data/lib/solargraph/language_server/message/text_document/hover.rb +2 -0
  46. data/lib/solargraph/language_server/message/text_document/signature_help.rb +1 -0
  47. data/lib/solargraph/language_server/message/text_document/type_definition.rb +2 -0
  48. data/lib/solargraph/language_server/message/workspace/workspace_symbol.rb +2 -0
  49. data/lib/solargraph/library.rb +59 -13
  50. data/lib/solargraph/location.rb +9 -4
  51. data/lib/solargraph/logging.rb +21 -1
  52. data/lib/solargraph/parser/comment_ripper.rb +7 -0
  53. data/lib/solargraph/parser/flow_sensitive_typing.rb +330 -102
  54. data/lib/solargraph/parser/node_processor/base.rb +32 -2
  55. data/lib/solargraph/parser/node_processor.rb +7 -6
  56. data/lib/solargraph/parser/parser_gem/class_methods.rb +28 -10
  57. data/lib/solargraph/parser/parser_gem/node_chainer.rb +31 -6
  58. data/lib/solargraph/parser/parser_gem/node_methods.rb +27 -7
  59. data/lib/solargraph/parser/parser_gem/node_processors/and_node.rb +4 -4
  60. data/lib/solargraph/parser/parser_gem/node_processors/args_node.rb +2 -0
  61. data/lib/solargraph/parser/parser_gem/node_processors/begin_node.rb +9 -0
  62. data/lib/solargraph/parser/parser_gem/node_processors/block_node.rb +11 -11
  63. data/lib/solargraph/parser/parser_gem/node_processors/def_node.rb +7 -0
  64. data/lib/solargraph/parser/parser_gem/node_processors/if_node.rb +36 -6
  65. data/lib/solargraph/parser/parser_gem/node_processors/ivasgn_node.rb +3 -2
  66. data/lib/solargraph/parser/parser_gem/node_processors/lvasgn_node.rb +1 -0
  67. data/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb +3 -1
  68. data/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb +2 -2
  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 +1 -1
  71. data/lib/solargraph/parser/parser_gem/node_processors/resbody_node.rb +2 -1
  72. data/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb +1 -0
  73. data/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +12 -7
  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 +5 -1
  76. data/lib/solargraph/parser/parser_gem/node_processors.rb +4 -0
  77. data/lib/solargraph/parser/region.rb +9 -3
  78. data/lib/solargraph/parser/snippet.rb +1 -1
  79. data/lib/solargraph/pin/base.rb +53 -21
  80. data/lib/solargraph/pin/base_variable.rb +312 -20
  81. data/lib/solargraph/pin/block.rb +26 -4
  82. data/lib/solargraph/pin/breakable.rb +5 -1
  83. data/lib/solargraph/pin/callable.rb +50 -3
  84. data/lib/solargraph/pin/closure.rb +2 -6
  85. data/lib/solargraph/pin/common.rb +20 -5
  86. data/lib/solargraph/pin/compound_statement.rb +55 -0
  87. data/lib/solargraph/pin/conversions.rb +2 -1
  88. data/lib/solargraph/pin/delegated_method.rb +15 -4
  89. data/lib/solargraph/pin/documenting.rb +1 -0
  90. data/lib/solargraph/pin/instance_variable.rb +5 -1
  91. data/lib/solargraph/pin/keyword.rb +0 -4
  92. data/lib/solargraph/pin/local_variable.rb +13 -57
  93. data/lib/solargraph/pin/method.rb +90 -42
  94. data/lib/solargraph/pin/method_alias.rb +8 -0
  95. data/lib/solargraph/pin/namespace.rb +7 -1
  96. data/lib/solargraph/pin/parameter.rb +76 -13
  97. data/lib/solargraph/pin/proxy_type.rb +2 -1
  98. data/lib/solargraph/pin/reference/override.rb +1 -1
  99. data/lib/solargraph/pin/reference/superclass.rb +2 -0
  100. data/lib/solargraph/pin/reference.rb +2 -0
  101. data/lib/solargraph/pin/search.rb +1 -0
  102. data/lib/solargraph/pin/signature.rb +8 -0
  103. data/lib/solargraph/pin/symbol.rb +1 -1
  104. data/lib/solargraph/pin/until.rb +1 -1
  105. data/lib/solargraph/pin/while.rb +1 -1
  106. data/lib/solargraph/pin.rb +2 -0
  107. data/lib/solargraph/pin_cache.rb +477 -57
  108. data/lib/solargraph/position.rb +12 -26
  109. data/lib/solargraph/range.rb +6 -6
  110. data/lib/solargraph/rbs_map/conversions.rb +33 -10
  111. data/lib/solargraph/rbs_map/core_map.rb +24 -17
  112. data/lib/solargraph/rbs_map/stdlib_map.rb +34 -5
  113. data/lib/solargraph/rbs_map.rb +74 -20
  114. data/lib/solargraph/shell.rb +73 -28
  115. data/lib/solargraph/source/chain/call.rb +52 -17
  116. data/lib/solargraph/source/chain/constant.rb +2 -0
  117. data/lib/solargraph/source/chain/hash.rb +1 -0
  118. data/lib/solargraph/source/chain/if.rb +1 -0
  119. data/lib/solargraph/source/chain/instance_variable.rb +22 -1
  120. data/lib/solargraph/source/chain/literal.rb +5 -0
  121. data/lib/solargraph/source/chain/or.rb +9 -1
  122. data/lib/solargraph/source/chain.rb +25 -22
  123. data/lib/solargraph/source/change.rb +9 -2
  124. data/lib/solargraph/source/cursor.rb +7 -1
  125. data/lib/solargraph/source/source_chainer.rb +13 -3
  126. data/lib/solargraph/source/updater.rb +4 -0
  127. data/lib/solargraph/source.rb +33 -7
  128. data/lib/solargraph/source_map/clip.rb +13 -2
  129. data/lib/solargraph/source_map/data.rb +4 -1
  130. data/lib/solargraph/source_map/mapper.rb +24 -1
  131. data/lib/solargraph/source_map.rb +14 -6
  132. data/lib/solargraph/type_checker/problem.rb +3 -1
  133. data/lib/solargraph/type_checker/rules.rb +75 -2
  134. data/lib/solargraph/type_checker.rb +111 -30
  135. data/lib/solargraph/version.rb +1 -1
  136. data/lib/solargraph/workspace/config.rb +3 -1
  137. data/lib/solargraph/workspace/gemspecs.rb +367 -0
  138. data/lib/solargraph/workspace/require_paths.rb +1 -0
  139. data/lib/solargraph/workspace.rb +158 -16
  140. data/lib/solargraph/yard_map/helpers.rb +2 -1
  141. data/lib/solargraph/yard_map/mapper/to_method.rb +5 -1
  142. data/lib/solargraph/yard_map/mapper/to_namespace.rb +1 -0
  143. data/lib/solargraph/yard_map/mapper.rb +5 -0
  144. data/lib/solargraph/yardoc.rb +33 -23
  145. data/lib/solargraph.rb +24 -3
  146. data/rbs/fills/rubygems/0/dependency.rbs +193 -0
  147. data/rbs/fills/tuple/tuple.rbs +28 -0
  148. data/rbs/shims/ast/0/node.rbs +1 -1
  149. data/rbs/shims/diff-lcs/1.5/diff-lcs.rbs +11 -0
  150. data/solargraph.gemspec +2 -1
  151. metadata +12 -7
  152. data/lib/solargraph/type_checker/checks.rb +0 -124
  153. data/lib/solargraph/type_checker/param_def.rb +0 -37
  154. data/lib/solargraph/yard_map/to_method.rb +0 -89
@@ -3,18 +3,25 @@ module Solargraph
3
3
  class FlowSensitiveTyping
4
4
  include Solargraph::Parser::NodeMethods
5
5
 
6
- # @param locals [Array<Solargraph::Pin::LocalVariable, Solargraph::Pin::Parameter>]
6
+ # @param locals [Array<Solargraph::Pin::LocalVariable>]
7
+ # @param ivars [Array<Solargraph::Pin::InstanceVariable>]
7
8
  # @param enclosing_breakable_pin [Solargraph::Pin::Breakable, nil]
8
- def initialize(locals, enclosing_breakable_pin = nil)
9
+ # @param enclosing_compound_statement_pin [Solargraph::Pin::CompoundStatement, nil]
10
+ def initialize(locals, ivars, enclosing_breakable_pin, enclosing_compound_statement_pin)
9
11
  @locals = locals
12
+ @ivars = ivars
10
13
  @enclosing_breakable_pin = enclosing_breakable_pin
14
+ @enclosing_compound_statement_pin = enclosing_compound_statement_pin
11
15
  end
12
16
 
13
17
  # @param and_node [Parser::AST::Node]
14
18
  # @param true_ranges [Array<Range>]
19
+ # @param false_ranges [Array<Range>]
15
20
  #
16
21
  # @return [void]
17
- def process_and(and_node, true_ranges = [])
22
+ def process_and(and_node, true_ranges = [], false_ranges = [])
23
+ return unless and_node.type == :and
24
+
18
25
  # @type [Parser::AST::Node]
19
26
  lhs = and_node.children[0]
20
27
  # @type [Parser::AST::Node]
@@ -25,13 +32,64 @@ module Solargraph
25
32
 
26
33
  rhs_presence = Range.new(before_rhs_pos,
27
34
  get_node_end_position(rhs))
28
- process_isa(lhs, true_ranges + [rhs_presence])
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)
29
83
  end
30
84
 
31
85
  # @param if_node [Parser::AST::Node]
86
+ # @param true_ranges [Array<Range>]
87
+ # @param false_ranges [Array<Range>]
32
88
  #
33
89
  # @return [void]
34
- def process_if(if_node)
90
+ def process_if(if_node, true_ranges = [], false_ranges = [])
91
+ return if if_node.type != :if
92
+
35
93
  #
36
94
  # See if we can refine a type based on the result of 'if foo.nil?'
37
95
  #
@@ -44,23 +102,45 @@ module Solargraph
44
102
  # s(:send, nil, :bar))
45
103
  # [4] pry(main)>
46
104
  conditional_node = if_node.children[0]
47
- # @type [Parser::AST::Node]
105
+ # @type [Parser::AST::Node, nil]
48
106
  then_clause = if_node.children[1]
49
- # @type [Parser::AST::Node]
107
+ # @type [Parser::AST::Node, nil]
50
108
  else_clause = if_node.children[2]
51
109
 
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))
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)
57
119
  true_ranges << rest_of_breakable_body
58
120
  end
59
121
  end
60
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
+
61
141
  unless then_clause.nil?
62
142
  #
63
- # Add specialized locals for the then clause range
143
+ # If the condition is true we can assume things about the then clause
64
144
  #
65
145
  before_then_clause_loc = then_clause.location.expression.adjust(begin_pos: -1)
66
146
  before_then_clause_pos = Position.new(before_then_clause_loc.line, before_then_clause_loc.column)
@@ -68,160 +148,296 @@ module Solargraph
68
148
  get_node_end_position(then_clause))
69
149
  end
70
150
 
71
- process_conditional(conditional_node, true_ranges)
72
- end
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
73
160
 
74
- class << self
75
- include Logging
161
+ process_expression(conditional_node, true_ranges, false_ranges)
76
162
  end
77
163
 
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]
164
+ # @param while_node [Parser::AST::Node]
165
+ # @param true_ranges [Array<Range>]
166
+ # @param false_ranges [Array<Range>]
88
167
  #
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
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))
111
194
  end
112
195
 
113
- logger.debug { "FlowSensitiveTyping#visible_pins(name=#{name}, closure=#{closure}, location=#{location}) => #{flow_defined_pins}" }
196
+ process_expression(conditional_node, true_ranges, false_ranges)
197
+ end
114
198
 
115
- flow_defined_pins
199
+ class << self
200
+ include Logging
116
201
  end
117
202
 
118
203
  include Logging
119
204
 
120
205
  private
121
206
 
122
- # @param pin [Pin::LocalVariable]
123
- # @param downcast_type_name [String]
207
+ # @param pin [Pin::BaseVariable]
124
208
  # @param presence [Range]
209
+ # @param downcast_type [ComplexType, nil]
210
+ # @param downcast_not_type [ComplexType, nil]
125
211
  #
126
212
  # @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}>}]
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}>}]
144
228
  # @param presences [Array<Range>]
145
229
  #
146
230
  # @return [void]
147
231
  def process_facts(facts_by_pin, presences)
148
232
  #
149
- # Add specialized locals for the rest of the block
233
+ # Add specialized vars for the rest of the block
150
234
  #
151
235
  facts_by_pin.each_pair do |pin, facts|
152
236
  facts.each do |fact|
153
- downcast_type_name = fact.fetch(:type)
237
+ downcast_type = fact.fetch(:type, nil)
238
+ downcast_not_type = fact.fetch(:not_type, nil)
154
239
  presences.each do |presence|
155
- add_downcast_local(pin, downcast_type_name, presence)
240
+ add_downcast_var(pin,
241
+ presence: presence,
242
+ downcast_type: downcast_type,
243
+ downcast_not_type: downcast_not_type)
156
244
  end
157
245
  end
158
246
  end
159
247
  end
160
248
 
161
- # @param conditional_node [Parser::AST::Node]
249
+ # @param expression_node [Parser::AST::Node]
162
250
  # @param true_ranges [Array<Range>]
251
+ # @param false_ranges [Array<Range>]
163
252
  #
164
253
  # @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
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)
171
259
  end
172
260
 
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?
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
177
268
  # Check if conditional node follows this pattern:
178
269
  # s(:send,
179
270
  # s(:send, nil, :foo), :is_a?,
180
271
  # 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
272
+ #
273
+ call_receiver = call_node.children[0]
274
+ call_arg = type_name(call_node.children[2])
184
275
 
185
- # check if isa_receiver looks like this:
276
+ # check if call_receiver looks like this:
186
277
  # s(:send, nil, :foo)
187
278
  # 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
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
190
281
  end
191
282
  # or like this:
192
283
  # (lvar :repr)
193
- variable_name = isa_receiver.children[0].to_s if isa_receiver&.type == :lvar
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)
194
286
  return unless variable_name
195
287
 
196
- [isa_type_name, variable_name]
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]
197
299
  end
198
300
 
199
301
  # @param variable_name [String]
200
302
  # @param position [Position]
201
303
  #
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
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
207
315
  end
208
316
 
209
317
  # @param isa_node [Parser::AST::Node]
210
318
  # @param true_presences [Array<Range>]
319
+ # @param false_presences [Array<Range>]
211
320
  #
212
321
  # @return [void]
213
- def process_isa(isa_node, true_presences)
322
+ def process_isa(isa_node, true_presences, false_presences)
214
323
  isa_type_name, variable_name = parse_isa(isa_node)
215
324
  return if variable_name.nil? || variable_name.empty?
325
+ # @sg-ignore Need to add nil check here
216
326
  isa_position = Range.from_node(isa_node).start
217
327
 
218
- pin = find_local(variable_name, isa_position)
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)
219
366
  return unless pin
220
367
 
368
+ # @type Hash{Pin::LocalVariable => Array<Hash{Symbol => ComplexType}>}
221
369
  if_true = {}
222
370
  if_true[pin] ||= []
223
- if_true[pin] << { type: isa_type_name }
371
+ if_true[pin] << { type: ComplexType::NIL }
224
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)
225
441
  end
226
442
 
227
443
  # @param node [Parser::AST::Node]
@@ -231,7 +447,9 @@ module Solargraph
231
447
  # e.g.,
232
448
  # s(:const, nil, :Baz)
233
449
  return unless node&.type == :const
450
+ # @type [Parser::AST::Node, nil]
234
451
  module_node = node.children[0]
452
+ # @type [Parser::AST::Node, nil]
235
453
  class_node = node.children[1]
236
454
 
237
455
  return class_node.to_s if module_node.nil?
@@ -242,14 +460,24 @@ module Solargraph
242
460
  "#{module_type_name}::#{class_node}"
243
461
  end
244
462
 
245
- # @param clause_node [Parser::AST::Node]
463
+ # @param clause_node [Parser::AST::Node, nil]
464
+ # @sg-ignore need boolish support for ? methods
246
465
  def always_breaks?(clause_node)
247
466
  clause_node&.type == :break
248
467
  end
249
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
+
250
476
  attr_reader :locals
251
477
 
252
- attr_reader :enclosing_breakable_pin
478
+ attr_reader :ivars
479
+
480
+ attr_reader :enclosing_breakable_pin, :enclosing_compound_statement_pin
253
481
  end
254
482
  end
255
483
  end
@@ -16,15 +16,20 @@ module Solargraph
16
16
  # @return [Array<Pin::LocalVariable>]
17
17
  attr_reader :locals
18
18
 
19
+ # @return [Array<Pin::InstanceVariable>]
20
+ attr_reader :ivars
21
+
19
22
  # @param node [Parser::AST::Node]
20
23
  # @param region [Region]
21
24
  # @param pins [Array<Pin::Base>]
22
25
  # @param locals [Array<Pin::LocalVariable>]
23
- def initialize node, region, pins, locals
26
+ # @param ivars [Array<Pin::InstanceVariable>]
27
+ def initialize node, region, pins, locals, ivars
24
28
  @node = node
25
29
  @region = region
26
30
  @pins = pins
27
31
  @locals = locals
32
+ @ivars = ivars
28
33
  @processed_children = false
29
34
  end
30
35
 
@@ -40,6 +45,28 @@ module Solargraph
40
45
 
41
46
  private
42
47
 
48
+ # @return [Solargraph::Location]
49
+ def location
50
+ get_node_location(node)
51
+ end
52
+
53
+ # @return [Solargraph::Position]
54
+ def position
55
+ Position.new(node.loc.line, node.loc.column)
56
+ end
57
+
58
+ # @sg-ignore downcast output of Enumerable#select
59
+ # @return [Solargraph::Pin::Breakable, nil]
60
+ def enclosing_breakable_pin
61
+ pins.select{|pin| pin.is_a?(Pin::Breakable) && pin.location&.range&.contain?(position)}.last
62
+ end
63
+
64
+ # @todo downcast output of Enumerable#select
65
+ # @return [Solargraph::Pin::CompoundStatement, nil]
66
+ def enclosing_compound_statement_pin
67
+ pins.select{|pin| pin.is_a?(Pin::CompoundStatement) && pin.location&.range&.contain?(position)}.last
68
+ end
69
+
43
70
  # @param subregion [Region]
44
71
  # @return [void]
45
72
  def process_children subregion = region
@@ -47,7 +74,7 @@ module Solargraph
47
74
  @processed_children = true
48
75
  node.children.each do |child|
49
76
  next unless Parser.is_ast_node?(child)
50
- NodeProcessor.process(child, subregion, pins, locals)
77
+ NodeProcessor.process(child, subregion, pins, locals, ivars)
51
78
  end
52
79
  end
53
80
 
@@ -68,6 +95,7 @@ module Solargraph
68
95
  # @return [Pin::Closure, nil]
69
96
  def named_path_pin position
70
97
  pins.select do |pin|
98
+ # @sg-ignore Need to add nil check here
71
99
  pin.is_a?(Pin::Closure) && pin.path && !pin.path.empty? && pin.location.range.contain?(position)
72
100
  end.last
73
101
  end
@@ -77,6 +105,7 @@ module Solargraph
77
105
  # @return [Pin::Closure, nil]
78
106
  def block_pin position
79
107
  # @todo determine if this can return a Pin::Block
108
+ # @sg-ignore Need to add nil check here
80
109
  pins.select { |pin| pin.is_a?(Pin::Closure) && pin.location.range.contain?(position) }.last
81
110
  end
82
111
 
@@ -84,6 +113,7 @@ module Solargraph
84
113
  # @param position [Solargraph::Position]
85
114
  # @return [Pin::Closure, nil]
86
115
  def closure_pin position
116
+ # @sg-ignore Need to add nil check here
87
117
  pins.select { |pin| pin.is_a?(Pin::Closure) && pin.location.range.contain?(position) }.last
88
118
  end
89
119
  end