steep 1.6.0 → 1.7.0.dev.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (314) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +6 -0
  3. data/.github/workflows/ruby-windows.yml +2 -1
  4. data/.github/workflows/ruby.yml +9 -10
  5. data/.gitignore +0 -1
  6. data/Gemfile +1 -1
  7. data/Gemfile.lock +30 -28
  8. data/Rakefile +5 -0
  9. data/bin/output_test.rb +1 -0
  10. data/doc/narrowing.md +195 -0
  11. data/gemfile_steep/Gemfile +1 -1
  12. data/gemfile_steep/Gemfile.lock +31 -16
  13. data/guides/src/getting-started/getting-started.md +10 -11
  14. data/lib/steep/ast/ignore.rb +148 -0
  15. data/lib/steep/cli.rb +6 -1
  16. data/lib/steep/diagnostic/ruby.rb +16 -0
  17. data/lib/steep/drivers/utils/driver_helper.rb +22 -11
  18. data/lib/steep/node_helper.rb +12 -0
  19. data/lib/steep/project/dsl.rb +18 -21
  20. data/lib/steep/project/options.rb +39 -2
  21. data/lib/steep/project.rb +11 -7
  22. data/lib/steep/server/change_buffer.rb +2 -2
  23. data/lib/steep/server/interaction_worker.rb +61 -11
  24. data/lib/steep/server/worker_process.rb +3 -1
  25. data/lib/steep/services/completion_provider.rb +39 -8
  26. data/lib/steep/services/file_loader.rb +3 -2
  27. data/lib/steep/services/signature_help_provider.rb +20 -17
  28. data/lib/steep/services/type_check_service.rb +36 -8
  29. data/lib/steep/source/ignore_ranges.rb +69 -0
  30. data/lib/steep/source.rb +10 -4
  31. data/lib/steep/subtyping/check.rb +6 -0
  32. data/lib/steep/subtyping/result.rb +6 -0
  33. data/lib/steep/type_construction.rb +6 -151
  34. data/lib/steep/type_inference/case_when.rb +301 -0
  35. data/lib/steep/version.rb +1 -1
  36. data/lib/steep.rb +14 -1
  37. data/rbs_collection.steep.lock.yaml +8 -6
  38. data/sig/shims/parser.rbs +10 -0
  39. data/sig/shims/yaml.rbs +4 -0
  40. data/sig/steep/ast/ignore.rbs +66 -0
  41. data/sig/steep/diagnostic/ruby.rbs +8 -0
  42. data/sig/steep/node_helper.rbs +2 -0
  43. data/sig/steep/project/dsl.rbs +8 -4
  44. data/sig/steep/project/options.rbs +21 -1
  45. data/sig/steep/project.rbs +3 -3
  46. data/sig/steep/server/change_buffer.rbs +6 -4
  47. data/sig/steep/server/interaction_worker.rbs +10 -0
  48. data/sig/steep/server/worker_process.rbs +4 -4
  49. data/sig/steep/services/completion_provider.rbs +14 -1
  50. data/sig/steep/services/type_check_service.rbs +6 -1
  51. data/sig/steep/source/ignore_ranges.rbs +38 -0
  52. data/sig/steep/source.rbs +5 -2
  53. data/sig/steep/subtyping/check.rbs +4 -2
  54. data/sig/steep/subtyping/result.rbs +5 -1
  55. data/sig/steep/type_construction.rbs +1 -26
  56. data/sig/steep/type_inference/case_when.rbs +130 -0
  57. data/sig/steep.rbs +9 -0
  58. data/steep.gemspec +1 -1
  59. metadata +11 -258
  60. data/smoke/alias/Steepfile +0 -6
  61. data/smoke/alias/a.rb +0 -16
  62. data/smoke/alias/a.rbs +0 -10
  63. data/smoke/alias/b.rb +0 -6
  64. data/smoke/alias/c.rb +0 -8
  65. data/smoke/alias/test_expectations.yml +0 -96
  66. data/smoke/and/Steepfile +0 -6
  67. data/smoke/and/a.rb +0 -8
  68. data/smoke/and/test_expectations.yml +0 -29
  69. data/smoke/array/Steepfile +0 -6
  70. data/smoke/array/a.rb +0 -18
  71. data/smoke/array/b.rb +0 -12
  72. data/smoke/array/c.rb +0 -6
  73. data/smoke/array/test_expectations.yml +0 -103
  74. data/smoke/block/Steepfile +0 -6
  75. data/smoke/block/a.rb +0 -10
  76. data/smoke/block/a.rbs +0 -6
  77. data/smoke/block/b.rb +0 -13
  78. data/smoke/block/c.rb +0 -9
  79. data/smoke/block/c.rbs +0 -3
  80. data/smoke/block/d.rb +0 -11
  81. data/smoke/block/e.rb +0 -12
  82. data/smoke/block/e.rbs +0 -4
  83. data/smoke/block/test_expectations.yml +0 -133
  84. data/smoke/case/Steepfile +0 -6
  85. data/smoke/case/a.rb +0 -18
  86. data/smoke/case/test_expectations.yml +0 -47
  87. data/smoke/class/Steepfile +0 -6
  88. data/smoke/class/a.rb +0 -25
  89. data/smoke/class/a.rbs +0 -23
  90. data/smoke/class/b.rb +0 -5
  91. data/smoke/class/c.rb +0 -9
  92. data/smoke/class/f.rb +0 -10
  93. data/smoke/class/g.rb +0 -6
  94. data/smoke/class/h.rb +0 -19
  95. data/smoke/class/h.rbs +0 -6
  96. data/smoke/class/i.rb +0 -14
  97. data/smoke/class/i.rbs +0 -9
  98. data/smoke/class/test_expectations.yml +0 -117
  99. data/smoke/compact/Steepfile +0 -6
  100. data/smoke/compact/a.rb +0 -2
  101. data/smoke/compact/a.rbs +0 -5
  102. data/smoke/compact/b.rb +0 -2
  103. data/smoke/compact/test_expectations.yml +0 -18
  104. data/smoke/const/Steepfile +0 -6
  105. data/smoke/const/a.rb +0 -27
  106. data/smoke/const/b.rb +0 -7
  107. data/smoke/const/b.rbs +0 -5
  108. data/smoke/const/test_expectations.yml +0 -134
  109. data/smoke/diagnostics/Steepfile +0 -6
  110. data/smoke/diagnostics/a.rbs +0 -22
  111. data/smoke/diagnostics/argument_type_mismatch.rb +0 -1
  112. data/smoke/diagnostics/block_body_type_mismatch.rb +0 -1
  113. data/smoke/diagnostics/block_type_mismatch.rb +0 -3
  114. data/smoke/diagnostics/break_type_mismatch.rb +0 -1
  115. data/smoke/diagnostics/different_method_parameter_kind.rb +0 -9
  116. data/smoke/diagnostics/else_on_exhaustive_case.rb +0 -12
  117. data/smoke/diagnostics/incompatible_annotation.rb +0 -6
  118. data/smoke/diagnostics/incompatible_argument.rb +0 -1
  119. data/smoke/diagnostics/incompatible_assignment.rb +0 -8
  120. data/smoke/diagnostics/method_arity_mismatch.rb +0 -11
  121. data/smoke/diagnostics/method_body_type_mismatch.rb +0 -6
  122. data/smoke/diagnostics/method_definition_missing.rb +0 -2
  123. data/smoke/diagnostics/method_parameter_mismatch.rb +0 -10
  124. data/smoke/diagnostics/method_return_type_annotation_mismatch.rb +0 -7
  125. data/smoke/diagnostics/missing_keyword.rb +0 -1
  126. data/smoke/diagnostics/no_method.rb +0 -1
  127. data/smoke/diagnostics/proc_type_expected.rb +0 -3
  128. data/smoke/diagnostics/required_block_missing.rb +0 -1
  129. data/smoke/diagnostics/return_type_mismatch.rb +0 -6
  130. data/smoke/diagnostics/test_expectations.yml +0 -591
  131. data/smoke/diagnostics/unexpected_block_given.rb +0 -1
  132. data/smoke/diagnostics/unexpected_dynamic_method.rb +0 -3
  133. data/smoke/diagnostics/unexpected_jump.rb +0 -4
  134. data/smoke/diagnostics/unexpected_jump_value.rb +0 -3
  135. data/smoke/diagnostics/unexpected_keyword.rb +0 -1
  136. data/smoke/diagnostics/unexpected_splat.rb +0 -1
  137. data/smoke/diagnostics/unexpected_yield.rb +0 -6
  138. data/smoke/diagnostics/unknown_constant_assigned.rb +0 -7
  139. data/smoke/diagnostics/unresolved_overloading.rb +0 -1
  140. data/smoke/diagnostics/unsupported_syntax.rb +0 -2
  141. data/smoke/diagnostics-rbs/Steepfile +0 -8
  142. data/smoke/diagnostics-rbs/duplicated-method-definition.rbs +0 -20
  143. data/smoke/diagnostics-rbs/generic-parameter-mismatch.rbs +0 -7
  144. data/smoke/diagnostics-rbs/inherit-module.rbs +0 -2
  145. data/smoke/diagnostics-rbs/invalid-method-overload.rbs +0 -3
  146. data/smoke/diagnostics-rbs/invalid-type-application.rbs +0 -7
  147. data/smoke/diagnostics-rbs/invalid_variance_annotation.rbs +0 -3
  148. data/smoke/diagnostics-rbs/mixin-class-error.rbs +0 -6
  149. data/smoke/diagnostics-rbs/nonregular-type-alias.rbs +0 -3
  150. data/smoke/diagnostics-rbs/recursive-alias.rbs +0 -5
  151. data/smoke/diagnostics-rbs/recursive-class.rbs +0 -8
  152. data/smoke/diagnostics-rbs/recursive-type-alias.rbs +0 -3
  153. data/smoke/diagnostics-rbs/superclass-mismatch.rbs +0 -7
  154. data/smoke/diagnostics-rbs/test_expectations.yml +0 -300
  155. data/smoke/diagnostics-rbs/unknown-method-alias.rbs +0 -3
  156. data/smoke/diagnostics-rbs/unknown-type-name-2.rbs +0 -5
  157. data/smoke/diagnostics-rbs/unknown-type-name.rbs +0 -13
  158. data/smoke/diagnostics-rbs-duplicated/Steepfile +0 -6
  159. data/smoke/diagnostics-rbs-duplicated/a.rbs +0 -5
  160. data/smoke/diagnostics-rbs-duplicated/test_expectations.yml +0 -13
  161. data/smoke/diagnostics-ruby-unsat/Steepfile +0 -6
  162. data/smoke/diagnostics-ruby-unsat/a.rbs +0 -3
  163. data/smoke/diagnostics-ruby-unsat/test_expectations.yml +0 -27
  164. data/smoke/diagnostics-ruby-unsat/unsatisfiable_constraint.rb +0 -6
  165. data/smoke/dstr/Steepfile +0 -6
  166. data/smoke/dstr/a.rb +0 -5
  167. data/smoke/dstr/test_expectations.yml +0 -13
  168. data/smoke/ensure/Steepfile +0 -6
  169. data/smoke/ensure/a.rb +0 -18
  170. data/smoke/ensure/test_expectations.yml +0 -62
  171. data/smoke/enumerator/Steepfile +0 -6
  172. data/smoke/enumerator/a.rb +0 -6
  173. data/smoke/enumerator/b.rb +0 -17
  174. data/smoke/enumerator/test_expectations.yml +0 -47
  175. data/smoke/extension/Steepfile +0 -6
  176. data/smoke/extension/a.rb +0 -10
  177. data/smoke/extension/a.rbs +0 -13
  178. data/smoke/extension/b.rb +0 -10
  179. data/smoke/extension/c.rb +0 -9
  180. data/smoke/extension/d.rb +0 -2
  181. data/smoke/extension/e.rb +0 -2
  182. data/smoke/extension/e.rbs +0 -7
  183. data/smoke/extension/f.rb +0 -2
  184. data/smoke/extension/f.rbs +0 -3
  185. data/smoke/extension/test_expectations.yml +0 -73
  186. data/smoke/hash/Steepfile +0 -6
  187. data/smoke/hash/a.rb +0 -17
  188. data/smoke/hash/a.rbs +0 -8
  189. data/smoke/hash/b.rb +0 -6
  190. data/smoke/hash/c.rb +0 -15
  191. data/smoke/hash/d.rb +0 -5
  192. data/smoke/hash/e.rb +0 -1
  193. data/smoke/hash/e.rbs +0 -3
  194. data/smoke/hash/f.rb +0 -11
  195. data/smoke/hash/test_expectations.yml +0 -81
  196. data/smoke/hello/Steepfile +0 -6
  197. data/smoke/hello/hello.rb +0 -11
  198. data/smoke/hello/hello.rbs +0 -7
  199. data/smoke/hello/test_expectations.yml +0 -25
  200. data/smoke/if/Steepfile +0 -6
  201. data/smoke/if/a.rb +0 -20
  202. data/smoke/if/test_expectations.yml +0 -34
  203. data/smoke/implements/Steepfile +0 -6
  204. data/smoke/implements/a.rb +0 -12
  205. data/smoke/implements/a.rbs +0 -6
  206. data/smoke/implements/b.rb +0 -13
  207. data/smoke/implements/b.rbs +0 -12
  208. data/smoke/implements/test_expectations.yml +0 -23
  209. data/smoke/initialize/Steepfile +0 -6
  210. data/smoke/initialize/a.rb +0 -12
  211. data/smoke/initialize/a.rbs +0 -3
  212. data/smoke/initialize/test_expectations.yml +0 -1
  213. data/smoke/integer/Steepfile +0 -6
  214. data/smoke/integer/a.rb +0 -26
  215. data/smoke/integer/test_expectations.yml +0 -110
  216. data/smoke/interface/Steepfile +0 -6
  217. data/smoke/interface/a.rb +0 -12
  218. data/smoke/interface/a.rbs +0 -12
  219. data/smoke/interface/test_expectations.yml +0 -23
  220. data/smoke/kwbegin/Steepfile +0 -6
  221. data/smoke/kwbegin/a.rb +0 -7
  222. data/smoke/kwbegin/test_expectations.yml +0 -17
  223. data/smoke/lambda/Steepfile +0 -6
  224. data/smoke/lambda/a.rb +0 -10
  225. data/smoke/lambda/test_expectations.yml +0 -17
  226. data/smoke/literal/Steepfile +0 -6
  227. data/smoke/literal/a.rb +0 -11
  228. data/smoke/literal/b.rb +0 -7
  229. data/smoke/literal/literal_methods.rbs +0 -4
  230. data/smoke/literal/test_expectations.yml +0 -106
  231. data/smoke/map/Steepfile +0 -6
  232. data/smoke/map/a.rb +0 -5
  233. data/smoke/map/test_expectations.yml +0 -1
  234. data/smoke/method/Steepfile +0 -6
  235. data/smoke/method/a.rb +0 -21
  236. data/smoke/method/a.rbs +0 -4
  237. data/smoke/method/b.rb +0 -25
  238. data/smoke/method/c.rb +0 -5
  239. data/smoke/method/d.rb +0 -1
  240. data/smoke/method/d.rbs +0 -3
  241. data/smoke/method/test_expectations.yml +0 -121
  242. data/smoke/module/Steepfile +0 -6
  243. data/smoke/module/a.rb +0 -19
  244. data/smoke/module/a.rbs +0 -16
  245. data/smoke/module/b.rb +0 -6
  246. data/smoke/module/c.rb +0 -22
  247. data/smoke/module/d.rb +0 -4
  248. data/smoke/module/e.rb +0 -13
  249. data/smoke/module/f.rb +0 -11
  250. data/smoke/module/test_expectations.yml +0 -75
  251. data/smoke/regexp/Steepfile +0 -6
  252. data/smoke/regexp/a.rb +0 -109
  253. data/smoke/regexp/b.rb +0 -79
  254. data/smoke/regexp/test_expectations.yml +0 -615
  255. data/smoke/regression/Steepfile +0 -6
  256. data/smoke/regression/array.rb +0 -7
  257. data/smoke/regression/block_param_split.rb +0 -7
  258. data/smoke/regression/block_param_split.rbs +0 -3
  259. data/smoke/regression/empty_yield.rb +0 -5
  260. data/smoke/regression/empty_yield.rbs +0 -3
  261. data/smoke/regression/enumerator_product.rb +0 -1
  262. data/smoke/regression/fun.rb +0 -8
  263. data/smoke/regression/fun.rbs +0 -4
  264. data/smoke/regression/hash.rb +0 -7
  265. data/smoke/regression/hello world.rb +0 -1
  266. data/smoke/regression/issue_328.rb +0 -1
  267. data/smoke/regression/issue_328.rbs +0 -0
  268. data/smoke/regression/issue_332.rb +0 -11
  269. data/smoke/regression/issue_332.rbs +0 -19
  270. data/smoke/regression/issue_372.rb +0 -8
  271. data/smoke/regression/issue_372.rbs +0 -4
  272. data/smoke/regression/lambda.rb +0 -3
  273. data/smoke/regression/masgn.rb +0 -4
  274. data/smoke/regression/poly_new.rb +0 -2
  275. data/smoke/regression/poly_new.rbs +0 -4
  276. data/smoke/regression/range.rb +0 -5
  277. data/smoke/regression/set_divide.rb +0 -12
  278. data/smoke/regression/test_expectations.yml +0 -120
  279. data/smoke/regression/thread.rb +0 -7
  280. data/smoke/rescue/Steepfile +0 -6
  281. data/smoke/rescue/a.rb +0 -48
  282. data/smoke/rescue/test_expectations.yml +0 -79
  283. data/smoke/self/Steepfile +0 -6
  284. data/smoke/self/a.rb +0 -21
  285. data/smoke/self/a.rbs +0 -4
  286. data/smoke/self/test_expectations.yml +0 -23
  287. data/smoke/skip/Steepfile +0 -6
  288. data/smoke/skip/skip.rb +0 -13
  289. data/smoke/skip/test_expectations.yml +0 -23
  290. data/smoke/stdout/Steepfile +0 -6
  291. data/smoke/stdout/a.rb +0 -8
  292. data/smoke/stdout/a.rbs +0 -7
  293. data/smoke/stdout/test_expectations.yml +0 -1
  294. data/smoke/super/Steepfile +0 -6
  295. data/smoke/super/a.rb +0 -30
  296. data/smoke/super/a.rbs +0 -10
  297. data/smoke/super/test_expectations.yml +0 -69
  298. data/smoke/toplevel/Steepfile +0 -6
  299. data/smoke/toplevel/a.rb +0 -3
  300. data/smoke/toplevel/a.rbs +0 -3
  301. data/smoke/toplevel/test_expectations.yml +0 -15
  302. data/smoke/tsort/Steepfile +0 -7
  303. data/smoke/tsort/a.rb +0 -12
  304. data/smoke/tsort/test_expectations.yml +0 -1
  305. data/smoke/type_case/Steepfile +0 -6
  306. data/smoke/type_case/a.rb +0 -24
  307. data/smoke/type_case/test_expectations.yml +0 -58
  308. data/smoke/unexpected/Steepfile +0 -6
  309. data/smoke/unexpected/test_expectations.yml +0 -13
  310. data/smoke/unexpected/unexpected.rbs +0 -3
  311. data/smoke/yield/Steepfile +0 -6
  312. data/smoke/yield/a.rb +0 -15
  313. data/smoke/yield/b.rb +0 -6
  314. data/smoke/yield/test_expectations.yml +0 -88
