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
|
@@ -1,39 +1,180 @@
|
|
|
1
|
-
//! Definition Handlers - Processing Ruby class/method definitions
|
|
1
|
+
//! Definition Handlers - Processing Ruby class/method/module definitions
|
|
2
2
|
//!
|
|
3
3
|
//! This module is responsible for:
|
|
4
4
|
//! - Class definition scope management (class Foo ... end)
|
|
5
|
-
//! -
|
|
6
|
-
//! -
|
|
5
|
+
//! - Module definition scope management (module Bar ... end)
|
|
6
|
+
//! - Method definition scope management (def baz ... end)
|
|
7
|
+
//! - Extracting class/module names from AST nodes (including qualified names like Api::User)
|
|
7
8
|
|
|
8
|
-
use crate::env::GlobalEnv;
|
|
9
|
+
use crate::env::{GlobalEnv, LocalEnv};
|
|
10
|
+
use crate::graph::{ChangeSet, VertexId};
|
|
11
|
+
use crate::types::Type;
|
|
12
|
+
use ruby_prism::Node;
|
|
13
|
+
|
|
14
|
+
use super::install::install_statements;
|
|
15
|
+
use super::parameters::install_parameters;
|
|
16
|
+
|
|
17
|
+
/// Process class definition node
|
|
18
|
+
pub(crate) fn process_class_node(
|
|
19
|
+
genv: &mut GlobalEnv,
|
|
20
|
+
lenv: &mut LocalEnv,
|
|
21
|
+
changes: &mut ChangeSet,
|
|
22
|
+
source: &str,
|
|
23
|
+
class_node: &ruby_prism::ClassNode,
|
|
24
|
+
) -> Option<VertexId> {
|
|
25
|
+
let class_name = extract_class_name(class_node);
|
|
26
|
+
install_class(genv, class_name);
|
|
27
|
+
|
|
28
|
+
if let Some(body) = class_node.body() {
|
|
29
|
+
if let Some(statements) = body.as_statements_node() {
|
|
30
|
+
install_statements(genv, lenv, changes, source, &statements);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
exit_scope(genv);
|
|
35
|
+
None
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/// Process module definition node
|
|
39
|
+
pub(crate) fn process_module_node(
|
|
40
|
+
genv: &mut GlobalEnv,
|
|
41
|
+
lenv: &mut LocalEnv,
|
|
42
|
+
changes: &mut ChangeSet,
|
|
43
|
+
source: &str,
|
|
44
|
+
module_node: &ruby_prism::ModuleNode,
|
|
45
|
+
) -> Option<VertexId> {
|
|
46
|
+
let module_name = extract_module_name(module_node);
|
|
47
|
+
install_module(genv, module_name);
|
|
48
|
+
|
|
49
|
+
if let Some(body) = module_node.body() {
|
|
50
|
+
if let Some(statements) = body.as_statements_node() {
|
|
51
|
+
install_statements(genv, lenv, changes, source, &statements);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
exit_scope(genv);
|
|
56
|
+
None
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/// Process method definition node
|
|
60
|
+
pub(crate) fn process_def_node(
|
|
61
|
+
genv: &mut GlobalEnv,
|
|
62
|
+
lenv: &mut LocalEnv,
|
|
63
|
+
changes: &mut ChangeSet,
|
|
64
|
+
source: &str,
|
|
65
|
+
def_node: &ruby_prism::DefNode,
|
|
66
|
+
) -> Option<VertexId> {
|
|
67
|
+
let method_name = String::from_utf8_lossy(def_node.name().as_slice()).to_string();
|
|
68
|
+
install_method(genv, method_name.clone());
|
|
69
|
+
|
|
70
|
+
// Process parameters BEFORE processing body
|
|
71
|
+
let param_vtxs = if let Some(params_node) = def_node.parameters() {
|
|
72
|
+
install_parameters(genv, lenv, changes, source, ¶ms_node)
|
|
73
|
+
} else {
|
|
74
|
+
vec![]
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
let mut last_vtx = None;
|
|
78
|
+
if let Some(body) = def_node.body() {
|
|
79
|
+
if let Some(statements) = body.as_statements_node() {
|
|
80
|
+
last_vtx = install_statements(genv, lenv, changes, source, &statements);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Register user-defined method with return vertex and param vertices (before exiting scope)
|
|
85
|
+
if let Some(return_vtx) = last_vtx {
|
|
86
|
+
let recv_type_name = genv
|
|
87
|
+
.scope_manager
|
|
88
|
+
.current_class_name()
|
|
89
|
+
.or_else(|| genv.scope_manager.current_module_name());
|
|
90
|
+
|
|
91
|
+
if let Some(name) = recv_type_name {
|
|
92
|
+
genv.register_user_method(
|
|
93
|
+
Type::instance(&name),
|
|
94
|
+
&method_name,
|
|
95
|
+
return_vtx,
|
|
96
|
+
param_vtxs,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
exit_scope(genv);
|
|
102
|
+
None
|
|
103
|
+
}
|
|
9
104
|
|
|
10
105
|
/// Install class definition
|
|
11
|
-
|
|
106
|
+
fn install_class(genv: &mut GlobalEnv, class_name: String) {
|
|
12
107
|
genv.enter_class(class_name);
|
|
13
108
|
}
|
|
14
109
|
|
|
110
|
+
/// Install module definition
|
|
111
|
+
fn install_module(genv: &mut GlobalEnv, module_name: String) {
|
|
112
|
+
genv.enter_module(module_name);
|
|
113
|
+
}
|
|
114
|
+
|
|
15
115
|
/// Install method definition
|
|
16
|
-
|
|
116
|
+
fn install_method(genv: &mut GlobalEnv, method_name: String) {
|
|
17
117
|
genv.enter_method(method_name);
|
|
18
118
|
}
|
|
19
119
|
|
|
20
|
-
/// Exit current scope (class or method)
|
|
21
|
-
|
|
120
|
+
/// Exit current scope (class, module, or method)
|
|
121
|
+
fn exit_scope(genv: &mut GlobalEnv) {
|
|
22
122
|
genv.exit_scope();
|
|
23
123
|
}
|
|
24
124
|
|
|
25
125
|
/// Extract class name from ClassNode
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
126
|
+
/// Supports both simple names (User) and qualified names (Api::V1::User)
|
|
127
|
+
fn extract_class_name(class_node: &ruby_prism::ClassNode) -> String {
|
|
128
|
+
extract_constant_path(&class_node.constant_path()).unwrap_or_else(|| "UnknownClass".to_string())
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/// Extract module name from ModuleNode
|
|
132
|
+
/// Supports both simple names (Utils) and qualified names (Api::V1::Utils)
|
|
133
|
+
fn extract_module_name(module_node: &ruby_prism::ModuleNode) -> String {
|
|
134
|
+
extract_constant_path(&module_node.constant_path())
|
|
135
|
+
.unwrap_or_else(|| "UnknownModule".to_string())
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/// Extract constant path from a Node (handles both ConstantReadNode and ConstantPathNode)
|
|
139
|
+
///
|
|
140
|
+
/// Examples:
|
|
141
|
+
/// - `User` (ConstantReadNode) → "User"
|
|
142
|
+
/// - `Api::User` (ConstantPathNode) → "Api::User"
|
|
143
|
+
/// - `Api::V1::User` (nested ConstantPathNode) → "Api::V1::User"
|
|
144
|
+
/// - `::Api::User` (absolute path with COLON3) → "Api::User"
|
|
145
|
+
pub(crate) fn extract_constant_path(node: &Node) -> Option<String> {
|
|
146
|
+
// Simple constant read: `User`
|
|
147
|
+
if let Some(constant_read) = node.as_constant_read_node() {
|
|
148
|
+
return Some(String::from_utf8_lossy(constant_read.name().as_slice()).to_string());
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Constant path: `Api::User` or `Api::V1::User`
|
|
152
|
+
if let Some(constant_path) = node.as_constant_path_node() {
|
|
153
|
+
// name() returns Option<ConstantId>, use as_slice() to get &[u8]
|
|
154
|
+
let name = constant_path
|
|
155
|
+
.name()
|
|
156
|
+
.map(|id| String::from_utf8_lossy(id.as_slice()).to_string())?;
|
|
157
|
+
|
|
158
|
+
// Get parent path if exists
|
|
159
|
+
if let Some(parent_node) = constant_path.parent() {
|
|
160
|
+
if let Some(parent_path) = extract_constant_path(&parent_node) {
|
|
161
|
+
return Some(format!("{}::{}", parent_path, name));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// No parent (absolute path like `::User`)
|
|
166
|
+
return Some(name);
|
|
31
167
|
}
|
|
168
|
+
|
|
169
|
+
None
|
|
32
170
|
}
|
|
33
171
|
|
|
34
172
|
#[cfg(test)]
|
|
35
173
|
mod tests {
|
|
36
174
|
use super::*;
|
|
175
|
+
use crate::graph::ChangeSet;
|
|
176
|
+
use crate::parser::ParseSession;
|
|
177
|
+
use crate::types::Type;
|
|
37
178
|
|
|
38
179
|
#[test]
|
|
39
180
|
fn test_enter_exit_class_scope() {
|
|
@@ -49,6 +190,20 @@ mod tests {
|
|
|
49
190
|
assert_eq!(genv.scope_manager.current_class_name(), None);
|
|
50
191
|
}
|
|
51
192
|
|
|
193
|
+
#[test]
|
|
194
|
+
fn test_enter_exit_module_scope() {
|
|
195
|
+
let mut genv = GlobalEnv::new();
|
|
196
|
+
|
|
197
|
+
install_module(&mut genv, "Utils".to_string());
|
|
198
|
+
assert_eq!(
|
|
199
|
+
genv.scope_manager.current_module_name(),
|
|
200
|
+
Some("Utils".to_string())
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
exit_scope(&mut genv);
|
|
204
|
+
assert_eq!(genv.scope_manager.current_module_name(), None);
|
|
205
|
+
}
|
|
206
|
+
|
|
52
207
|
#[test]
|
|
53
208
|
fn test_nested_method_scope() {
|
|
54
209
|
let mut genv = GlobalEnv::new();
|
|
@@ -67,4 +222,116 @@ mod tests {
|
|
|
67
222
|
|
|
68
223
|
assert_eq!(genv.scope_manager.current_class_name(), None);
|
|
69
224
|
}
|
|
225
|
+
|
|
226
|
+
#[test]
|
|
227
|
+
fn test_method_in_module() {
|
|
228
|
+
let mut genv = GlobalEnv::new();
|
|
229
|
+
|
|
230
|
+
install_module(&mut genv, "Helpers".to_string());
|
|
231
|
+
install_method(&mut genv, "format".to_string());
|
|
232
|
+
|
|
233
|
+
// Should find module context from within method
|
|
234
|
+
assert_eq!(
|
|
235
|
+
genv.scope_manager.current_module_name(),
|
|
236
|
+
Some("Helpers".to_string())
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
exit_scope(&mut genv); // exit method
|
|
240
|
+
exit_scope(&mut genv); // exit module
|
|
241
|
+
|
|
242
|
+
assert_eq!(genv.scope_manager.current_module_name(), None);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
#[test]
|
|
246
|
+
fn test_extract_simple_class_name() {
|
|
247
|
+
let source = "class User; end";
|
|
248
|
+
let session = ParseSession::new();
|
|
249
|
+
let parse_result = session.parse_source(source, "test.rb").unwrap();
|
|
250
|
+
let root = parse_result.node();
|
|
251
|
+
let program = root.as_program_node().unwrap();
|
|
252
|
+
let stmt = program.statements().body().first().unwrap();
|
|
253
|
+
let class_node = stmt.as_class_node().unwrap();
|
|
254
|
+
|
|
255
|
+
let name = extract_class_name(&class_node);
|
|
256
|
+
assert_eq!(name, "User");
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
#[test]
|
|
260
|
+
fn test_extract_qualified_class_name() {
|
|
261
|
+
let source = "class Api::User; end";
|
|
262
|
+
let session = ParseSession::new();
|
|
263
|
+
let parse_result = session.parse_source(source, "test.rb").unwrap();
|
|
264
|
+
let root = parse_result.node();
|
|
265
|
+
let program = root.as_program_node().unwrap();
|
|
266
|
+
let stmt = program.statements().body().first().unwrap();
|
|
267
|
+
let class_node = stmt.as_class_node().unwrap();
|
|
268
|
+
|
|
269
|
+
let name = extract_class_name(&class_node);
|
|
270
|
+
assert_eq!(name, "Api::User");
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
#[test]
|
|
274
|
+
fn test_extract_deeply_qualified_class_name() {
|
|
275
|
+
let source = "class Api::V1::Admin::User; end";
|
|
276
|
+
let session = ParseSession::new();
|
|
277
|
+
let parse_result = session.parse_source(source, "test.rb").unwrap();
|
|
278
|
+
let root = parse_result.node();
|
|
279
|
+
let program = root.as_program_node().unwrap();
|
|
280
|
+
let stmt = program.statements().body().first().unwrap();
|
|
281
|
+
let class_node = stmt.as_class_node().unwrap();
|
|
282
|
+
|
|
283
|
+
let name = extract_class_name(&class_node);
|
|
284
|
+
assert_eq!(name, "Api::V1::Admin::User");
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
#[test]
|
|
288
|
+
fn test_extract_simple_module_name() {
|
|
289
|
+
let source = "module Utils; end";
|
|
290
|
+
let session = ParseSession::new();
|
|
291
|
+
let parse_result = session.parse_source(source, "test.rb").unwrap();
|
|
292
|
+
let root = parse_result.node();
|
|
293
|
+
let program = root.as_program_node().unwrap();
|
|
294
|
+
let stmt = program.statements().body().first().unwrap();
|
|
295
|
+
let module_node = stmt.as_module_node().unwrap();
|
|
296
|
+
|
|
297
|
+
let name = extract_module_name(&module_node);
|
|
298
|
+
assert_eq!(name, "Utils");
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
#[test]
|
|
302
|
+
fn test_extract_qualified_module_name() {
|
|
303
|
+
let source = "module Api::V1; end";
|
|
304
|
+
let session = ParseSession::new();
|
|
305
|
+
let parse_result = session.parse_source(source, "test.rb").unwrap();
|
|
306
|
+
let root = parse_result.node();
|
|
307
|
+
let program = root.as_program_node().unwrap();
|
|
308
|
+
let stmt = program.statements().body().first().unwrap();
|
|
309
|
+
let module_node = stmt.as_module_node().unwrap();
|
|
310
|
+
|
|
311
|
+
let name = extract_module_name(&module_node);
|
|
312
|
+
assert_eq!(name, "Api::V1");
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
#[test]
|
|
316
|
+
fn test_process_def_node_registers_user_method() {
|
|
317
|
+
let source = "class User; def name; \"Alice\"; end; end";
|
|
318
|
+
let session = ParseSession::new();
|
|
319
|
+
let parse_result = session.parse_source(source, "test.rb").unwrap();
|
|
320
|
+
let root = parse_result.node();
|
|
321
|
+
let program = root.as_program_node().unwrap();
|
|
322
|
+
|
|
323
|
+
let mut genv = GlobalEnv::new();
|
|
324
|
+
let mut lenv = LocalEnv::new();
|
|
325
|
+
let mut changes = ChangeSet::new();
|
|
326
|
+
|
|
327
|
+
let stmt = program.statements().body().first().unwrap();
|
|
328
|
+
let class_node = stmt.as_class_node().unwrap();
|
|
329
|
+
process_class_node(&mut genv, &mut lenv, &mut changes, source, &class_node);
|
|
330
|
+
|
|
331
|
+
// User#name should be registered as a user-defined method
|
|
332
|
+
let info = genv
|
|
333
|
+
.resolve_method(&Type::instance("User"), "name")
|
|
334
|
+
.expect("User#name should be registered");
|
|
335
|
+
assert!(info.return_vertex.is_some());
|
|
336
|
+
}
|
|
70
337
|
}
|