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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/{rust → core}/Cargo.toml +1 -1
- data/core/src/analyzer/assignments.rs +499 -0
- data/{rust → core}/src/analyzer/blocks.rs +140 -0
- data/{rust → core}/src/analyzer/definitions.rs +6 -5
- data/{rust → core}/src/analyzer/dispatch.rs +295 -31
- data/{rust → core}/src/analyzer/exceptions.rs +104 -3
- data/{rust → core}/src/analyzer/install.rs +16 -1
- data/{rust → core}/src/analyzer/literals.rs +3 -17
- data/{rust → core}/src/analyzer/loops.rs +126 -1
- data/{rust → core}/src/analyzer/mod.rs +1 -0
- data/{rust → core}/src/analyzer/parameters.rs +160 -0
- data/core/src/analyzer/super_calls.rs +285 -0
- data/{rust → core}/src/env/global_env.rs +18 -4
- data/{rust → core}/src/env/method_registry.rs +109 -23
- data/{rust → core}/src/env/scope.rs +78 -0
- data/{rust → core}/src/types.rs +11 -0
- data/ext/Cargo.toml +2 -2
- data/lib/methodray/binary_locator.rb +2 -2
- data/lib/methodray/commands.rb +1 -1
- data/lib/methodray/version.rb +1 -1
- metadata +54 -53
- data/rust/src/analyzer/assignments.rs +0 -152
- /data/{rust → core}/src/analyzer/attributes.rs +0 -0
- /data/{rust → core}/src/analyzer/calls.rs +0 -0
- /data/{rust → core}/src/analyzer/conditionals.rs +0 -0
- /data/{rust → core}/src/analyzer/operators.rs +0 -0
- /data/{rust → core}/src/analyzer/parentheses.rs +0 -0
- /data/{rust → core}/src/analyzer/returns.rs +0 -0
- /data/{rust → core}/src/analyzer/variables.rs +0 -0
- /data/{rust → core}/src/cache/mod.rs +0 -0
- /data/{rust → core}/src/cache/rbs_cache.rs +0 -0
- /data/{rust → core}/src/checker.rs +0 -0
- /data/{rust → core}/src/cli/args.rs +0 -0
- /data/{rust → core}/src/cli/commands.rs +0 -0
- /data/{rust → core}/src/cli/mod.rs +0 -0
- /data/{rust → core}/src/diagnostics/diagnostic.rs +0 -0
- /data/{rust → core}/src/diagnostics/formatter.rs +0 -0
- /data/{rust → core}/src/diagnostics/mod.rs +0 -0
- /data/{rust → core}/src/env/box_manager.rs +0 -0
- /data/{rust → core}/src/env/local_env.rs +0 -0
- /data/{rust → core}/src/env/mod.rs +0 -0
- /data/{rust → core}/src/env/type_error.rs +0 -0
- /data/{rust → core}/src/env/vertex_manager.rs +0 -0
- /data/{rust → core}/src/graph/box.rs +0 -0
- /data/{rust → core}/src/graph/change_set.rs +0 -0
- /data/{rust → core}/src/graph/mod.rs +0 -0
- /data/{rust → core}/src/graph/vertex.rs +0 -0
- /data/{rust → core}/src/lib.rs +0 -0
- /data/{rust → core}/src/lsp/diagnostics.rs +0 -0
- /data/{rust → core}/src/lsp/main.rs +0 -0
- /data/{rust → core}/src/lsp/mod.rs +0 -0
- /data/{rust → core}/src/lsp/server.rs +0 -0
- /data/{rust → core}/src/main.rs +0 -0
- /data/{rust → core}/src/parser.rs +0 -0
- /data/{rust → core}/src/rbs/converter.rs +0 -0
- /data/{rust → core}/src/rbs/error.rs +0 -0
- /data/{rust → core}/src/rbs/loader.rs +0 -0
- /data/{rust → core}/src/rbs/mod.rs +0 -0
- /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
|
-
|
|
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 =
|
|
180
|
-
|
|
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
|
}
|
|
@@ -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
|
+
}
|