method-ray 0.1.7 → 0.1.9

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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +33 -0
  3. data/{rust → core}/Cargo.toml +1 -1
  4. data/core/src/analyzer/assignments.rs +499 -0
  5. data/{rust → core}/src/analyzer/attributes.rs +2 -1
  6. data/{rust → core}/src/analyzer/blocks.rs +140 -0
  7. data/{rust → core}/src/analyzer/calls.rs +7 -3
  8. data/{rust → core}/src/analyzer/definitions.rs +12 -7
  9. data/{rust → core}/src/analyzer/dispatch.rs +431 -13
  10. data/core/src/analyzer/exceptions.rs +622 -0
  11. data/{rust → core}/src/analyzer/install.rs +37 -1
  12. data/{rust → core}/src/analyzer/literals.rs +3 -17
  13. data/core/src/analyzer/loops.rs +301 -0
  14. data/{rust → core}/src/analyzer/mod.rs +4 -0
  15. data/{rust → core}/src/analyzer/operators.rs +119 -27
  16. data/{rust → core}/src/analyzer/parameters.rs +214 -5
  17. data/core/src/analyzer/super_calls.rs +285 -0
  18. data/{rust → core}/src/cache/rbs_cache.rs +0 -1
  19. data/{rust → core}/src/cli/commands.rs +3 -3
  20. data/{rust → core}/src/diagnostics/diagnostic.rs +0 -3
  21. data/{rust → core}/src/diagnostics/formatter.rs +0 -1
  22. data/{rust → core}/src/env/box_manager.rs +2 -4
  23. data/{rust → core}/src/env/global_env.rs +28 -7
  24. data/{rust → core}/src/env/local_env.rs +35 -1
  25. data/{rust → core}/src/env/method_registry.rs +117 -25
  26. data/{rust → core}/src/env/scope.rs +91 -4
  27. data/{rust → core}/src/env/vertex_manager.rs +0 -1
  28. data/{rust → core}/src/graph/box.rs +134 -8
  29. data/{rust → core}/src/graph/change_set.rs +14 -0
  30. data/{rust → core}/src/lsp/server.rs +1 -1
  31. data/{rust → core}/src/rbs/loader.rs +1 -2
  32. data/{rust → core}/src/source_map.rs +0 -1
  33. data/{rust → core}/src/types.rs +11 -1
  34. data/ext/Cargo.toml +2 -2
  35. data/lib/methodray/binary_locator.rb +2 -2
  36. data/lib/methodray/commands.rb +1 -1
  37. data/lib/methodray/version.rb +1 -1
  38. metadata +54 -50
  39. /data/{rust → core}/src/analyzer/conditionals.rs +0 -0
  40. /data/{rust → core}/src/analyzer/parentheses.rs +0 -0
  41. /data/{rust → core}/src/analyzer/returns.rs +0 -0
  42. /data/{rust → core}/src/analyzer/variables.rs +0 -0
  43. /data/{rust → core}/src/cache/mod.rs +0 -0
  44. /data/{rust → core}/src/checker.rs +0 -0
  45. /data/{rust → core}/src/cli/args.rs +0 -0
  46. /data/{rust → core}/src/cli/mod.rs +0 -0
  47. /data/{rust → core}/src/diagnostics/mod.rs +0 -0
  48. /data/{rust → core}/src/env/mod.rs +0 -0
  49. /data/{rust → core}/src/env/type_error.rs +0 -0
  50. /data/{rust → core}/src/graph/mod.rs +0 -0
  51. /data/{rust → core}/src/graph/vertex.rs +0 -0
  52. /data/{rust → core}/src/lib.rs +0 -0
  53. /data/{rust → core}/src/lsp/diagnostics.rs +0 -0
  54. /data/{rust → core}/src/lsp/main.rs +0 -0
  55. /data/{rust → core}/src/lsp/mod.rs +0 -0
  56. /data/{rust → core}/src/main.rs +0 -0
  57. /data/{rust → core}/src/parser.rs +0 -0
  58. /data/{rust → core}/src/rbs/converter.rs +0 -0
  59. /data/{rust → core}/src/rbs/error.rs +0 -0
  60. /data/{rust → core}/src/rbs/mod.rs +0 -0