@@ -157,6 +157,9 @@ module Steep
157
157
  end
158
158
  end
159
159
 
160
+ class TextItem < Struct.new(:text, :help_text, :range, :label, keyword_init: true)
161
+ end
162
+
160
163
  attr_reader :source_text
161
164
  attr_reader :path
162
165
  attr_reader :subtyping
@@ -205,7 +208,7 @@ module Steep
205
208
  end
206
209
  end
207
210
 
208
- if at_comment?(position)
211
+ if comment = at_comment?(position)
209
212
  node, *parents = source.find_nodes(line: position.line, column: position.column)
210
213
 
211
214
  case
@@ -232,9 +235,41 @@ module Steep
232
235
  if annotation
233
236
  annotation.location or raise
234
237
  return items_for_rbs(position: position, buffer: annotation.location.buffer)
235
- else
236
- return []
237
238
  end
239
+
240
+ comment_content = comment.text[1..] || ""
241
+ comment_content.strip!
242
+
243
+ range = Range.new(
244
+ start: Position.new(line: line, column: column),
245
+ end: Position.new(line: line, column: column)
246
+ )
247
+
248
+ items = [
249
+ TextItem.new(label: "steep:ignore:start", text: "steep:ignore:start", help_text: "Open ignore block", range: range),
250
+ TextItem.new(label: "steep:ignore:end", text: "steep:ignore:end", help_text: "Close ignore block", range: range),
251
+ TextItem.new(label: "steep:ignore", text: "steep:ignore ${1:optional diagnostics}", help_text: "Ignore line", range: range),
252
+ TextItem.new(label: "@type var x: T", text: "@type var ${1:variable}: ${2:var type}", help_text: "Type of local variable", range: range),
253
+ TextItem.new(label: "@type self: T", text: "@type self: ${1:self type}", help_text: "Type of `self`", range: range),
254
+ TextItem.new(label: "@type block: T", text: "@type block: ${1:block type}", help_text: "Type of `block`", range: range),
255
+ TextItem.new(label: "@type break: T", text: "@type break: ${1:break type}", help_text: "Type of `block`", range: range),
256
+ ]
257
+
258
+ items = items.filter_map do |item|
259
+ if item.text.start_with?(comment_content)
260
+ TextItem.new(
261
+ label: item.label,
262
+ text: item.text,
263
+ help_text: item.help_text,
264
+ range: Range.new(
265
+ start: Position.new(line: item.range.start.line, column: item.range.start.column - comment_content.size),
266
+ end: item.range.end
267
+ )
268
+ )
269
+ end
270
+ end
271
+
272
+ return items
238
273
  end
