steep 1.6.0 → 1.7.0.dev.2

Sign up to get free protection for your applications and to get access to all the features.
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