@@ -0,0 +1,622 @@
1
+ //! Exceptions - begin/rescue/ensure type inference
2
+ //!
3
+ //! Collects types from each branch and merges them into a Union
4
+ //! via edges into a single result Vertex.
5
+ //! Applies the same MergeVertex pattern as conditionals.rs.
6
+
7
+ use crate::env::{GlobalEnv, LocalEnv};
8
+ use crate::graph::{ChangeSet, VertexId};
9
+ use crate::types::Type;
10
+ use ruby_prism::{BeginNode, RescueModifierNode, RescueNode};
11
+
12
+ use super::bytes_to_name;
13
+ use super::install::{install_node, install_statements};
14
+
15
+ /// Process BeginNode: begin/rescue/else/ensure
16
+ ///
17
+ /// Type aggregation rules:
18
+ /// - No rescue clause: return begin body type directly
19
+ /// - With else clause: else type + all rescue types → Union (begin body excluded)
20
+ /// - Without else clause: begin body type + all rescue types → Union
21
+ /// - Ensure clause: processed for side effects only, does not affect return type
22
+ pub(crate) fn process_begin_node(
23
+ genv: &mut GlobalEnv,
24
+ lenv: &mut LocalEnv,
25
+ changes: &mut ChangeSet,
26
+ source: &str,
27
+ begin_node: &BeginNode,
28
+ ) -> Option<VertexId> {
29
+ let begin_vtx = begin_node
30
+ .statements()
31
+ .and_then(|s| install_statements(genv, lenv, changes, source, &s));
32
+
33
+ let result = if let Some(rescue_node) = begin_node.rescue_clause() {
34
+ let result_vtx = genv.new_vertex();
35
+
36
+ process_rescue_chain(genv, lenv, changes, source, &rescue_node, result_vtx);
37
+
38
+ if let Some(else_node) = begin_node.else_clause() {
39
+ // With else: else type replaces begin body type (Ruby spec)
40
+ let else_vtx = else_node
41
+ .statements()
42
+ .and_then(|s| install_statements(genv, lenv, changes, source, &s));
43
+ if let Some(vtx) = else_vtx {
44
+ genv.add_edge(vtx, result_vtx);
45
+ }
46
+ } else if let Some(vtx) = begin_vtx {
47
+ genv.add_edge(vtx, result_vtx);
48
+ }
49
+
50
+ Some(result_vtx)
51
+ } else {
52
+ begin_vtx
53
+ };
54
+
55
+ // Ensure: side effects only, does not affect return type
56
+ if let Some(ensure_node) = begin_node.ensure_clause() {
57
+ if let Some(stmts) = ensure_node.statements() {
58
+ let _ = install_statements(genv, lenv, changes, source, &stmts);
59
+ }
60
+ }
61
+
62
+ result
63
+ }
64
+
65
+ /// Process RescueNode chain recursively.
66
+ /// Empty rescue body evaluates to nil.
67
+ fn process_rescue_chain(
68
+ genv: &mut GlobalEnv,
69
+ lenv: &mut LocalEnv,
70
+ changes: &mut ChangeSet,
71
+ source: &str,
72
+ rescue_node: &RescueNode,
73
+ result_vtx: VertexId,
74
+ ) {
75
+ let body_vtx = process_rescue_body(genv, lenv, changes, source, rescue_node);
76
+ let vtx = body_vtx.unwrap_or_else(|| genv.new_source(Type::Nil));
77
+ genv.add_edge(vtx, result_vtx);
78
+
79
+ if let Some(next) = rescue_node.subsequent() {
80
+ process_rescue_chain(genv, lenv, changes, source, &next, result_vtx);
81
+ }
82
+ }
83
+
84
+ /// Extract the exception type from rescue node's exception class list.
85
+ /// Falls back to StandardError when no exceptions are specified or none can be resolved.
86
+ // TODO: Non-constant exception expressions (method calls, splats, variables) are silently skipped.
87
+ fn extract_exception_type(rescue_node: &RescueNode) -> Type {
88
+ let types: Vec<Type> = rescue_node
89
+ .exceptions()
90
+ .iter()
91
+ .filter_map(|exc| super::definitions::extract_constant_path(&exc))
92
+ .map(|name| Type::instance(&name))
93
+ .collect();
94
+
95
+ if types.is_empty() {
96
+ Type::instance("StandardError")
97
+ } else {
98
+ Type::union_of(types)
99
+ }
100
+ }
101
+
102
+ /// Process a single RescueNode body.
103
+ /// Registers the rescue variable (=> e), processes the body,
104
+ /// then removes the variable from scope.
105
+ fn process_rescue_body(
106
+ genv: &mut GlobalEnv,
107
+ lenv: &mut LocalEnv,
108
+ changes: &mut ChangeSet,
109
+ source: &str,
110
+ rescue_node: &RescueNode,
111
+ ) -> Option<VertexId> {
112
+ for exc in &rescue_node.exceptions() {
113
+ install_node(genv, lenv, changes, source, &exc);
114
+ }
115
+
116
+ // Save/restore rescue variable binding (=> e)
117
+ // TODO: Only LocalVariableTargetNode is handled; instance/global/class vars are not yet supported.
118
+ let var_binding = if let Some(ref_node) = rescue_node.reference() {
119
+ ref_node.as_local_variable_target_node().map(|target| {
120
+ let name = bytes_to_name(target.name().as_slice());
121
+ let saved = lenv.get_var(&name);
122
+ let exception_vtx = genv.new_vertex();
123
+ let exception_type = extract_exception_type(rescue_node);
124
+ let exception_src = genv.new_source(exception_type);
125
+ genv.add_edge(exception_src, exception_vtx);
126
+ lenv.new_var(name.clone(), exception_vtx);
127
+ (name, saved)
128
+ })
129
+ } else {
130
+ None
131
+ };
132
+
133
+ let body_vtx = rescue_node
134
+ .statements()
135
+ .and_then(|s| install_statements(genv, lenv, changes, source, &s));
136
+
137
+ if let Some((name, saved)) = var_binding {
138
+ match saved {
139
+ Some(prev_vtx) => lenv.new_var(name, prev_vtx),
140
+ None => lenv.remove_var(&name),
141
+ }
142
+ }
143
+
144
+ body_vtx
145
+ }
146
+
147
+ /// Process RescueModifierNode: `expression rescue rescue_expression`
148
+ pub(crate) fn process_rescue_modifier_node(
149
+ genv: &mut GlobalEnv,
150
+ lenv: &mut LocalEnv,
151
+ changes: &mut ChangeSet,
152
+ source: &str,
153
+ node: &RescueModifierNode,
154
+ ) -> Option<VertexId> {
155
+ let result_vtx = genv.new_vertex();
156
+
157
+ let expr_vtx = install_node(genv, lenv, changes, source, &node.expression());
158
+ if let Some(vtx) = expr_vtx {
159
+ genv.add_edge(vtx, result_vtx);
160
+ }
161
+
162
+ let rescue_vtx = install_node(genv, lenv, changes, source, &node.rescue_expression());
163
+ if let Some(vtx) = rescue_vtx {
164
+ genv.add_edge(vtx, result_vtx);
165
+ }
166
+
167
+ Some(result_vtx)
168
+ }
169
+
170
+ #[cfg(test)]
171
+ mod tests {
172
+ use crate::analyzer::install::AstInstaller;
173
+ use crate::env::{GlobalEnv, LocalEnv};
174
+ use crate::graph::VertexId;
175
+ use crate::parser::ParseSession;
176
+ use crate::types::Type;
177
+
178
+ /// Helper: parse Ruby source, process with AstInstaller, and return GlobalEnv
179
+ fn analyze(source: &str) -> GlobalEnv {
180
+ let session = ParseSession::new();
181
+ let parse_result = session.parse_source(source, "test.rb").unwrap();
182
+ let root = parse_result.node();
183
+ let program = root.as_program_node().unwrap();
184
+
185
+ let mut genv = GlobalEnv::new();
186
+ let mut lenv = LocalEnv::new();
187
+
188
+ let mut installer = AstInstaller::new(&mut genv, &mut lenv, source);
189
+ for stmt in &program.statements().body() {
190
+ installer.install_node(&stmt);
191
+ }
192
+ installer.finish();
193
+
194
+ genv
195
+ }
196
+
197
+ /// Helper: get the type string for a vertex ID (checks both Vertex and Source)
198
+ fn get_type_show(genv: &GlobalEnv, vtx: VertexId) -> String {
199
+ if let Some(vertex) = genv.get_vertex(vtx) {
200
+ vertex.show()
201
+ } else if let Some(source) = genv.get_source(vtx) {
202
+ source.ty.show()
203
+ } else {
204
+ panic!("vertex {:?} not found as either Vertex or Source", vtx);
205
+ }
206
+ }
207
+
208
+ #[test]
209
+ fn test_begin_rescue_basic() {
210
+ let source = r#"
211
+ class Foo
212
+ def bar
213
+ begin
214
+ "hello"
215
+ rescue
216
+ 42
217
+ end
218
+ end
219
+ end
220
+ "#;
221
+ let genv = analyze(source);
222
+ let info = genv
223
+ .resolve_method(&Type::instance("Foo"), "bar")
224
+ .expect("Foo#bar should be registered");
225
+ let ret_vtx = info.return_vertex.unwrap();
226
+ assert_eq!(get_type_show(&genv, ret_vtx), "(Integer | String)");
227
+ }
228
+
229
+ #[test]
230
+ fn test_begin_rescue_else() {
231
+ let source = r#"
232
+ class Foo
233
+ def bar
234
+ begin
235
+ "hello"
236
+ rescue
237
+ 42
238
+ else
239
+ :ok
240
+ end
241
+ end
242
+ end
243
+ "#;
244
+ let genv = analyze(source);
245
+ let info = genv
246
+ .resolve_method(&Type::instance("Foo"), "bar")
247
+ .expect("Foo#bar should be registered");
248
+ let ret_vtx = info.return_vertex.unwrap();
249
+ let type_str = get_type_show(&genv, ret_vtx);
250
+ // else present: begin body excluded, else + rescue types
251
+ assert!(type_str.contains("Symbol"), "should contain Symbol: {}", type_str);
252
+ assert!(type_str.contains("Integer"), "should contain Integer: {}", type_str);
253
+ assert!(!type_str.contains("String"), "should NOT contain String: {}", type_str);
254
+ }
255
+
256
+ #[test]
257
+ fn test_begin_ensure_only() {
258
+ let source = r#"
259
+ class Foo
260
+ def bar
261
+ begin
262
+ "hello"
263
+ ensure
264
+ puts "cleanup"
265
+ end
266
+ end
267
+ end
268
+ "#;
269
+ let genv = analyze(source);
270
+ let info = genv
271
+ .resolve_method(&Type::instance("Foo"), "bar")
272
+ .expect("Foo#bar should be registered");
273
+ let ret_vtx = info.return_vertex.unwrap();
274
+ assert_eq!(get_type_show(&genv, ret_vtx), "String");
275
+ }
276
+
277
+ #[test]
278
+ fn test_begin_rescue_ensure() {
279
+ let source = r#"
280
+ class Foo
281
+ def bar
282
+ begin
283
+ "hello"
284
+ rescue
285
+ 42
286
+ ensure
287
+ :cleanup
288
+ end
289
+ end
290
+ end
291
+ "#;
292
+ let genv = analyze(source);
293
+ let info = genv
294
+ .resolve_method(&Type::instance("Foo"), "bar")
295
+ .expect("Foo#bar should be registered");
296
+ let ret_vtx = info.return_vertex.unwrap();
297
+ let type_str = get_type_show(&genv, ret_vtx);
298
+ // ensure does not affect return type
299
+ assert_eq!(type_str, "(Integer | String)");
300
+ }
301
+
302
+ #[test]
303
+ fn test_rescue_variable_type() {
304
+ let source = r#"
305
+ class Foo
306
+ def bar
307
+ begin
308
+ "hello"
309
+ rescue => e
310
+ e
311
+ end
312
+ end
313
+ end
314
+ "#;
315
+ let genv = analyze(source);
316
+ let info = genv
317
+ .resolve_method(&Type::instance("Foo"), "bar")
318
+ .expect("Foo#bar should be registered");
319
+ let ret_vtx = info.return_vertex.unwrap();
320
+ let type_str = get_type_show(&genv, ret_vtx);
321
+ assert!(
322
+ type_str.contains("StandardError"),
323
+ "should contain StandardError: {}",
324
+ type_str
325
+ );
326
+ }
327
+
328
+ #[test]
329
+ fn test_multiple_rescue_clauses() {
330
+ let source = r#"
331
+ class Foo
332
+ def bar
333
+ begin
334
+ "hello"
335
+ rescue ArgumentError
336
+ 42
337
+ rescue RuntimeError
338
+ :error
339
+ end
340
+ end
341
+ end
342
+ "#;
343
+ let genv = analyze(source);
344
+ let info = genv
345
+ .resolve_method(&Type::instance("Foo"), "bar")
346
+ .expect("Foo#bar should be registered");
347
+ let ret_vtx = info.return_vertex.unwrap();
348
+ let type_str = get_type_show(&genv, ret_vtx);
349
+ assert!(type_str.contains("String"), "should contain String: {}", type_str);
350
+ assert!(type_str.contains("Integer"), "should contain Integer: {}", type_str);
351
+ assert!(type_str.contains("Symbol"), "should contain Symbol: {}", type_str);
352
+ }
353
+
354
+ #[test]
355
+ fn test_rescue_modifier_basic() {
356
+ let source = r#"
357
+ class Foo
358
+ def bar
359
+ "hello" rescue 42
360
+ end
361
+ end
362
+ "#;
363
+ let genv = analyze(source);
364
+ let info = genv
365
+ .resolve_method(&Type::instance("Foo"), "bar")
366
+ .expect("Foo#bar should be registered");
367
+ let ret_vtx = info.return_vertex.unwrap();
368
+ assert_eq!(get_type_show(&genv, ret_vtx), "(Integer | String)");
369
+ }
370
+
371
+ #[test]
372
+ fn test_rescue_modifier_same_type() {
373
+ let source = r#"
374
+ class Foo
375
+ def bar
376
+ "hello" rescue "world"
377
+ end
378
+ end
379
+ "#;
380
+ let genv = analyze(source);
381
+ let info = genv
382
+ .resolve_method(&Type::instance("Foo"), "bar")
383
+ .expect("Foo#bar should be registered");
384
+ let ret_vtx = info.return_vertex.unwrap();
385
+ assert_eq!(get_type_show(&genv, ret_vtx), "String");
386
+ }
387
+
388
+ #[test]
389
+ fn test_nested_begin_rescue() {
390
+ let source = r#"
391
+ class Foo
392
+ def bar
393
+ begin
394
+ begin
395
+ "inner"
396
+ rescue
397
+ 42
398
+ end
399
+ rescue
400
+ :outer
401
+ end
402
+ end
403
+ end
404
+ "#;
405
+ let genv = analyze(source);
406
+ let info = genv
407
+ .resolve_method(&Type::instance("Foo"), "bar")
408
+ .expect("Foo#bar should be registered");
409
+ let ret_vtx = info.return_vertex.unwrap();
410
+ let type_str = get_type_show(&genv, ret_vtx);
411
+ // Outer: Union(inner_begin_rescue | :outer) = (Integer | String | Symbol)
412
+ assert!(type_str.contains("Integer"), "should contain Integer: {}", type_str);
413
+ assert!(type_str.contains("String"), "should contain String: {}", type_str);
414
+ assert!(type_str.contains("Symbol"), "should contain Symbol: {}", type_str);
415
+ }
416
+
417
+ #[test]
418
+ fn test_begin_rescue_in_method() {
419
+ let source = r#"
420
+ class Foo
421
+ def bar
422
+ x = begin
423
+ "hello"
424
+ rescue
425
+ 42
426
+ end
427
+ x
428
+ end
429
+ end
430
+ "#;
431
+ let genv = analyze(source);
432
+ let info = genv
433
+ .resolve_method(&Type::instance("Foo"), "bar")
434
+ .expect("Foo#bar should be registered");
435
+ let ret_vtx = info.return_vertex.unwrap();
436
+ assert_eq!(get_type_show(&genv, ret_vtx), "(Integer | String)");
437
+ }
438
+
439
+ #[test]
440
+ fn test_ensure_side_effects() {
441
+ // ensure body should be processed (no panic) but not affect return type
442
+ let source = r#"
443
+ class Foo
444
+ def bar
445
+ begin
446
+ "hello"
447
+ rescue
448
+ 42
449
+ ensure
450
+ x = "side_effect"
451
+ end
452
+ end
453
+ end
454
+ "#;
455
+ let genv = analyze(source);
456
+ let info = genv
457
+ .resolve_method(&Type::instance("Foo"), "bar")
458
+ .expect("Foo#bar should be registered");
459
+ let ret_vtx = info.return_vertex.unwrap();
460
+ assert_eq!(get_type_show(&genv, ret_vtx), "(Integer | String)");
461
+ }
462
+
463
+ #[test]
464
+ fn test_rescue_variable_scope_restore() {
465
+ // Rescue variable should not destroy outer binding
466
+ let source = r#"
467
+ class Foo
468
+ def bar
469
+ e = "outer"
470
+ begin
471
+ "hello"
472
+ rescue => e
473
+ e
474
+ end
475
+ e
476
+ end
477
+ end
478
+ "#;
479
+ let genv = analyze(source);
480
+ let info = genv
481
+ .resolve_method(&Type::instance("Foo"), "bar")
482
+ .expect("Foo#bar should be registered");
483
+ let ret_vtx = info.return_vertex.unwrap();
484
+ let type_str = get_type_show(&genv, ret_vtx);
485
+ // After rescue block, e should be restored to outer binding (String)
486
+ assert!(type_str.contains("String"), "should contain String: {}", type_str);
487
+ }
488
+
489
+ #[test]
490
+ fn test_rescue_variable_scope_removal() {
491
+ // When rescue variable has no prior binding, it should be removed after rescue block
492
+ let source = r#"
493
+ class Foo
494
+ def bar
495
+ begin
496
+ "hello"
497
+ rescue => e
498
+ e
499
+ end
500
+ end
501
+ end
502
+ "#;
503
+ let genv = analyze(source);
504
+ let info = genv
505
+ .resolve_method(&Type::instance("Foo"), "bar")
506
+ .expect("Foo#bar should be registered");
507
+ let ret_vtx = info.return_vertex.unwrap();
508
+ let type_str = get_type_show(&genv, ret_vtx);
509
+ // begin body (String) + rescue body where e = StandardError
510
+ assert!(type_str.contains("String"), "should contain String: {}", type_str);
511
+ assert!(
512
+ type_str.contains("StandardError"),
513
+ "should contain StandardError: {}",
514
+ type_str
515
+ );
516
+ }
517
+
518
+ #[test]
519
+ fn test_rescue_specific_exception_class() {
520
+ let source = r#"
521
+ class Foo
522
+ def bar
523
+ begin
524
+ "hello"
525
+ rescue ArgumentError => e
526
+ e
527
+ end
528
+ end
529
+ end
530
+ "#;
531
+ let genv = analyze(source);
532
+ let info = genv
533
+ .resolve_method(&Type::instance("Foo"), "bar")
534
+ .expect("Foo#bar should be registered");
535
+ let ret_vtx = info.return_vertex.unwrap();
536
+ let type_str = get_type_show(&genv, ret_vtx);
537
+ assert!(
538
+ type_str.contains("ArgumentError"),
539
+ "should contain ArgumentError: {}",
540
+ type_str
541
+ );
542
+ }
543
+
544
+ #[test]
545
+ fn test_rescue_multiple_exception_classes() {
546
+ let source = r#"
547
+ class Foo
548
+ def bar
549
+ begin
550
+ "hello"
551
+ rescue TypeError, NameError => e
552
+ e
553
+ end
554
+ end
555
+ end
556
+ "#;
557
+ let genv = analyze(source);
558
+ let info = genv
559
+ .resolve_method(&Type::instance("Foo"), "bar")
560
+ .expect("Foo#bar should be registered");
561
+ let ret_vtx = info.return_vertex.unwrap();
562
+ let type_str = get_type_show(&genv, ret_vtx);
563
+ assert!(
564
+ type_str.contains("TypeError"),
565
+ "should contain TypeError: {}",
566
+ type_str
567
+ );
568
+ assert!(
569
+ type_str.contains("NameError"),
570
+ "should contain NameError: {}",
571
+ type_str
572
+ );
573
+ }
574
+
575
+ #[test]
576
+ fn test_rescue_qualified_exception_class() {
577
+ let source = r#"
578
+ class Foo
579
+ def bar
580
+ begin
581
+ "hello"
582
+ rescue Net::HTTPError => e
583
+ e
584
+ end
585
+ end
586
+ end
587
+ "#;
588
+ let genv = analyze(source);
589
+ let info = genv
590
+ .resolve_method(&Type::instance("Foo"), "bar")
591
+ .expect("Foo#bar should be registered");
592
+ let ret_vtx = info.return_vertex.unwrap();
593
+ let type_str = get_type_show(&genv, ret_vtx);
594
+ assert!(
595
+ type_str.contains("Net::HTTPError"),
596
+ "should contain Net::HTTPError: {}",
597
+ type_str
598
+ );
599
+ }
600
+
601
+ #[test]
602
+ fn test_empty_rescue_body_is_nil() {
603
+ let source = r#"
604
+ class Foo
605
+ def bar
606
+ begin
607
+ "hello"
608
+ rescue
609
+ end
610
+ end
611
+ end
612
+ "#;
613
+ let genv = analyze(source);
614
+ let info = genv
615
+ .resolve_method(&Type::instance("Foo"), "bar")
616
+ .expect("Foo#bar should be registered");
617
+ let ret_vtx = info.return_vertex.unwrap();
618
+ let type_str = get_type_show(&genv, ret_vtx);
619
+ assert!(type_str.contains("String"), "should contain String: {}", type_str);
620
+ assert!(type_str.contains("nil"), "should contain nil: {}", type_str);
621
+ }
622
+ }
@@ -8,14 +8,18 @@ use crate::env::{GlobalEnv, LocalEnv};
8
8
  use crate::graph::{ChangeSet, VertexId};