239
274
  end
240
275
 
@@ -294,11 +329,7 @@ module Steep
294
329
  end
295
330
 
296
331
  def at_comment?(position)
297
- if source.find_comment(line: position.line, column: position.column)
298
- true
299
- else
300
- false
301
- end
332
+ source.find_comment(line: position.line, column: position.column)
302
333
  end
303
334
 
304
335
  def range_for(position, prefix: "")
@@ -15,8 +15,9 @@ module Steep
15
15
  absolute_path = base_dir + path
16
16
 
17
17
  if absolute_path.file?
18
- if pattern =~ path
19
- yield absolute_path.relative_path_from(base_dir)
18
+ relative_path = absolute_path.relative_path_from(base_dir)
19
+ if pattern =~ relative_path
20
+ yield relative_path
20
21
  end
21
22
  else
22
23
  files = if absolute_path.directory?
@@ -55,7 +55,7 @@ module Steep
55
55
  if begin_loc.end_pos <= pos && pos <= end_loc.begin_pos
56
56
  # Given position is between open/close parens of args of send node
57
57
 
58
- if parent && (parent.type == :block || parent.type == :numblock)
58
+ if parent && (parent.type == :block || parent.type == :numblock) && node.equal?(parent.children[0])
59
59
  send_node = parent
60
60
  else
