method-ray 0.1.8 → 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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -0
  3. data/{rust → core}/Cargo.toml +1 -1
  4. data/core/src/analyzer/assignments.rs +499 -0
  5. data/{rust → core}/src/analyzer/blocks.rs +140 -0
  6. data/{rust → core}/src/analyzer/definitions.rs +6 -5
  7. data/{rust → core}/src/analyzer/dispatch.rs +295 -31
  8. data/{rust → core}/src/analyzer/exceptions.rs +104 -3
  9. data/{rust → core}/src/analyzer/install.rs +16 -1
  10. data/{rust → core}/src/analyzer/literals.rs +3 -17
  11. data/{rust → core}/src/analyzer/loops.rs +126 -1
  12. data/{rust → core}/src/analyzer/mod.rs +1 -0
  13. data/{rust → core}/src/analyzer/parameters.rs +160 -0
  14. data/core/src/analyzer/super_calls.rs +285 -0
  15. data/{rust → core}/src/env/global_env.rs +18 -4
  16. data/{rust → core}/src/env/method_registry.rs +109 -23
  17. data/{rust → core}/src/env/scope.rs +78 -0
  18. data/{rust → core}/src/types.rs +11 -0
  19. data/ext/Cargo.toml +2 -2
  20. data/lib/methodray/binary_locator.rb +2 -2
  21. data/lib/methodray/commands.rb +1 -1
  22. data/lib/methodray/version.rb +1 -1
  23. metadata +54 -53
  24. data/rust/src/analyzer/assignments.rs +0 -152
  25. /data/{rust → core}/src/analyzer/attributes.rs +0 -0
  26. /data/{rust → core}/src/analyzer/calls.rs +0 -0
  27. /data/{rust → core}/src/analyzer/conditionals.rs +0 -0
  28. /data/{rust → core}/src/analyzer/operators.rs +0 -0
  29. /data/{rust → core}/src/analyzer/parentheses.rs +0 -0
  30. /data/{rust → core}/src/analyzer/returns.rs +0 -0
  31. /data/{rust → core}/src/analyzer/variables.rs +0 -0
  32. /data/{rust → core}/src/cache/mod.rs +0 -0
  33. /data/{rust → core}/src/cache/rbs_cache.rs +0 -0
  34. /data/{rust → core}/src/checker.rs +0 -0
  35. /data/{rust → core}/src/cli/args.rs +0 -0
  36. /data/{rust → core}/src/cli/commands.rs +0 -0
  37. /data/{rust → core}/src/cli/mod.rs +0 -0
  38. /data/{rust → core}/src/diagnostics/diagnostic.rs +0 -0
  39. /data/{rust → core}/src/diagnostics/formatter.rs +0 -0
  40. /data/{rust → core}/src/diagnostics/mod.rs +0 -0
  41. /data/{rust → core}/src/env/box_manager.rs +0 -0
  42. /data/{rust → core}/src/env/local_env.rs +0 -0
  43. /data/{rust → core}/src/env/mod.rs +0 -0
  44. /data/{rust → core}/src/env/type_error.rs +0 -0
  45. /data/{rust → core}/src/env/vertex_manager.rs +0 -0
  46. /data/{rust → core}/src/graph/box.rs +0 -0
  47. /data/{rust → core}/src/graph/change_set.rs +0 -0
  48. /data/{rust → core}/src/graph/mod.rs +0 -0
  49. /data/{rust → core}/src/graph/vertex.rs +0 -0
  50. /data/{rust → core}/src/lib.rs +0 -0
  51. /data/{rust → core}/src/lsp/diagnostics.rs +0 -0
  52. /data/{rust → core}/src/lsp/main.rs +0 -0
  53. /data/{rust → core}/src/lsp/mod.rs +0 -0
  54. /data/{rust → core}/src/lsp/server.rs +0 -0
  55. /data/{rust → core}/src/main.rs +0 -0
  56. /data/{rust → core}/src/parser.rs +0 -0
  57. /data/{rust → core}/src/rbs/converter.rs +0 -0
  58. /data/{rust → core}/src/rbs/error.rs +0 -0
  59. /data/{rust → core}/src/rbs/loader.rs +0 -0
  60. /data/{rust → core}/src/rbs/mod.rs +0 -0
  61. /data/{rust → core}/src/source_map.rs +0 -0
