solargraph 0.58.0 → 0.59.0

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