61
61
  send_node = node
@@ -76,7 +76,8 @@ module Steep
76
76
  end
77
77
 
78
78
  def last_argument_nodes_for(argument_nodes:, line:, column:)
79
- return unless argument_nodes.last.children[2] # No arguments
79
+ last = argument_nodes.last or raise
80
+ return unless last.children[2] # No arguments
80
81
  return argument_nodes if argument_nodes.size > 1 # Cursor is on the last argument
81
82
 
82
83
  pos = buffer.loc_to_pos([line, column])
@@ -150,9 +151,9 @@ module Steep
150
151
  case last_argument_nodes[-3].type
151
152
  when :pair
152
153
  argname = last_argument_nodes[-3].children.first.children.first
153
- if method_type.type.required_keywords[argname]
154
+ if method_type.type.required_keywords.key?(argname)
154
155
  positionals + method_type.type.required_keywords.keys.index(argname).to_i + 1
155
- elsif method_type.type.optional_keywords[argname]
156
+ elsif method_type.type.optional_keywords.key?(argname)
156
157
  positionals + method_type.type.required_keywords.size + method_type.type.optional_keywords.keys.index(argname).to_i + 1
157
158
  elsif method_type.type.rest_keywords
158
159
  positionals + method_type.type.required_keywords.size + method_type.type.optional_keywords.size