9
9
  use ruby_prism::Node;
10
10
 
11
+ use super::assignments::process_multi_write_node;
11
12
  use super::blocks::process_block_node;
12
13
  use super::conditionals::{process_case_node, process_if_node, process_unless_node};
13
14
  use super::definitions::{process_class_node, process_def_node, process_module_node};
15
+ use super::exceptions::{process_begin_node, process_rescue_modifier_node};
14
16
  use super::dispatch::{dispatch_needs_child, dispatch_simple, process_needs_child, DispatchResult};
15
17
  use super::literals::install_literal_node;
16
- use super::parentheses::process_parentheses_node;
18
+ use super::loops::{process_for_node, process_until_node, process_while_node};
17
19
  use super::operators::{process_and_node, process_or_node};
20
+ use super::parentheses::process_parentheses_node;
18
21
  use super::returns::process_return_node;
22
+ use super::super_calls;
19
23
 
20
24
  /// Build graph from AST (public API wrapper)
21
25
  pub struct AstInstaller<'a> {
@@ -79,6 +83,34 @@ pub(crate) fn install_node(
79
83
  return process_case_node(genv, lenv, changes, source, &case_node);
80
84
  }
81
85
 
86
+ if let Some(begin_node) = node.as_begin_node() {
87
+ return process_begin_node(genv, lenv, changes, source, &begin_node);
88
+ }
89
+ if let Some(rescue_modifier) = node.as_rescue_modifier_node() {
90
+ return process_rescue_modifier_node(genv, lenv, changes, source, &rescue_modifier);
91
+ }
92
+
93
+ // SuperNode: super(args) — explicit arguments
94
+ if let Some(super_node) = node.as_super_node() {
95
+ return super_calls::process_super_node(genv, lenv, changes, source, &super_node);
96
+ }
97
+ // ForwardingSuperNode: super — implicit argument forwarding
98
+ if let Some(fwd_super_node) = node.as_forwarding_super_node() {
99
+ return super_calls::process_forwarding_super_node(
100
+ genv, lenv, changes, source, &fwd_super_node,
101
+ );
102
+ }
103
+
104
+ if let Some(while_node) = node.as_while_node() {
105
+ return process_while_node(genv, lenv, changes, source, &while_node);
106
+ }
107
+ if let Some(until_node) = node.as_until_node() {
108
+ return process_until_node(genv, lenv, changes, source, &until_node);
109
+ }
110
+ if let Some(for_node) = node.as_for_node() {
111
+ return process_for_node(genv, lenv, changes, source, &for_node);
112
+ }
113
+
82
114
  if let Some(paren_node) = node.as_parentheses_node() {
83
115
  return process_parentheses_node(genv, lenv, changes, source, &paren_node);
84
116
  }
@@ -94,6 +126,10 @@ pub(crate) fn install_node(
94
126
  return process_or_node(genv, lenv, changes, source, &or_node);
95
127
  }
96
128
 
129
+ if let Some(multi_write) = node.as_multi_write_node() {
130
+ return process_multi_write_node(genv, lenv, changes, source, &multi_write);
131
+ }
132
+
97
133
  match dispatch_simple(genv, lenv, node) {
98
134
  DispatchResult::Vertex(vtx) => return Some(vtx),
99
135
  DispatchResult::NotHandled => {}