@@ -15,10 +15,11 @@ use super::definitions::{process_class_node, process_def_node, process_module_no
15
15
  use super::exceptions::{process_begin_node, process_rescue_modifier_node};
16
16
  use super::dispatch::{dispatch_needs_child, dispatch_simple, process_needs_child, DispatchResult};
17
17
  use super::literals::install_literal_node;
18
- use super::loops::{process_until_node, process_while_node};
18
+ use super::loops::{process_for_node, process_until_node, process_while_node};
19
19
  use super::operators::{process_and_node, process_or_node};
20
20
  use super::parentheses::process_parentheses_node;
21
21
  use super::returns::process_return_node;
22
+ use super::super_calls;
22
23
 
23
24
  /// Build graph from AST (public API wrapper)
24
25
  pub struct AstInstaller<'a> {
@@ -89,12 +90,26 @@ pub(crate) fn install_node(
89
90
  return process_rescue_modifier_node(genv, lenv, changes, source, &rescue_modifier);
90
91
  }
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
+
92
104
  if let Some(while_node) = node.as_while_node() {
93
105
  return process_while_node(genv, lenv, changes, source, &while_node);
94
106
  }
95
107
  if let Some(until_node) = node.as_until_node() {
96
108
  return process_until_node(genv, lenv, changes, source, &until_node);
97
109
  }
110
+ if let Some(for_node) = node.as_for_node() {
111
+ return process_for_node(genv, lenv, changes, source, &for_node);
112
+ }
98
113
 
99
114
  if let Some(paren_node) = node.as_parentheses_node() {
100
115
  return process_parentheses_node(genv, lenv, changes, source, &paren_node);
@@ -118,13 +118,9 @@ fn install_array_literal_elements(
118
118
 
119
119
  let array_type = if element_types.is_empty() {
120
120
  Type::array()
121
- } else if element_types.len() == 1 {
122
- let elem_type = element_types.into_iter().next().unwrap();
123
- Type::array_of(elem_type)
124
121
  } else {
125
122
  let types_vec: Vec<Type> = element_types.into_iter().collect();
126
- let union_type = Type::Union(types_vec);
127
- Type::array_of(union_type)
123
+ Type::array_of(Type::union_of(types_vec))
128
124
  };
129
125
 
130
126
  Some(genv.new_source(array_type))
@@ -176,18 +172,8 @@ fn install_hash_literal_elements(
176
172
  let hash_type = if key_types.is_empty() || value_types.is_empty() {
177
173
  Type::hash()
178
174
  } else {
179
- let key_type = if key_types.len() == 1 {
180
- key_types.into_iter().next().unwrap()
181
- } else {
182
- let types_vec: Vec<Type> = key_types.into_iter().collect();
183
- Type::Union(types_vec)
184
- };
185
- let value_type = if value_types.len() == 1 {
186
- value_types.into_iter().next().unwrap()
187
- } else {
188
- let types_vec: Vec<Type> = value_types.into_iter().collect();
189
- Type::Union(types_vec)
190
- };
175
+ let key_type = Type::union_of(key_types.into_iter().collect());
176
+ let value_type = Type::union_of(value_types.into_iter().collect());
191
177
  Type::hash_of(key_type, value_type)
192
178
  };
193
179
 
@@ -6,8 +6,9 @@
6
6
  use crate::env::{GlobalEnv, LocalEnv};
7
7
  use crate::graph::{ChangeSet, VertexId};
8
8
  use crate::types::Type;
9
- use ruby_prism::{UntilNode, WhileNode};
9
+ use ruby_prism::{ForNode, UntilNode, WhileNode};
10
10
 
11
+ use super::bytes_to_name;
11
12
  use super::install::{install_node, install_statements};
12
13
 
13
14
  /// Process WhileNode: `while predicate; statements; end`
@@ -50,6 +51,48 @@ pub(crate) fn process_until_node(
50
51
  Some(genv.new_source(Type::Nil))
51
52
  }
52
53
 
54
+ /// Process ForNode: `for index in collection; statements; end`
55
+ ///
56
+ /// Ruby's `for` does NOT create a new scope — the loop variable persists
57
+ /// after the loop. This differs from `collection.each { |x| }` which
58
+ /// creates a block scope.
59
+ ///
60
+ /// Returns nil (consistent with while/until; Ruby's for returns the
61
+ /// collection, but the return value is rarely used in practice).
62
+ pub(crate) fn process_for_node(
63
+ genv: &mut GlobalEnv,
64
+ lenv: &mut LocalEnv,
65
+ changes: &mut ChangeSet,
66
+ source: &str,
67
+ for_node: &ForNode,
68
+ ) -> Option<VertexId> {
69
+ let collection_vtx = install_node(genv, lenv, changes, source, &for_node.collection());
70
+
71
+ // TODO: MultiTargetNode (e.g., `for a, b in [[1, "x"]]`) is not yet supported
72
+ if let Some(target) = for_node.index().as_local_variable_target_node() {
73
+ let name = bytes_to_name(target.name().as_slice());
74
+ let var_vtx = genv.new_vertex();
75
+
76
+ // Array[T] or Range[T] → loop var gets T
77
+ let elem_type = collection_vtx
78
+ .and_then(|vtx| genv.get_source(vtx))
79
+ .and_then(|src| src.ty.type_args())
80
+ .and_then(|args| args.first().cloned());
81
+ if let Some(ty) = elem_type {
82
+ let elem_src = genv.new_source(ty);
83
+ genv.add_edge(elem_src, var_vtx);
84
+ }
85
+
86
+ lenv.new_var(name, var_vtx);
87
+ }
88
+
89
+ if let Some(stmts) = for_node.statements() {
90
+ install_statements(genv, lenv, changes, source, &stmts);
91
+ }
92
+
93
+ Some(genv.new_source(Type::Nil))
94
+ }
95
+
53
96
  #[cfg(test)]
54
97
  mod tests {
55
98
  use crate::analyzer::install::AstInstaller;
@@ -173,4 +216,86 @@ end
173
216
  let ret_vtx = info.return_vertex.unwrap();
174
217
  assert_eq!(get_type_show(&genv, ret_vtx), "nil");
175
218
  }
219
+
220
+ // --- for loop tests ---
221
+
222
+ #[test]
223
+ fn test_for_returns_nil() {
224
+ let source = r#"
225
+ class Foo
226
+ def bar
227
+ for x in [1, 2, 3]
228
+ x
229
+ end
230
+ end
231
+ end
232
+ "#;
233
+ let genv = analyze(source);
234
+ let info = genv
235
+ .resolve_method(&Type::instance("Foo"), "bar")
236
+ .expect("Foo#bar should be registered");
237
+ let ret_vtx = info.return_vertex.unwrap();
238
+ assert_eq!(get_type_show(&genv, ret_vtx), "nil");
239
+ }
240
+
241
+ #[test]
242
+ fn test_for_variable_type_from_array() {
243
+ let source = r#"
244
+ for item in [1, 2, 3]
245
+ item
246
+ end
247
+ "#;
248
+ // Should not panic; loop variable is registered
249
+ analyze(source);
250
+ }
251
+
252
+ #[test]
253
+ fn test_for_variable_persists_after_loop() {
254
+ // for does NOT create a new scope — variable persists
255
+ let source = r#"
256
+ class Foo
257
+ def bar
258
+ for x in [1, 2, 3]
259
+ end
260
+ x
261
+ end
262
+ end
263
+ "#;
264
+ // Should not panic — x is accessible after the loop
265
+ analyze(source);
266
+ }
267
+
268
+ #[test]
269
+ fn test_for_empty_body() {
270
+ let source = r#"
271
+ class Foo
272
+ def bar
273
+ for x in [1, 2, 3]
274
+ end
275
+ end
276
+ end
277
+ "#;
278
+ let genv = analyze(source);
279
+ let info = genv
280
+ .resolve_method(&Type::instance("Foo"), "bar")
281
+ .expect("Foo#bar should be registered");
282
+ let ret_vtx = info.return_vertex.unwrap();
283
+ assert_eq!(get_type_show(&genv, ret_vtx), "nil");
284
+ }
285
+
286
+ #[test]
287
+ fn test_for_with_method_call_in_body() {
288
+ // Should not panic — method call on loop variable is processed
289
+ // (type error check requires RBS, covered by Ruby integration test)
290
+ let source = r#"
291
+ class Foo
292
+ def bar
293
+ for item in ["hello", "world"]
294
+ item.upcase
295
+ end
296
+ end
297
+ end
298
+ "#;
299
+ analyze(source);
300
+ }
176
301
  }
@@ -13,6 +13,7 @@ mod operators;
13
13
  mod parameters;
14
14
  mod parentheses;
15
15
  mod returns;
16
+ mod super_calls;
16
17
  mod variables;
17
18
 
18
19
  pub use install::AstInstaller;
@@ -208,6 +208,36 @@ pub(crate) fn install_parameters(
208
208
  #[cfg(test)]
209
209
  mod tests {
210
210
  use super::*;
211
+ use crate::analyzer::install::AstInstaller;
212
+ use crate::parser::ParseSession;
213
+
214
+ fn analyze(source: &str) -> GlobalEnv {
215
+ let session = ParseSession::new();
216
+ let parse_result = session.parse_source(source, "test.rb").unwrap();
217
+ let root = parse_result.node();
218
+ let program = root.as_program_node().unwrap();
219
+
220
+ let mut genv = GlobalEnv::new();
221
+ let mut lenv = LocalEnv::new();
222
+
223
+ let mut installer = AstInstaller::new(&mut genv, &mut lenv, source);
224
+ for stmt in &program.statements().body() {
225
+ installer.install_node(&stmt);
226
+ }
227
+ installer.finish();
228
+
229
+ genv
230
+ }
231
+
232
+ fn get_type_show(genv: &GlobalEnv, vtx: VertexId) -> String {
233
+ if let Some(vertex) = genv.get_vertex(vtx) {
234
+ vertex.show()
235
+ } else if let Some(source) = genv.get_source(vtx) {
236
+ source.ty.show()
237
+ } else {
238
+ panic!("vertex {:?} not found as either Vertex or Source", vtx);
239
+ }
240
+ }
211
241
 
212
242
  #[test]
213
243
  fn test_install_required_parameter() {
@@ -266,4 +296,134 @@ mod tests {
266
296
  let vertex = genv.get_vertex(vtx).unwrap();
267
297
  assert_eq!(vertex.show(), "Integer");
268
298
  }
299
+
300
+ #[test]
301
+ fn test_required_parameter_type_propagation() {
302
+ let source = r#"
303
+ class Foo
304
+ def greet(name)
305
+ name
306
+ end
307
+ end
308
+
309
+ Foo.new.greet("Alice")
310
+ "#;
311
+ let genv = analyze(source);
312
+ let info = genv.resolve_method(&Type::instance("Foo"), "greet").unwrap();
313
+ let ret_vtx = info.return_vertex.unwrap();
314
+ assert_eq!(get_type_show(&genv, ret_vtx), "String");
315
+ }
316
+
317
+ #[test]
318
+ fn test_optional_parameter_default_type() {
319
+ let source = r#"
320
+ class Foo
321
+ def greet(name = "World")
322
+ name
323
+ end
324
+ end
325
+ "#;
326
+ let genv = analyze(source);
327
+ let info = genv.resolve_method(&Type::instance("Foo"), "greet").unwrap();
328
+ let ret_vtx = info.return_vertex.unwrap();
329
+ assert_eq!(get_type_show(&genv, ret_vtx), "String");
330
+ }
331
+
332
+ #[test]
333
+ fn test_multiple_parameters_from_call_site() {
334
+ let source = r#"
335
+ class Calc
336
+ def add(x, y)
337
+ x
338
+ end
339
+ end
340
+
341
+ Calc.new.add(1, 2)
342
+ "#;
343
+ let genv = analyze(source);
344
+ let info = genv.resolve_method(&Type::instance("Calc"), "add").unwrap();
345
+ let param_vtxs = info.param_vertices.as_ref().unwrap();
346
+ assert_eq!(param_vtxs.len(), 2);
347
+ // Verify return type is Integer (method returns x, which receives 1)
348
+ let ret_vtx = info.return_vertex.unwrap();
349
+ assert_eq!(get_type_show(&genv, ret_vtx), "Integer");
350
+ }
351
+
352
+ #[test]
353
+ fn test_keyword_parameter_propagation() {
354
+ let source = r#"
355
+ class Foo
356
+ def greet(name:)
357
+ name
358
+ end
359
+ end
360
+
361
+ Foo.new.greet(name: "Alice")
362
+ "#;
363
+ let genv = analyze(source);
364
+ let info = genv.resolve_method(&Type::instance("Foo"), "greet").unwrap();
365
+ let ret_vtx = info.return_vertex.unwrap();
366
+ assert_eq!(get_type_show(&genv, ret_vtx), "String");
367
+ }
368
+
369
+ #[test]
370
+ fn test_optional_keyword_parameter_default() {
371
+ let source = r#"
372
+ class Counter
373
+ def count(step: 1)
374
+ step
375
+ end
376
+ end
377
+ "#;
378
+ let genv = analyze(source);
379
+ let info = genv.resolve_method(&Type::instance("Counter"), "count").unwrap();
380
+ let ret_vtx = info.return_vertex.unwrap();
381
+ assert_eq!(get_type_show(&genv, ret_vtx), "Integer");
382
+ }
383
+
384
+ #[test]
385
+ fn test_mixed_positional_and_keyword_params() {
386
+ let source = r#"
387
+ class User
388
+ def initialize(id, name:)
389
+ @id = id
390
+ @name = name
391
+ end
392
+ end
393
+
394
+ User.new(1, name: "Alice")
395
+ "#;
396
+ let genv = analyze(source);
397
+ assert!(genv.type_errors.is_empty());
398
+ }
399
+
400
+ #[test]
401
+ fn test_rest_parameter() {
402
+ let source = r#"
403
+ class Foo
404
+ def bar(*args)
405
+ args
406
+ end
407
+ end
408
+ "#;
409
+ let genv = analyze(source);
410
+ let info = genv.resolve_method(&Type::instance("Foo"), "bar").unwrap();
411
+ let ret_vtx = info.return_vertex.unwrap();
412
+ assert_eq!(get_type_show(&genv, ret_vtx), "Array");
413
+ }
414
+
415
+ #[test]
416
+ fn test_no_parameters() {
417
+ let source = r#"
418
+ class Foo
419
+ def bar
420
+ "hello"
421
+ end
422
+ end
423
+ "#;
424
+ let genv = analyze(source);
425
+ let info = genv.resolve_method(&Type::instance("Foo"), "bar").unwrap();
426
+ let param_vtxs = info.param_vertices.as_ref().unwrap();
427
+ assert!(param_vtxs.is_empty());
428
+ }
269
429
  }
@@ -0,0 +1,285 @@
1
+ //! Super call handling: `super` and `super(args)`
2
+ //!
3
+ //! Ruby's `super` calls the same-named method on the parent class.
4
+ //! - `super(args)` → SuperNode: explicit arguments
5
+ //! - `super` (bare) → ForwardingSuperNode: implicit argument forwarding
6
+ //!
7
+ //! Note: ForwardingSuperNode (bare `super`) is treated as a zero-argument
8
+ //! call. In Ruby, bare `super` forwards all arguments from the enclosing
9
+ //! method, but replicating this requires parameter-vertex forwarding that
10
+ //! is not yet implemented. Return type inference is unaffected.
11
+
12
+ use ruby_prism::{ForwardingSuperNode, SuperNode};
13
+
14
+ use crate::env::{GlobalEnv, LocalEnv};
15
+ use crate::graph::{ChangeSet, VertexId};
16
+ use crate::source_map::SourceLocation as SL;
17
+ use crate::types::Type;
18
+
19
+ /// Process SuperNode: `super(args)` — explicit arguments
20
+ pub(crate) fn process_super_node(
21
+ genv: &mut GlobalEnv,
22
+ lenv: &mut LocalEnv,
23
+ changes: &mut ChangeSet,
24
+ source: &str,
25
+ super_node: &SuperNode,
26
+ ) -> Option<VertexId> {
27
+ let location = SL::from_prism_location_with_source(&super_node.location(), source);
28
+ process_super_call(genv, lenv, changes, source, super_node.arguments(), location)
29
+ }
30
+
31
+ /// Process ForwardingSuperNode: `super` — implicit argument forwarding
32
+ pub(crate) fn process_forwarding_super_node(
33
+ genv: &mut GlobalEnv,
34
+ lenv: &mut LocalEnv,
35
+ changes: &mut ChangeSet,
36
+ source: &str,
37
+ node: &ForwardingSuperNode,
38
+ ) -> Option<VertexId> {
39
+ let location = SL::from_prism_location_with_source(&node.location(), source);
40
+ process_super_call(genv, lenv, changes, source, None, location)
41
+ }
42
+
43
+ /// Resolve a super call by looking up the same-named method on the superclass.
44
+ ///
45
+ /// Returns `None` if there is no enclosing method scope (super outside a method)
46
+ /// or no explicit superclass declared on the enclosing class.
47
+ fn process_super_call(
48
+ genv: &mut GlobalEnv,
49
+ lenv: &mut LocalEnv,
50
+ changes: &mut ChangeSet,
51
+ source: &str,
52
+ arguments: Option<ruby_prism::ArgumentsNode>,
53
+ location: SL,
54
+ ) -> Option<VertexId> {
55
+ let method_name = genv.scope_manager.current_method_name()?;
56
+ let superclass_name = genv.scope_manager.current_superclass()?;
57
+ let recv_vtx = genv.new_source(Type::instance(&superclass_name));
58
+
59
+ let (arg_vtxs, kw) = if let Some(args) = arguments {
60
+ super::dispatch::collect_arguments(genv, lenv, changes, source, args.arguments().iter())
61
+ } else {
62
+ (vec![], None)
63
+ };
64
+
65
+ Some(super::calls::install_method_call(
66
+ genv,
67
+ recv_vtx,
68
+ method_name,
69
+ arg_vtxs,
70
+ kw,
71
+ Some(location),
72
+ ))
73
+ }
74
+
75
+ #[cfg(test)]
76
+ mod tests {
77
+ use crate::analyzer::install::AstInstaller;
78
+ use crate::env::{GlobalEnv, LocalEnv};
79
+ use crate::graph::VertexId;
80
+ use crate::parser::ParseSession;
81
+ use crate::types::Type;
82
+
83
+ /// Helper: parse Ruby source, process with AstInstaller, and return GlobalEnv
84
+ fn analyze(source: &str) -> GlobalEnv {
85
+ let session = ParseSession::new();
86
+ let parse_result = session.parse_source(source, "test.rb").unwrap();
87
+ let root = parse_result.node();
88
+ let program = root.as_program_node().unwrap();
89
+
90
+ let mut genv = GlobalEnv::new();
91
+ let mut lenv = LocalEnv::new();
92
+
93
+ let mut installer = AstInstaller::new(&mut genv, &mut lenv, source);
94
+ for stmt in &program.statements().body() {
95
+ installer.install_node(&stmt);
96
+ }
97
+ installer.finish();
98
+
99
+ genv
100
+ }
101
+
102
+ /// Helper: get the type string for a vertex ID
103
+ fn get_type_show(genv: &GlobalEnv, vtx: VertexId) -> String {
104
+ if let Some(vertex) = genv.get_vertex(vtx) {
105
+ vertex.show()
106
+ } else if let Some(source) = genv.get_source(vtx) {
107
+ source.ty.show()
108
+ } else {
109
+ panic!("vertex {:?} not found as either Vertex or Source", vtx);
110
+ }
111
+ }
112
+
113
+ #[test]
114
+ fn test_super_basic() {
115
+ let source = r#"
116
+ class Animal
117
+ def speak
118
+ "..."
119
+ end
120
+ end
121
+
122
+ class Dog < Animal
123
+ def speak
124
+ super
125
+ end
126
+ end
127
+ "#;
128
+ let genv = analyze(source);
129
+ let info = genv
130
+ .resolve_method(&Type::instance("Dog"), "speak")
131
+ .expect("Dog#speak should be registered");
132
+ let ret_vtx = info.return_vertex.unwrap();
133
+ assert_eq!(get_type_show(&genv, ret_vtx), "String");
134
+ }
135
+
136
+ #[test]
137
+ fn test_super_with_method_chain() {
138
+ let source = r#"
139
+ class Animal
140
+ def speak
141
+ "hello"
142
+ end
143
+ end
144
+
145
+ class Dog < Animal
146
+ def speak
147
+ super.upcase
148
+ end
149
+ end
150
+ "#;
151
+ let genv = analyze(source);
152
+ let info = genv
153
+ .resolve_method(&Type::instance("Dog"), "speak")
154
+ .expect("Dog#speak should be registered");
155
+ assert!(info.return_vertex.is_some());
156
+ }
157
+
158
+ #[test]
159
+ fn test_super_with_arguments() {
160
+ let source = r#"
161
+ class Base
162
+ def greet(name)
163
+ name
164
+ end
165
+ end
166
+
167
+ class Child < Base
168
+ def greet(name)
169
+ super(name)
170
+ end
171
+ end
172
+
173
+ Child.new.greet("Alice")
174
+ "#;
175
+ let genv = analyze(source);
176
+ let info = genv
177
+ .resolve_method(&Type::instance("Child"), "greet")
178
+ .expect("Child#greet should be registered");
179
+ let ret_vtx = info.return_vertex.unwrap();
180
+ assert_eq!(get_type_show(&genv, ret_vtx), "String");
181
+ }
182
+
183
+ #[test]
184
+ fn test_super_outside_method_ignored() {
185
+ let source = r#"
186
+ class Foo < Object
187
+ super
188
+ end
189
+ "#;
190
+ analyze(source);
191
+ }
192
+
193
+ #[test]
194
+ fn test_super_explicit_empty_args() {
195
+ let source = r#"
196
+ class Animal
197
+ def speak
198
+ "hello"
199
+ end
200
+ end
201
+
202
+ class Dog < Animal
203
+ def speak
204
+ super()
205
+ end
206
+ end
207
+ "#;
208
+ let genv = analyze(source);
209
+ let info = genv
210
+ .resolve_method(&Type::instance("Dog"), "speak")
211
+ .expect("Dog#speak should be registered");
212
+ let ret_vtx = info.return_vertex.unwrap();
213
+ assert_eq!(get_type_show(&genv, ret_vtx), "String");
214
+ }
215
+
216
+ #[test]
217
+ fn test_super_without_superclass_ignored() {
218
+ let source = r#"
219
+ class Foo
220
+ def bar
221
+ super
222
+ end
223
+ end
224
+ "#;
225
+ let genv = analyze(source);
226
+ let info = genv
227
+ .resolve_method(&Type::instance("Foo"), "bar")
228
+ .expect("Foo#bar should be registered");
229
+ assert!(info.return_vertex.is_some());
230
+ }
231
+
232
+ #[test]
233
+ fn test_super_qualified_superclass() {
234
+ let source = r#"
235
+ module Animals
236
+ class Pet
237
+ def name
238
+ "pet"
239
+ end
240
+ end
241
+ end
242
+
243
+ class Dog < Animals::Pet
244
+ def name
245
+ super
246
+ end
247
+ end
248
+ "#;
249
+ let genv = analyze(source);
250
+ let info = genv
251
+ .resolve_method(&Type::instance("Dog"), "name")
252
+ .expect("Dog#name should be registered");
253
+ let ret_vtx = info.return_vertex.unwrap();
254
+ assert_eq!(get_type_show(&genv, ret_vtx), "String");
255
+ }
256
+
257
+ #[test]
258
+ fn test_super_multi_level_inheritance() {
259
+ let source = r#"
260
+ class A
261
+ def foo
262
+ "hello"
263
+ end
264
+ end
265
+
266
+ class B < A
267
+ def foo
268
+ super
269
+ end
270
+ end
271
+
272
+ class C < B
273
+ def foo
274
+ super
275
+ end
276
+ end
277
+ "#;
278
+ let genv = analyze(source);
279
+ let info = genv
280
+ .resolve_method(&Type::instance("C"), "foo")
281
+ .expect("C#foo should be registered");
282
+ let ret_vtx = info.return_vertex.unwrap();
283
+ assert_eq!(get_type_show(&genv, ret_vtx), "String");
284
+ }
285
+ }