@@ -161,7 +162,7 @@ module Steep
161
162
  positionals + method_type.type.required_keywords.size + method_type.type.optional_keywords.size if method_type.type.rest_keywords
162
163
  end
163
164
  else
164
- pos = node.children[2...].index { |c| c.location == last_argument_nodes[-2].location }.to_i
165
+ pos = (node.children[2...] || raise).index { |c| c.location == last_argument_nodes[-2].location }.to_i
165
166
  if method_type.type.rest_positionals
166
167
  [pos + 1, positionals - 1].min
167
168
  else
@@ -174,21 +175,23 @@ module Steep
174
175
  when :splat
175
176
  method_type.type.required_positionals.size + method_type.type.optional_positionals.size if method_type.type.rest_positionals
176
177
  when :kwargs
177
- case argument_nodes[-3].type
178
- when :pair
179
- argname = argument_nodes[-3].children.first.children.first
180
- if method_type.type.required_keywords[argname]
181
- positionals + method_type.type.required_keywords.keys.index(argname).to_i
182
- elsif method_type.type.optional_keywords[argname]
183
- positionals + method_type.type.required_keywords.size + method_type.type.optional_keywords.keys.index(argname).to_i
184
- elsif method_type.type.rest_keywords
185
- positionals + method_type.type.required_keywords.size + method_type.type.optional_keywords.size
178
+ if argument_nodes[-3]
179
+ case argument_nodes[-3].type
180
+ when :pair
181
+ argname = argument_nodes[-3].children.first.children.first
182
+ if method_type.type.required_keywords.key?(argname)
183
+ positionals + method_type.type.required_keywords.keys.index(argname).to_i
184
+ elsif method_type.type.optional_keywords.key?(argname)
185
+ positionals + method_type.type.required_keywords.size + method_type.type.optional_keywords.keys.index(argname).to_i
186
+ elsif method_type.type.rest_keywords
187
+ positionals + method_type.type.required_keywords.size + method_type.type.optional_keywords.size
188
+ end
189
+ when :kwsplat
190
+ positionals + method_type.type.required_keywords.size + method_type.type.optional_keywords.size if method_type.type.rest_keywords
186
191
  end
187
- when :kwsplat
188
- positionals + method_type.type.required_keywords.size + method_type.type.optional_keywords.size if method_type.type.rest_keywords
189
192
  end
190
193
  else
191
- pos = node.children[2...].index { |c| c.location == argument_nodes[-2].location }.to_i
194
+ pos = (node.children[2...] || raise).index { |c| c.location == argument_nodes[-2].location }.to_i
192
195
  [pos, positionals - 1].min
193
196
  end
194
197
  end
@@ -13,25 +13,27 @@ module Steep
13
13
  attr_reader :node
14
14
  attr_reader :typing
15
15
  attr_reader :errors
16
+ attr_reader :ignores
16
17
 
