method-ray 0.1.2 → 0.1.4
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 +41 -0
- data/README.md +27 -1
- data/ext/Cargo.toml +1 -1
- data/ext/src/lib.rs +7 -6
- data/lib/methodray/binary_locator.rb +29 -0
- data/lib/methodray/commands.rb +3 -20
- data/lib/methodray/version.rb +1 -1
- data/lib/methodray.rb +1 -1
- data/rust/Cargo.toml +3 -1
- data/rust/src/analyzer/attributes.rs +57 -0
- data/rust/src/analyzer/blocks.rs +175 -0
- data/rust/src/analyzer/calls.rs +7 -4
- data/rust/src/analyzer/conditionals.rs +466 -0
- data/rust/src/analyzer/definitions.rs +280 -13
- data/rust/src/analyzer/dispatch.rs +754 -11
- data/rust/src/analyzer/install.rs +58 -176
- data/rust/src/analyzer/literals.rs +201 -37
- data/rust/src/analyzer/mod.rs +4 -3
- data/rust/src/analyzer/parameters.rs +218 -0
- data/rust/src/analyzer/variables.rs +16 -8
- data/rust/src/cache/rbs_cache.rs +11 -4
- data/rust/src/checker.rs +20 -8
- data/rust/src/env/global_env.rs +42 -2
- data/rust/src/env/method_registry.rs +86 -4
- data/rust/src/env/mod.rs +1 -0
- data/rust/src/env/scope.rs +291 -25
- data/rust/src/graph/box.rs +478 -4
- data/rust/src/graph/change_set.rs +14 -0
- data/rust/src/graph/mod.rs +1 -1
- data/rust/src/lib.rs +2 -1
- data/rust/src/parser.rs +99 -39
- data/rust/src/rbs/converter.rs +16 -11
- data/rust/src/rbs/loader.rs +35 -5
- data/rust/src/rbs/mod.rs +4 -3
- data/rust/src/types.rs +344 -9
- metadata +6 -3
- data/rust/src/analyzer/tests/integration_test.rs +0 -136
- data/rust/src/analyzer/tests/mod.rs +0 -1
|
@@ -4,17 +4,25 @@
|
|
|
4
4
|
//! and dispatches them to specialized handlers.
|
|
5
5
|
|
|
6
6
|
use crate::env::{GlobalEnv, LocalEnv};
|
|
7
|
-
use crate::graph::{ChangeSet, VertexId};
|
|
7
|
+
use crate::graph::{BlockParameterTypeBox, ChangeSet, VertexId};
|
|
8
8
|
use crate::source_map::SourceLocation;
|
|
9
|
+
use crate::types::Type;
|
|
9
10
|
use ruby_prism::Node;
|
|
10
11
|
|
|
11
12
|
use super::calls::install_method_call;
|
|
12
|
-
use super::literals::install_literal;
|
|
13
13
|
use super::variables::{
|
|
14
14
|
install_ivar_read, install_ivar_write, install_local_var_read, install_local_var_write,
|
|
15
15
|
install_self,
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
+
/// Kind of attr_* declaration
|
|
19
|
+
#[derive(Debug, Clone, Copy)]
|
|
20
|
+
pub enum AttrKind {
|
|
21
|
+
Reader,
|
|
22
|
+
Writer,
|
|
23
|
+
Accessor,
|
|
24
|
+
}
|
|
25
|
+
|
|
18
26
|
/// Result of dispatching a simple node (no child processing needed)
|
|
19
27
|
pub enum DispatchResult {
|
|
20
28
|
/// Node produced a vertex
|
|
@@ -34,10 +42,29 @@ pub enum NeedsChildKind<'a> {
|
|
|
34
42
|
receiver: Node<'a>,
|
|
35
43
|
method_name: String,
|
|
36
44
|
location: SourceLocation,
|
|
45
|
+
/// Optional block attached to the method call
|
|
46
|
+
block: Option<Node<'a>>,
|
|
47
|
+
/// Arguments to the method call
|
|
48
|
+
arguments: Vec<Node<'a>>,
|
|
49
|
+
},
|
|
50
|
+
/// Implicit self method call: method call without explicit receiver (implicit self)
|
|
51
|
+
ImplicitSelfCall {
|
|
52
|
+
method_name: String,
|
|
53
|
+
location: SourceLocation,
|
|
54
|
+
block: Option<Node<'a>>,
|
|
55
|
+
arguments: Vec<Node<'a>>,
|
|
56
|
+
},
|
|
57
|
+
/// attr_reader / attr_writer / attr_accessor declaration
|
|
58
|
+
AttrDeclaration {
|
|
59
|
+
kind: AttrKind,
|
|
60
|
+
attr_names: Vec<String>,
|
|
37
61
|
},
|
|
38
62
|
}
|
|
39
63
|
|
|
40
64
|
/// First pass: check if node can be handled immediately without child processing
|
|
65
|
+
///
|
|
66
|
+
/// Note: Literals (including Array) are handled in install.rs via install_literal
|
|
67
|
+
/// because Array literals need child processing for element type inference.
|
|
41
68
|
pub fn dispatch_simple(genv: &mut GlobalEnv, lenv: &mut LocalEnv, node: &Node) -> DispatchResult {
|
|
42
69
|
// Instance variable read: @name
|
|
43
70
|
if let Some(ivar_read) = node.as_instance_variable_read_node() {
|
|
@@ -62,14 +89,41 @@ pub fn dispatch_simple(genv: &mut GlobalEnv, lenv: &mut LocalEnv, node: &Node) -
|
|
|
62
89
|
};
|
|
63
90
|
}
|
|
64
91
|
|
|
65
|
-
//
|
|
66
|
-
if let Some(
|
|
92
|
+
// ConstantReadNode: User → Type::Singleton("User")
|
|
93
|
+
if let Some(const_read) = node.as_constant_read_node() {
|
|
94
|
+
let name = String::from_utf8_lossy(const_read.name().as_slice()).to_string();
|
|
95
|
+
let vtx = genv.new_source(Type::singleton(&name));
|
|
67
96
|
return DispatchResult::Vertex(vtx);
|
|
68
97
|
}
|
|
69
98
|
|
|
99
|
+
// ConstantPathNode: Api::User → Type::Singleton("Api::User")
|
|
100
|
+
if node.as_constant_path_node().is_some() {
|
|
101
|
+
if let Some(name) = super::definitions::extract_constant_path(node) {
|
|
102
|
+
let vtx = genv.new_source(Type::singleton(&name));
|
|
103
|
+
return DispatchResult::Vertex(vtx);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
70
107
|
DispatchResult::NotHandled
|
|
71
108
|
}
|
|
72
109
|
|
|
110
|
+
/// Extract symbol names from attr_* arguments (e.g., `attr_reader :name, :email`)
|
|
111
|
+
fn extract_symbol_names(call_node: &ruby_prism::CallNode) -> Vec<String> {
|
|
112
|
+
call_node
|
|
113
|
+
.arguments()
|
|
114
|
+
.map(|args| {
|
|
115
|
+
args.arguments()
|
|
116
|
+
.iter()
|
|
117
|
+
.filter_map(|arg| {
|
|
118
|
+
arg.as_symbol_node().map(|sym| {
|
|
119
|
+
String::from_utf8_lossy(&sym.unescaped()).to_string()
|
|
120
|
+
})
|
|
121
|
+
})
|
|
122
|
+
.collect()
|
|
123
|
+
})
|
|
124
|
+
.unwrap_or_default()
|
|
125
|
+
}
|
|
126
|
+
|
|
73
127
|
/// Check if node needs child processing
|
|
74
128
|
pub fn dispatch_needs_child<'a>(node: &Node<'a>, source: &str) -> Option<NeedsChildKind<'a>> {
|
|
75
129
|
// Instance variable write: @name = value
|
|
@@ -90,16 +144,57 @@ pub fn dispatch_needs_child<'a>(node: &Node<'a>, source: &str) -> Option<NeedsCh
|
|
|
90
144
|
});
|
|
91
145
|
}
|
|
92
146
|
|
|
93
|
-
// Method call: x.upcase
|
|
147
|
+
// Method call: x.upcase, x.each { |i| ... }, or name (implicit self)
|
|
94
148
|
if let Some(call_node) = node.as_call_node() {
|
|
149
|
+
let method_name = String::from_utf8_lossy(call_node.name().as_slice()).to_string();
|
|
150
|
+
let block = call_node.block();
|
|
151
|
+
let arguments: Vec<Node<'a>> = call_node
|
|
152
|
+
.arguments()
|
|
153
|
+
.map(|args| args.arguments().iter().collect())
|
|
154
|
+
.unwrap_or_default();
|
|
155
|
+
|
|
95
156
|
if let Some(receiver) = call_node.receiver() {
|
|
96
|
-
|
|
157
|
+
// Explicit receiver: x.upcase, x.each { |i| ... }
|
|
158
|
+
let prism_location = call_node
|
|
159
|
+
.call_operator_loc()
|
|
160
|
+
.unwrap_or_else(|| node.location());
|
|
97
161
|
let location =
|
|
98
|
-
SourceLocation::from_prism_location_with_source(&
|
|
162
|
+
SourceLocation::from_prism_location_with_source(&prism_location, source);
|
|
163
|
+
|
|
99
164
|
return Some(NeedsChildKind::MethodCall {
|
|
100
165
|
receiver,
|
|
101
166
|
method_name,
|
|
102
167
|
location,
|
|
168
|
+
block,
|
|
169
|
+
arguments,
|
|
170
|
+
});
|
|
171
|
+
} else {
|
|
172
|
+
// No receiver: implicit self method call (e.g., `name`, `puts "hello"`)
|
|
173
|
+
|
|
174
|
+
if let Some(kind) = match method_name.as_str() {
|
|
175
|
+
"attr_reader" => Some(AttrKind::Reader),
|
|
176
|
+
"attr_writer" => Some(AttrKind::Writer),
|
|
177
|
+
"attr_accessor" => Some(AttrKind::Accessor),
|
|
178
|
+
_ => None,
|
|
179
|
+
} {
|
|
180
|
+
let attr_names = extract_symbol_names(&call_node);
|
|
181
|
+
if !attr_names.is_empty() {
|
|
182
|
+
return Some(NeedsChildKind::AttrDeclaration { kind, attr_names });
|
|
183
|
+
}
|
|
184
|
+
return None;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
let prism_location = call_node
|
|
188
|
+
.message_loc()
|
|
189
|
+
.unwrap_or_else(|| node.location());
|
|
190
|
+
let location =
|
|
191
|
+
SourceLocation::from_prism_location_with_source(&prism_location, source);
|
|
192
|
+
|
|
193
|
+
return Some(NeedsChildKind::ImplicitSelfCall {
|
|
194
|
+
method_name,
|
|
195
|
+
location,
|
|
196
|
+
block,
|
|
197
|
+
arguments,
|
|
103
198
|
});
|
|
104
199
|
}
|
|
105
200
|
}
|
|
@@ -107,13 +202,73 @@ pub fn dispatch_needs_child<'a>(node: &Node<'a>, source: &str) -> Option<NeedsCh
|
|
|
107
202
|
None
|
|
108
203
|
}
|
|
109
204
|
|
|
205
|
+
/// Process a node that needs child processing
|
|
206
|
+
///
|
|
207
|
+
/// This function handles the second phase of two-phase dispatch:
|
|
208
|
+
/// 1. `dispatch_needs_child` identifies the node kind and extracts data
|
|
209
|
+
/// 2. `process_needs_child` processes child nodes and completes the operation
|
|
210
|
+
pub(crate) fn process_needs_child(
|
|
211
|
+
genv: &mut GlobalEnv,
|
|
212
|
+
lenv: &mut LocalEnv,
|
|
213
|
+
changes: &mut ChangeSet,
|
|
214
|
+
source: &str,
|
|
215
|
+
kind: NeedsChildKind,
|
|
216
|
+
) -> Option<VertexId> {
|
|
217
|
+
match kind {
|
|
218
|
+
NeedsChildKind::IvarWrite { ivar_name, value } => {
|
|
219
|
+
let value_vtx = super::install::install_node(genv, lenv, changes, source, &value)?;
|
|
220
|
+
Some(finish_ivar_write(genv, ivar_name, value_vtx))
|
|
221
|
+
}
|
|
222
|
+
NeedsChildKind::LocalVarWrite { var_name, value } => {
|
|
223
|
+
let value_vtx = super::install::install_node(genv, lenv, changes, source, &value)?;
|
|
224
|
+
Some(finish_local_var_write(genv, lenv, changes, var_name, value_vtx))
|
|
225
|
+
}
|
|
226
|
+
NeedsChildKind::MethodCall {
|
|
227
|
+
receiver,
|
|
228
|
+
method_name,
|
|
229
|
+
location,
|
|
230
|
+
block,
|
|
231
|
+
arguments,
|
|
232
|
+
} => {
|
|
233
|
+
let recv_vtx = super::install::install_node(genv, lenv, changes, source, &receiver)?;
|
|
234
|
+
process_method_call_common(
|
|
235
|
+
genv, lenv, changes, source, recv_vtx, method_name, location, block, arguments,
|
|
236
|
+
)
|
|
237
|
+
}
|
|
238
|
+
NeedsChildKind::ImplicitSelfCall {
|
|
239
|
+
method_name,
|
|
240
|
+
location,
|
|
241
|
+
block,
|
|
242
|
+
arguments,
|
|
243
|
+
} => {
|
|
244
|
+
// Use the same naming as method registration in definitions.rs
|
|
245
|
+
let recv_type_name = genv
|
|
246
|
+
.scope_manager
|
|
247
|
+
.current_class_name()
|
|
248
|
+
.or_else(|| genv.scope_manager.current_module_name());
|
|
249
|
+
let recv_vtx = if let Some(name) = recv_type_name {
|
|
250
|
+
genv.new_source(Type::instance(&name))
|
|
251
|
+
} else {
|
|
252
|
+
genv.new_source(Type::instance("Object"))
|
|
253
|
+
};
|
|
254
|
+
process_method_call_common(
|
|
255
|
+
genv, lenv, changes, source, recv_vtx, method_name, location, block, arguments,
|
|
256
|
+
)
|
|
257
|
+
}
|
|
258
|
+
NeedsChildKind::AttrDeclaration { kind, attr_names } => {
|
|
259
|
+
super::attributes::process_attr_declaration(genv, kind, attr_names);
|
|
260
|
+
None
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
110
265
|
/// Finish instance variable write after child is processed
|
|
111
|
-
|
|
266
|
+
fn finish_ivar_write(genv: &mut GlobalEnv, ivar_name: String, value_vtx: VertexId) -> VertexId {
|
|
112
267
|
install_ivar_write(genv, ivar_name, value_vtx)
|
|
113
268
|
}
|
|
114
269
|
|
|
115
270
|
/// Finish local variable write after child is processed
|
|
116
|
-
|
|
271
|
+
fn finish_local_var_write(
|
|
117
272
|
genv: &mut GlobalEnv,
|
|
118
273
|
lenv: &mut LocalEnv,
|
|
119
274
|
changes: &mut ChangeSet,
|
|
@@ -123,12 +278,600 @@ pub fn finish_local_var_write(
|
|
|
123
278
|
install_local_var_write(genv, lenv, changes, var_name, value_vtx)
|
|
124
279
|
}
|
|
125
280
|
|
|
281
|
+
/// MethodCall / ImplicitSelfCall common processing:
|
|
282
|
+
/// Handles argument processing, block processing, and MethodCallBox creation after recv_vtx is obtained
|
|
283
|
+
fn process_method_call_common<'a>(
|
|
284
|
+
genv: &mut GlobalEnv,
|
|
285
|
+
lenv: &mut LocalEnv,
|
|
286
|
+
changes: &mut ChangeSet,
|
|
287
|
+
source: &str,
|
|
288
|
+
recv_vtx: VertexId,
|
|
289
|
+
method_name: String,
|
|
290
|
+
location: SourceLocation,
|
|
291
|
+
block: Option<Node<'a>>,
|
|
292
|
+
arguments: Vec<Node<'a>>,
|
|
293
|
+
) -> Option<VertexId> {
|
|
294
|
+
let arg_vtxs: Vec<VertexId> = arguments
|
|
295
|
+
.iter()
|
|
296
|
+
.filter_map(|arg| super::install::install_node(genv, lenv, changes, source, arg))
|
|
297
|
+
.collect();
|
|
298
|
+
|
|
299
|
+
if let Some(block_node) = block {
|
|
300
|
+
if let Some(block) = block_node.as_block_node() {
|
|
301
|
+
let param_vtxs = super::blocks::process_block_node_with_params(
|
|
302
|
+
genv, lenv, changes, source, &block,
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
if !param_vtxs.is_empty() {
|
|
306
|
+
let box_id = genv.alloc_box_id();
|
|
307
|
+
let block_box = BlockParameterTypeBox::new(
|
|
308
|
+
box_id,
|
|
309
|
+
recv_vtx,
|
|
310
|
+
method_name.clone(),
|
|
311
|
+
param_vtxs,
|
|
312
|
+
);
|
|
313
|
+
genv.register_box(box_id, Box::new(block_box));
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
Some(finish_method_call(
|
|
319
|
+
genv, recv_vtx, method_name, arg_vtxs, location,
|
|
320
|
+
))
|
|
321
|
+
}
|
|
322
|
+
|
|
126
323
|
/// Finish method call after receiver is processed
|
|
127
|
-
|
|
324
|
+
fn finish_method_call(
|
|
128
325
|
genv: &mut GlobalEnv,
|
|
129
326
|
recv_vtx: VertexId,
|
|
130
327
|
method_name: String,
|
|
328
|
+
arg_vtxs: Vec<VertexId>,
|
|
131
329
|
location: SourceLocation,
|
|
132
330
|
) -> VertexId {
|
|
133
|
-
install_method_call(genv, recv_vtx, method_name, Some(location))
|
|
331
|
+
install_method_call(genv, recv_vtx, method_name, arg_vtxs, Some(location))
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
#[cfg(test)]
|
|
335
|
+
mod tests {
|
|
336
|
+
use super::*;
|
|
337
|
+
use crate::analyzer::install::AstInstaller;
|
|
338
|
+
use crate::parser::ParseSession;
|
|
339
|
+
|
|
340
|
+
/// Helper: parse Ruby source, process with AstInstaller, and return GlobalEnv
|
|
341
|
+
fn analyze(source: &str) -> GlobalEnv {
|
|
342
|
+
let session = ParseSession::new();
|
|
343
|
+
let parse_result = session.parse_source(source, "test.rb").unwrap();
|
|
344
|
+
let root = parse_result.node();
|
|
345
|
+
let program = root.as_program_node().unwrap();
|
|
346
|
+
|
|
347
|
+
let mut genv = GlobalEnv::new();
|
|
348
|
+
let mut lenv = LocalEnv::new();
|
|
349
|
+
|
|
350
|
+
let mut installer = AstInstaller::new(&mut genv, &mut lenv, source);
|
|
351
|
+
for stmt in &program.statements().body() {
|
|
352
|
+
installer.install_node(&stmt);
|
|
353
|
+
}
|
|
354
|
+
installer.finish();
|
|
355
|
+
|
|
356
|
+
genv
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/// Helper: get the type string for a vertex ID (checks both Vertex and Source)
|
|
360
|
+
fn get_type_show(genv: &GlobalEnv, vtx: VertexId) -> String {
|
|
361
|
+
if let Some(vertex) = genv.get_vertex(vtx) {
|
|
362
|
+
vertex.show()
|
|
363
|
+
} else if let Some(source) = genv.get_source(vtx) {
|
|
364
|
+
source.ty.show()
|
|
365
|
+
} else {
|
|
366
|
+
panic!("vertex {:?} not found as either Vertex or Source", vtx);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Test 1: Receiverless method call type resolution
|
|
371
|
+
#[test]
|
|
372
|
+
fn test_implicit_self_call_type_resolution() {
|
|
373
|
+
let source = r#"
|
|
374
|
+
class User
|
|
375
|
+
def name
|
|
376
|
+
"Alice"
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
def greet
|
|
380
|
+
name
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
"#;
|
|
384
|
+
let genv = analyze(source);
|
|
385
|
+
|
|
386
|
+
// User#greet should resolve to String via User#name
|
|
387
|
+
let info = genv
|
|
388
|
+
.resolve_method(&Type::instance("User"), "greet")
|
|
389
|
+
.expect("User#greet should be registered");
|
|
390
|
+
assert!(info.return_vertex.is_some());
|
|
391
|
+
|
|
392
|
+
let ret_vtx = info.return_vertex.unwrap();
|
|
393
|
+
assert_eq!(get_type_show(&genv, ret_vtx), "String");
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Test 2: Receiverless method call with arguments
|
|
397
|
+
#[test]
|
|
398
|
+
fn test_implicit_self_call_with_arguments() {
|
|
399
|
+
let source = r#"
|
|
400
|
+
class Calculator
|
|
401
|
+
def add(x, y)
|
|
402
|
+
x
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
def compute
|
|
406
|
+
add(1, 2)
|
|
407
|
+
end
|
|
408
|
+
end
|
|
409
|
+
"#;
|
|
410
|
+
let genv = analyze(source);
|
|
411
|
+
|
|
412
|
+
// Calculator#compute should resolve via Calculator#add
|
|
413
|
+
let info = genv
|
|
414
|
+
.resolve_method(&Type::instance("Calculator"), "compute")
|
|
415
|
+
.expect("Calculator#compute should be registered");
|
|
416
|
+
assert!(info.return_vertex.is_some());
|
|
417
|
+
|
|
418
|
+
let ret_vtx = info.return_vertex.unwrap();
|
|
419
|
+
assert_eq!(get_type_show(&genv, ret_vtx), "Integer");
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Test 3: Receiverless call in nested class
|
|
423
|
+
#[test]
|
|
424
|
+
fn test_implicit_self_call_in_nested_class() {
|
|
425
|
+
let source = r#"
|
|
426
|
+
module Api
|
|
427
|
+
module V1
|
|
428
|
+
class User
|
|
429
|
+
def name
|
|
430
|
+
"Alice"
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
def greet
|
|
434
|
+
name
|
|
435
|
+
end
|
|
436
|
+
end
|
|
437
|
+
end
|
|
438
|
+
end
|
|
439
|
+
"#;
|
|
440
|
+
let genv = analyze(source);
|
|
441
|
+
|
|
442
|
+
// Method registered with simple class name "User" (current behavior of definitions.rs)
|
|
443
|
+
let info = genv
|
|
444
|
+
.resolve_method(&Type::instance("User"), "greet")
|
|
445
|
+
.expect("User#greet should be registered");
|
|
446
|
+
assert!(info.return_vertex.is_some());
|
|
447
|
+
|
|
448
|
+
let ret_vtx = info.return_vertex.unwrap();
|
|
449
|
+
assert_eq!(get_type_show(&genv, ret_vtx), "String");
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Test 4: Receiverless call in module
|
|
453
|
+
#[test]
|
|
454
|
+
fn test_implicit_self_call_in_module() {
|
|
455
|
+
let source = r#"
|
|
456
|
+
module Utils
|
|
457
|
+
def self.format(value)
|
|
458
|
+
value
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
def self.run
|
|
462
|
+
format("test")
|
|
463
|
+
end
|
|
464
|
+
end
|
|
465
|
+
"#;
|
|
466
|
+
let genv = analyze(source);
|
|
467
|
+
|
|
468
|
+
// Utils.run should be registered
|
|
469
|
+
let info = genv
|
|
470
|
+
.resolve_method(&Type::instance("Utils"), "run")
|
|
471
|
+
.expect("Utils#run should be registered");
|
|
472
|
+
assert!(info.return_vertex.is_some());
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Test 5: Receiverless call from within block
|
|
476
|
+
#[test]
|
|
477
|
+
fn test_implicit_self_call_from_block() {
|
|
478
|
+
let source = r#"
|
|
479
|
+
class User
|
|
480
|
+
def name
|
|
481
|
+
"Alice"
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
def greet
|
|
485
|
+
[1].each { name }
|
|
486
|
+
end
|
|
487
|
+
end
|
|
488
|
+
"#;
|
|
489
|
+
let genv = analyze(source);
|
|
490
|
+
|
|
491
|
+
// User#name should be registered and resolve to String
|
|
492
|
+
let name_info = genv
|
|
493
|
+
.resolve_method(&Type::instance("User"), "name")
|
|
494
|
+
.expect("User#name should be registered");
|
|
495
|
+
assert!(name_info.return_vertex.is_some());
|
|
496
|
+
assert_eq!(get_type_show(&genv, name_info.return_vertex.unwrap()), "String");
|
|
497
|
+
|
|
498
|
+
// User#greet should also be registered (block contains implicit self call)
|
|
499
|
+
let greet_info = genv
|
|
500
|
+
.resolve_method(&Type::instance("User"), "greet")
|
|
501
|
+
.expect("User#greet should be registered");
|
|
502
|
+
assert!(greet_info.return_vertex.is_some());
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Test 6: Top-level receiverless call (Object receiver)
|
|
506
|
+
#[test]
|
|
507
|
+
fn test_implicit_self_call_at_top_level() {
|
|
508
|
+
let source = r#"
|
|
509
|
+
def helper
|
|
510
|
+
"result"
|
|
511
|
+
end
|
|
512
|
+
|
|
513
|
+
helper
|
|
514
|
+
"#;
|
|
515
|
+
let genv = analyze(source);
|
|
516
|
+
|
|
517
|
+
// Should not panic; top-level call uses Object as receiver type
|
|
518
|
+
// NOTE: top-level def is not registered in method_registry yet,
|
|
519
|
+
// so this will produce a type error (false positive).
|
|
520
|
+
// The important thing is that it doesn't panic.
|
|
521
|
+
// No panic is the real assertion - top-level call should be processed without error
|
|
522
|
+
let _ = genv;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Test 7: attr_reader basic — getter type resolution
|
|
526
|
+
#[test]
|
|
527
|
+
fn test_attr_reader_basic() {
|
|
528
|
+
let source = r#"
|
|
529
|
+
class User
|
|
530
|
+
attr_reader :name
|
|
531
|
+
|
|
532
|
+
def initialize
|
|
533
|
+
@name = "Alice"
|
|
534
|
+
end
|
|
535
|
+
end
|
|
536
|
+
"#;
|
|
537
|
+
let genv = analyze(source);
|
|
538
|
+
|
|
539
|
+
// User#name should be registered and resolve to String via @name
|
|
540
|
+
let info = genv
|
|
541
|
+
.resolve_method(&Type::instance("User"), "name")
|
|
542
|
+
.expect("User#name should be registered");
|
|
543
|
+
assert!(info.return_vertex.is_some());
|
|
544
|
+
let ret_vtx = info.return_vertex.unwrap();
|
|
545
|
+
assert_eq!(get_type_show(&genv, ret_vtx), "String");
|
|
546
|
+
|
|
547
|
+
// Other methods still work
|
|
548
|
+
let greet_src = r#"
|
|
549
|
+
class User
|
|
550
|
+
attr_reader :name
|
|
551
|
+
|
|
552
|
+
def greet
|
|
553
|
+
"hello"
|
|
554
|
+
end
|
|
555
|
+
end
|
|
556
|
+
"#;
|
|
557
|
+
let genv2 = analyze(greet_src);
|
|
558
|
+
let info2 = genv2
|
|
559
|
+
.resolve_method(&Type::instance("User"), "greet")
|
|
560
|
+
.expect("User#greet should be registered");
|
|
561
|
+
assert_eq!(get_type_show(&genv2, info2.return_vertex.unwrap()), "String");
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Test 8: attr_reader + self.name method call
|
|
565
|
+
#[test]
|
|
566
|
+
fn test_attr_reader_with_self_call() {
|
|
567
|
+
let source = r#"
|
|
568
|
+
class User
|
|
569
|
+
attr_reader :name
|
|
570
|
+
|
|
571
|
+
def initialize
|
|
572
|
+
@name = "Alice"
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
def greet
|
|
576
|
+
self.name
|
|
577
|
+
end
|
|
578
|
+
end
|
|
579
|
+
"#;
|
|
580
|
+
let genv = analyze(source);
|
|
581
|
+
|
|
582
|
+
// User#greet should resolve to String via User#name → @name
|
|
583
|
+
let info = genv
|
|
584
|
+
.resolve_method(&Type::instance("User"), "greet")
|
|
585
|
+
.expect("User#greet should be registered");
|
|
586
|
+
assert!(info.return_vertex.is_some());
|
|
587
|
+
let ret_vtx = info.return_vertex.unwrap();
|
|
588
|
+
assert_eq!(get_type_show(&genv, ret_vtx), "String");
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// Test 9: attr_reader + receiverless name call (proposal D integration)
|
|
592
|
+
#[test]
|
|
593
|
+
fn test_attr_reader_receiverless_call() {
|
|
594
|
+
let source = r#"
|
|
595
|
+
class User
|
|
596
|
+
attr_reader :name
|
|
597
|
+
|
|
598
|
+
def initialize
|
|
599
|
+
@name = "Alice"
|
|
600
|
+
end
|
|
601
|
+
|
|
602
|
+
def greet
|
|
603
|
+
name
|
|
604
|
+
end
|
|
605
|
+
end
|
|
606
|
+
"#;
|
|
607
|
+
let genv = analyze(source);
|
|
608
|
+
|
|
609
|
+
// User#greet should resolve to String via implicit self → User#name → @name
|
|
610
|
+
let info = genv
|
|
611
|
+
.resolve_method(&Type::instance("User"), "greet")
|
|
612
|
+
.expect("User#greet should be registered");
|
|
613
|
+
assert!(info.return_vertex.is_some());
|
|
614
|
+
let ret_vtx = info.return_vertex.unwrap();
|
|
615
|
+
assert_eq!(get_type_show(&genv, ret_vtx), "String");
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// Test 10: attr_accessor — both getter and setter
|
|
619
|
+
#[test]
|
|
620
|
+
fn test_attr_accessor() {
|
|
621
|
+
let source = r#"
|
|
622
|
+
class User
|
|
623
|
+
attr_accessor :age
|
|
624
|
+
|
|
625
|
+
def initialize
|
|
626
|
+
@age = 30
|
|
627
|
+
end
|
|
628
|
+
end
|
|
629
|
+
"#;
|
|
630
|
+
let genv = analyze(source);
|
|
631
|
+
|
|
632
|
+
// User#age (getter) should be registered and resolve to Integer
|
|
633
|
+
let getter = genv
|
|
634
|
+
.resolve_method(&Type::instance("User"), "age")
|
|
635
|
+
.expect("User#age getter should be registered");
|
|
636
|
+
assert!(getter.return_vertex.is_some());
|
|
637
|
+
assert_eq!(get_type_show(&genv, getter.return_vertex.unwrap()), "Integer");
|
|
638
|
+
|
|
639
|
+
// User#age= (setter) should also be registered
|
|
640
|
+
let setter = genv
|
|
641
|
+
.resolve_method(&Type::instance("User"), "age=")
|
|
642
|
+
.expect("User#age= setter should be registered");
|
|
643
|
+
assert!(setter.return_vertex.is_some());
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// Test 11: multiple attributes in single declaration
|
|
647
|
+
#[test]
|
|
648
|
+
fn test_attr_reader_multiple() {
|
|
649
|
+
let source = r#"
|
|
650
|
+
class User
|
|
651
|
+
attr_reader :name, :email
|
|
652
|
+
|
|
653
|
+
def initialize
|
|
654
|
+
@name = "Alice"
|
|
655
|
+
@email = "alice@test.com"
|
|
656
|
+
end
|
|
657
|
+
end
|
|
658
|
+
"#;
|
|
659
|
+
let genv = analyze(source);
|
|
660
|
+
|
|
661
|
+
let name_info = genv
|
|
662
|
+
.resolve_method(&Type::instance("User"), "name")
|
|
663
|
+
.expect("User#name should be registered");
|
|
664
|
+
assert_eq!(get_type_show(&genv, name_info.return_vertex.unwrap()), "String");
|
|
665
|
+
|
|
666
|
+
let email_info = genv
|
|
667
|
+
.resolve_method(&Type::instance("User"), "email")
|
|
668
|
+
.expect("User#email should be registered");
|
|
669
|
+
assert_eq!(get_type_show(&genv, email_info.return_vertex.unwrap()), "String");
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// Test 12: attr_reader in nested class
|
|
673
|
+
#[test]
|
|
674
|
+
fn test_attr_reader_nested_class() {
|
|
675
|
+
let source = r#"
|
|
676
|
+
module Api
|
|
677
|
+
class User
|
|
678
|
+
attr_reader :name
|
|
679
|
+
|
|
680
|
+
def initialize
|
|
681
|
+
@name = "Alice"
|
|
682
|
+
end
|
|
683
|
+
end
|
|
684
|
+
end
|
|
685
|
+
"#;
|
|
686
|
+
let genv = analyze(source);
|
|
687
|
+
|
|
688
|
+
// Registered with simple class name "User"
|
|
689
|
+
let info = genv
|
|
690
|
+
.resolve_method(&Type::instance("User"), "name")
|
|
691
|
+
.expect("User#name should be registered");
|
|
692
|
+
assert!(info.return_vertex.is_some());
|
|
693
|
+
assert_eq!(get_type_show(&genv, info.return_vertex.unwrap()), "String");
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// Test 13: attr_writer only — setter registered, getter not
|
|
697
|
+
#[test]
|
|
698
|
+
fn test_attr_writer_only() {
|
|
699
|
+
let source = r#"
|
|
700
|
+
class User
|
|
701
|
+
attr_writer :name
|
|
702
|
+
end
|
|
703
|
+
"#;
|
|
704
|
+
let genv = analyze(source);
|
|
705
|
+
|
|
706
|
+
// User#name= should be registered
|
|
707
|
+
let setter = genv.resolve_method(&Type::instance("User"), "name=");
|
|
708
|
+
assert!(setter.is_some(), "User#name= should be registered");
|
|
709
|
+
|
|
710
|
+
// User#name (getter) should NOT be registered
|
|
711
|
+
let getter = genv.resolve_method(&Type::instance("User"), "name");
|
|
712
|
+
assert!(getter.is_none(), "User#name getter should NOT be registered for attr_writer");
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// Test 14: attr_reader with no assignment (empty vertex)
|
|
716
|
+
#[test]
|
|
717
|
+
fn test_attr_reader_unassigned() {
|
|
718
|
+
let source = r#"
|
|
719
|
+
class User
|
|
720
|
+
attr_reader :unknown
|
|
721
|
+
end
|
|
722
|
+
"#;
|
|
723
|
+
let genv = analyze(source);
|
|
724
|
+
|
|
725
|
+
// User#unknown should be registered (with empty vertex)
|
|
726
|
+
let info = genv
|
|
727
|
+
.resolve_method(&Type::instance("User"), "unknown")
|
|
728
|
+
.expect("User#unknown should be registered");
|
|
729
|
+
assert!(info.return_vertex.is_some());
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// Test 15: super call independence (SuperNode is not CallNode)
|
|
733
|
+
#[test]
|
|
734
|
+
fn test_super_call_independence() {
|
|
735
|
+
let source = r#"
|
|
736
|
+
class Base
|
|
737
|
+
def greet
|
|
738
|
+
"hello"
|
|
739
|
+
end
|
|
740
|
+
end
|
|
741
|
+
|
|
742
|
+
class Child < Base
|
|
743
|
+
def greet
|
|
744
|
+
super
|
|
745
|
+
end
|
|
746
|
+
end
|
|
747
|
+
"#;
|
|
748
|
+
let genv = analyze(source);
|
|
749
|
+
|
|
750
|
+
// super is a SuperNode, not a CallNode, so ImplicitSelfCall should not be triggered.
|
|
751
|
+
// Base#greet should still work.
|
|
752
|
+
let info = genv
|
|
753
|
+
.resolve_method(&Type::instance("Base"), "greet")
|
|
754
|
+
.expect("Base#greet should be registered");
|
|
755
|
+
assert!(info.return_vertex.is_some());
|
|
756
|
+
|
|
757
|
+
let ret_vtx = info.return_vertex.unwrap();
|
|
758
|
+
assert_eq!(get_type_show(&genv, ret_vtx), "String");
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// Test 16: User.new → instance(User)
|
|
762
|
+
#[test]
|
|
763
|
+
fn test_constant_read_user_new() {
|
|
764
|
+
let source = r#"
|
|
765
|
+
class User
|
|
766
|
+
def name
|
|
767
|
+
"Alice"
|
|
768
|
+
end
|
|
769
|
+
end
|
|
770
|
+
|
|
771
|
+
x = User.new
|
|
772
|
+
"#;
|
|
773
|
+
let genv = analyze(source);
|
|
774
|
+
assert!(
|
|
775
|
+
genv.type_errors.is_empty(),
|
|
776
|
+
"User.new should not produce type errors: {:?}",
|
|
777
|
+
genv.type_errors
|
|
778
|
+
);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// Test 17: User.new.name → String
|
|
782
|
+
#[test]
|
|
783
|
+
fn test_constant_read_user_new_method_chain() {
|
|
784
|
+
let source = r#"
|
|
785
|
+
class User
|
|
786
|
+
def name
|
|
787
|
+
"Alice"
|
|
788
|
+
end
|
|
789
|
+
end
|
|
790
|
+
|
|
791
|
+
x = User.new.name
|
|
792
|
+
"#;
|
|
793
|
+
let genv = analyze(source);
|
|
794
|
+
assert!(
|
|
795
|
+
genv.type_errors.is_empty(),
|
|
796
|
+
"User.new.name should not produce type errors: {:?}",
|
|
797
|
+
genv.type_errors
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// Test 18: Api::User.new → instance(Api::User) (ConstantPathNode)
|
|
802
|
+
#[test]
|
|
803
|
+
fn test_constant_path_qualified_new() {
|
|
804
|
+
let source = r#"
|
|
805
|
+
class Api::User
|
|
806
|
+
def name
|
|
807
|
+
"Alice"
|
|
808
|
+
end
|
|
809
|
+
end
|
|
810
|
+
|
|
811
|
+
x = Api::User.new
|
|
812
|
+
"#;
|
|
813
|
+
let genv = analyze(source);
|
|
814
|
+
assert!(
|
|
815
|
+
genv.type_errors.is_empty(),
|
|
816
|
+
"Api::User.new should not produce type errors: {:?}",
|
|
817
|
+
genv.type_errors
|
|
818
|
+
);
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
// Test 19: User.new("Alice") → initialize parameter propagation
|
|
822
|
+
#[test]
|
|
823
|
+
fn test_constant_read_new_with_initialize_params() {
|
|
824
|
+
let source = r#"
|
|
825
|
+
class User
|
|
826
|
+
def initialize(name)
|
|
827
|
+
@name = name
|
|
828
|
+
end
|
|
829
|
+
end
|
|
830
|
+
|
|
831
|
+
x = User.new("Alice")
|
|
832
|
+
"#;
|
|
833
|
+
let genv = analyze(source);
|
|
834
|
+
assert!(genv.type_errors.is_empty());
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// Test 20: user = User.new; user.name → String
|
|
838
|
+
#[test]
|
|
839
|
+
fn test_constant_read_assign_and_call() {
|
|
840
|
+
let source = r#"
|
|
841
|
+
class User
|
|
842
|
+
def name
|
|
843
|
+
"Alice"
|
|
844
|
+
end
|
|
845
|
+
end
|
|
846
|
+
|
|
847
|
+
user = User.new
|
|
848
|
+
user.name
|
|
849
|
+
"#;
|
|
850
|
+
let genv = analyze(source);
|
|
851
|
+
assert!(
|
|
852
|
+
genv.type_errors.is_empty(),
|
|
853
|
+
"user = User.new; user.name should not produce type errors: {:?}",
|
|
854
|
+
genv.type_errors
|
|
855
|
+
);
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
// Test 21: User.some_method should not produce type error (Singleton error suppression)
|
|
859
|
+
#[test]
|
|
860
|
+
fn test_constant_read_no_false_positive() {
|
|
861
|
+
let source = r#"
|
|
862
|
+
class User
|
|
863
|
+
def name
|
|
864
|
+
"Alice"
|
|
865
|
+
end
|
|
866
|
+
end
|
|
867
|
+
|
|
868
|
+
User.some_method
|
|
869
|
+
"#;
|
|
870
|
+
let genv = analyze(source);
|
|
871
|
+
assert!(
|
|
872
|
+
genv.type_errors.is_empty(),
|
|
873
|
+
"User.some_method should not produce type errors (Singleton suppression): {:?}",
|
|
874
|
+
genv.type_errors
|
|
875
|
+
);
|
|
876
|
+
}
|
|
134
877
|
}
|