17
- def initialize(path:, node:, content:, typing:, errors:)
18
+ def initialize(path:, node:, content:, typing:, ignores:, errors:)
18
19
  @path = path
19
20
  @node = node
20
21
  @content = content
21
22
  @typing = typing
23
+ @ignores = ignores
22
24
  @errors = errors
23
25
  end
24
26
 
25
27
  def self.with_syntax_error(path:, content:, error:)
26
- new(path: path, node: false, content: content, errors: [error], typing: nil)
28
+ new(path: path, node: false, content: content, errors: [error], typing: nil, ignores: nil)
27
29
  end
28
30
 
29
- def self.with_typing(path:, content:, typing:, node:)
30
- new(path: path, node: node, content: content, errors: nil, typing: typing)
31
+ def self.with_typing(path:, content:, typing:, node:, ignores:)
32
+ new(path: path, node: node, content: content, errors: nil, typing: typing, ignores: ignores)
31
33
  end
32
34
 
33
35
  def self.no_data(path:, content:)
34
- new(path: path, content: content, node: false, errors: nil, typing: nil)
36
+ new(path: path, content: content, node: false, errors: nil, typing: nil, ignores: nil)
35
37
  end
36
38
 
37
39
  def update_content(content)
@@ -40,12 +42,37 @@ module Steep
40
42
  content: content,
41
43
  node: node,
42
44
  errors: errors,
43
- typing: typing
45
+ typing: typing,
46
+ ignores: ignores
44
47
  )
45
48
  end
46
49
 
47
50
  def diagnostics
48
- errors || typing&.errors || []
51
+ case
52
+ when errors
53
+ errors
54
+ when typing && ignores
55
+ errors = [] #: Array[Diagnostic::Ruby::Base]
56
+
57
+ errors.concat(
58
+ typing.errors.delete_if do |diagnostic|
59
+ case diagnostic.location
60
+ when ::Parser::Source::Range
61
+ ignores.ignore?(diagnostic.location.first_line, diagnostic.location.last_line, diagnostic.diagnostic_code)
62
+ when RBS::Location
63
+ ignores.ignore?(diagnostic.location.start_line, diagnostic.location.end_line, diagnostic.diagnostic_code)
64
+ end
65
+ end
66
+ )
67
+
68
+ ignores.error_ignores.each do |ignore|
69
+ errors << Diagnostic::Ruby::InvalidIgnoreComment.new(comment: ignore.comment)
70
+ end
71
+
72
+ errors
73
+ else
74
+ []
75
+ end
49
76
  end
50
77
  end
51
78
 
@@ -327,7 +354,8 @@ module Steep
327
354
  Steep.logger.tagged "#type_check_file(#{path}@#{target.name})" do
328
355
  source = Source.parse(text, path: path, factory: subtyping.factory)
329
356
  typing = TypeCheckService.type_check(source: source, subtyping: subtyping, constant_resolver: yield)
330
- SourceFile.with_typing(path: path, content: text, node: source.node, typing: typing)
357
+ ignores = Source::IgnoreRanges.new(ignores: source.ignores)
358
+ SourceFile.with_typing(path: path, content: text, node: source.node, typing: typing, ignores: ignores)
331
359
  end
332
360
  rescue AnnotationParser::SyntaxError => exn
333
361
  error = Diagnostic::Ruby::SyntaxError.new(message: exn.message, location: exn.location)
@@ -0,0 +1,69 @@
1
+ module Steep
2
+ class Source
3
+ class IgnoreRanges
4
+ attr_reader :all_ignores, :error_ignores, :ignored_ranges, :ignored_lines
5
+
6
+ def initialize(ignores:)
7
+ @all_ignores = ignores.sort_by(&:line)
8
+ @error_ignores = []
9
+
10
+ @ignored_lines = {}
11
+ @ignored_ranges = []
12
+
13
+ last_start = nil #: AST::Ignore::IgnoreStart?
14
+
15
+ all_ignores.each do |ignore|
16
+ case ignore
17
+ when AST::Ignore::IgnoreStart
18
+ if last_start
19
+ error_ignores << last_start
20
+ end
21
+ last_start = ignore
22
+ when AST::Ignore::IgnoreEnd
23
+ if last_start
24
+ ignored_ranges << (last_start.line..ignore.line)
25
+ last_start = nil
26
+ else
27
+ error_ignores << ignore
28
+ end
29
+ when AST::Ignore::IgnoreLine
30
+ if last_start
31
+ error_ignores << ignore
32
+ else
33
+ ignored_lines[ignore.line] = ignore
34
+ end
35
+ end
36
+ end
37
+
38
+ if last_start
39
+ error_ignores << last_start
40
+ end
41
+ end
42
+
43
+ def ignore?(start_line, end_line, code)
44
+ if ignore = ignored_lines.fetch(start_line, nil)
45
+ ignore_code?(ignore, code) and return true
46
+ end
47
+
48
+ if start_line != end_line
49
+ if ignore = ignored_lines.fetch(end_line, nil)
50
+ ignore_code?(ignore, code) and return true
51
+ end
52
+ end
53
+
54
+ ignored_ranges.any? do |range|
55
+ range.cover?(start_line) && range.cover?(end_line)
56
+ end
57
+ end
58
+
59
+ def ignore_code?(line, code)
60
+ case diags = line.ignored_diagnostics
61
+ when Symbol
62
+ true
63
+ else
64
+ diags.any? {|d| code == "Ruby::#{d}" }
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
data/lib/steep/source.rb CHANGED
@@ -5,15 +5,17 @@ module Steep
5
5
  attr_reader :node
6
6
  attr_reader :mapping
7
7
  attr_reader :comments
8
+ attr_reader :ignores
8
9
 
9
10
  extend NodeHelper
10
11
 
11
- def initialize(buffer:, path:, node:, mapping:, comments:)
12
+ def initialize(buffer:, path:, node:, mapping:, comments:, ignores:)
12
13
  @buffer = buffer
13
14
  @path = path
14
15
  @node = node
15
16
  @mapping = mapping
16
17
  @comments = comments
18
+ @ignores = ignores
17
19
  end
18
20
 
19
21
  class Builder < ::Parser::Builders::Default
@@ -28,7 +30,7 @@ module Steep
28
30
  end
29
31
 
30
32
  def self.new_parser
31
- ::Parser::Ruby32.new(Builder.new).tap do |parser|
33
+ ::Parser::Ruby33.new(Builder.new).tap do |parser|
32
34
  parser.diagnostics.all_errors_are_fatal = true
33
35
  parser.diagnostics.ignore_warnings = true
34
36
  end
@@ -88,7 +90,11 @@ module Steep
88
90
  map[node] << annot
89
91
  end
90
92
 
91
- new(buffer: buffer, path: path, node: node, mapping: map, comments: comments)
93
+ ignores = comments.filter_map do |comment|
94
+ AST::Ignore.parse(comment, buffer)
95
+ end
96
+
97
+ new(buffer: buffer, path: path, node: node, mapping: map, comments: comments, ignores: ignores)
92
98
  end
93
99
 
94
100
  def self.construct_mapping(node:, annotations:, mapping:, line_range: nil)
@@ -432,7 +438,7 @@ module Steep
432
438
  mapping[node_] << annot
433
439
  end
434
440
 
435
- Source.new(buffer: buffer, path: path, node: node_, mapping: mapping, comments: comments)
441
+ Source.new(buffer: buffer, path: path, node: node_, mapping: mapping, comments: comments, ignores: ignores)
436
442
  else
437
443
  self
438
444
  end
@@ -1,6 +1,8 @@
1
1
  module Steep
2
2
  module Subtyping
3
3
  class Check
4
+ ABORT_LIMIT = ENV.fetch("STEEP_SUBTYPING_ABORT_LIMIT", 50).to_i
5
+
4
6
  attr_reader :builder
5
7
  attr_reader :cache
6
8
 
@@ -187,6 +189,10 @@ module Steep
187
189
  end
188
190
 
189
191
  def check_type(relation)
192
+ if assumptions.size > ABORT_LIMIT
193
+ return Failure(relation, Result::Failure::LoopAbort.new)
194
+ end
195
+
190
196
  relation.type!
191
197
 
192
198
  Steep.logger.tagged "#{relation.sub_type} <: #{relation.super_type}" do
@@ -269,6 +269,12 @@ module Steep
269
269
  end
270
270
  end
271
271
 
272
+ class LoopAbort
273
+ def message
274
+ "Detected infinite loop with limit of `#{Check::ABORT_LIMIT}`; specify $STEEP_SUBTYPING_ABORT_LIMIT env var to override the limit."
275
+ end
276
+ end
277
+
272
278
  attr_reader :error
273
279
 
274
280
  def initialize(relation, error)
@@ -1966,113 +1966,7 @@ module Steep
1966
1966
  interpreter = TypeInference::LogicTypeInterpreter.new(subtyping: checker, typing: typing, config: builder_config)
1967
1967
 
1968
1968
  if cond
1969
- branch_results = [] #: Array[Pair]
1970
-
1971
- cond_type, constr = constr.synthesize(cond)
1972
-
1973
- var_name = :"_a[#{SecureRandom.alphanumeric(4)}]"
1974
- var_cond, value_node = transform_condition_node(cond, var_name)
1975
- constr = constr.update_type_env {|env| env.assign_local_variable(var_name, cond_type, nil) }
1976
-
1977
- next_branch_reachable = true
1978
-
1979
- when_constr = constr
1980
- whens.each do |clause|
1981
- # @type var tests: Array[Parser::AST::Node]
1982
- # @type var body: Parser::AST::Node?
1983
- *tests, body = clause.children
1984
-
1985
- test_constr = when_constr
1986
- # @type var test_envs: Array[TypeInference::TypeEnv]
1987
- test_envs = []
1988
-
1989
- branch_reachable = false
1990
- false_branch_reachable = false
1991
-
1992
- tests.each do |test|
1993
- test_node = test.updated(:send, [test, :===, var_cond])
1994
- test_type, test_constr = test_constr.synthesize(test_node, condition: true).to_ary
1995
- truthy, falsy = interpreter.eval(node: test_node, env: test_constr.context.type_env)
1996
-
1997
- truthy_env = propagate_type_env(var_name, value_node, truthy.env)
1998
- falsy_env = propagate_type_env(var_name, value_node, falsy.env)
1999
-
2000
- test_envs << truthy_env
2001
-
2002
- test_constr = test_constr.update_type_env { falsy_env }
2003
-
2004
- branch_reachable ||= next_branch_reachable && !truthy.unreachable
2005
- false_branch_reachable = !falsy.unreachable
2006
- end
2007
-
2008
- next_branch_reachable &&= false_branch_reachable
2009
- body_constr = when_constr.update_type_env {|env| env.join(*test_envs) }
2010
-
2011
- branch_result =
2012
- if body
2013
- body_constr
2014
- .for_branch(body)
2015
- .tap {|constr| typing.add_context_for_node(body, context: constr.context) }
2016
- .synthesize(body, hint: hint)
2017
- else
2018
- Pair.new(type: AST::Builtin.nil_type, constr: body_constr)
2019
- end
2020
-
2021
- branch_results << branch_result
2022
-
2023
- if !branch_reachable && !branch_result.type.is_a?(AST::Types::Bot)
2024
- typing.add_error(
2025
- Diagnostic::Ruby::UnreachableValueBranch.new(
2026
- node: clause,
2027
- type: branch_result.type,
2028
- location: clause.location.keyword
2029
- )
2030
- )
2031
- end
2032
-
2033
- when_constr = test_constr
2034
- end
2035
-
2036
- if els
2037
- node.loc.else or raise
2038
-
2039
- begin_pos = node.loc.else.end_pos
2040
- end_pos = node.loc.end.begin_pos
2041
- typing.add_context(begin_pos..end_pos, context: when_constr.context)
2042
-
2043
- else_result = when_constr.synthesize(els, hint: hint)
2044
-
2045
- if next_branch_reachable
2046
- branch_results << else_result
2047
- end
2048
- end
2049
-
2050
- types = branch_results.map(&:type)
2051
- constrs = branch_results.map(&:constr)
2052
-
2053
- if !next_branch_reachable
2054
- # Exhaustive
2055
- _, _, _, loc = deconstruct_case_node!(node)
2056
-
2057
- # `else` may present even if it's empty
2058
- if loc.else
2059
- if els
2060
- else_result or raise
2061
- unless else_result.type.is_a?(AST::Types::Bot)
2062
- typing.add_error Diagnostic::Ruby::UnreachableValueBranch.new(
2063
- node: els,
2064
- type: else_result.type,
2065
- location: node.loc.else || raise
2066
- )
2067
- end
2068
- end
2069
- end
2070
- else
2071
- unless els
2072
- constrs << when_constr
2073
- types << AST::Builtin.nil_type
2074
- end
2075
- end
1969
+ types, envs = TypeInference::CaseWhen.type_check(constr, node, interpreter, hint: hint, condition: condition)
2076
1970
  else
2077
1971
  branch_results = [] #: Array[Pair]
2078
1972
 
@@ -2131,7 +2025,7 @@ module Steep
2131
2025
  end
2132
2026
 
2133
2027
  types = branch_results.map(&:type)
2134
- constrs = branch_results.map(&:constr)
2028
+ envs = branch_results.map {|result| result.constr.context.type_env }
2135
2029
 
2136
2030
  unless els
2137
2031
  types << AST::Builtin.nil_type
@@ -2139,7 +2033,6 @@ module Steep
2139
2033
  end
2140
2034
 
2141
2035
  constr = constr.update_type_env do |env|
2142
- envs = constrs.map {|c| c.context.type_env }
2143
2036
  env.join(*envs)
2144
2037
  end
2145
2038
 
@@ -2455,11 +2348,8 @@ module Steep
2455
2348
  end
2456
2349
 
2457
2350
  when :defined?
2458
- each_child_node(node) do |child|
2459
- synthesize(child)
2460
- end
2461
-
2462
- add_typing(node, type: AST::Builtin.any_type)
2351
+ type_any_rec(node, only_children: true)
2352
+ add_typing(node, type: AST::Builtin.optional(AST::Builtin::String.instance_type))
2463
2353
 
2464
2354
  when :gvasgn
2465
2355
  yield_self do
@@ -3732,10 +3622,6 @@ module Steep
3732
3622
  end
3733
3623
  end
3734
3624
 
3735
- def inspect
3736
- "#<#{self.class}>"
3737
- end
3738
-
3739
3625
  def with_child_typing(range:)
3740
3626
  constr = with_new_typing(typing.new_child(range))
3741
3627
 
@@ -4774,8 +4660,8 @@ module Steep
4774
4660
  !nodes.empty? && nodes.all? {|child| child.type == :class || child.type == :module}
4775
4661
  end
4776
4662
 
4777
- def type_any_rec(node)
4778
- add_typing node, type: AST::Builtin.any_type
4663
+ def type_any_rec(node, only_children: false)
4664
+ add_typing node, type: AST::Builtin.any_type unless only_children
4779
4665
 
4780
4666
  each_child_node(node) do |child|
4781
4667
  type_any_rec(child)
@@ -5162,22 +5048,6 @@ module Steep
5162
5048
  with_new_typing(typing.parent || raise)
5163
5049
  end
5164
5050
 
5165
- def transform_condition_node(node, var_name)
5166
- case node.type
5167
- when :lvasgn
5168
- name, rhs = node.children
5169
- rhs, value_node = transform_condition_node(rhs, var_name)
5170
- [node.updated(nil, [name, rhs]), value_node]
5171
- when :begin
5172
- *children, last = node.children
5173
- last, value_node = transform_condition_node(last, var_name)
5174
- [node.updated(nil, children.push(last)), value_node]
5175
- else
5176
- var_node = node.updated(:lvar, [var_name])
5177
- [var_node, node]
5178
- end
5179
- end
5180
-
5181
5051
  def type_name(type)
5182
5052
  case type
5183
5053
  when AST::Types::Name::Instance, AST::Types::Name::Singleton
@@ -5228,20 +5098,5 @@ module Steep
5228
5098
  end
5229
5099
  end
5230
5100
  end
5231
-
5232
- def propagate_type_env(source, dest, env)
5233
- source_type = env[source] or raise
5234
-
5235
- if dest.type == :lvar
5236
- var_name = dest.children[0] #: Symbol
5237
- env.assign_local_variable(var_name, source_type, nil)
5238
- else
5239
- if env[dest]
5240
- env.replace_pure_call_type(dest, source_type)
5241
- else
5242
- env
5243
- end
5244
- end
5245
- end
5246
5101
  end
5247
5102
  end