method-ray 0.1.1
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 +7 -0
- data/CHANGELOG.md +23 -0
- data/LICENSE +21 -0
- data/README.md +39 -0
- data/exe/methodray +7 -0
- data/ext/Cargo.toml +24 -0
- data/ext/extconf.rb +40 -0
- data/ext/src/cli.rs +33 -0
- data/ext/src/lib.rs +79 -0
- data/lib/methodray/cli.rb +28 -0
- data/lib/methodray/commands.rb +78 -0
- data/lib/methodray/version.rb +5 -0
- data/lib/methodray.rb +9 -0
- data/rust/Cargo.toml +39 -0
- data/rust/src/analyzer/calls.rs +56 -0
- data/rust/src/analyzer/definitions.rs +70 -0
- data/rust/src/analyzer/dispatch.rs +134 -0
- data/rust/src/analyzer/install.rs +226 -0
- data/rust/src/analyzer/literals.rs +85 -0
- data/rust/src/analyzer/mod.rs +11 -0
- data/rust/src/analyzer/tests/integration_test.rs +136 -0
- data/rust/src/analyzer/tests/mod.rs +1 -0
- data/rust/src/analyzer/variables.rs +76 -0
- data/rust/src/cache/mod.rs +3 -0
- data/rust/src/cache/rbs_cache.rs +158 -0
- data/rust/src/checker.rs +139 -0
- data/rust/src/cli/args.rs +40 -0
- data/rust/src/cli/commands.rs +139 -0
- data/rust/src/cli/mod.rs +6 -0
- data/rust/src/diagnostics/diagnostic.rs +125 -0
- data/rust/src/diagnostics/formatter.rs +119 -0
- data/rust/src/diagnostics/mod.rs +5 -0
- data/rust/src/env/box_manager.rs +121 -0
- data/rust/src/env/global_env.rs +279 -0
- data/rust/src/env/local_env.rs +58 -0
- data/rust/src/env/method_registry.rs +63 -0
- data/rust/src/env/mod.rs +15 -0
- data/rust/src/env/scope.rs +330 -0
- data/rust/src/env/type_error.rs +23 -0
- data/rust/src/env/vertex_manager.rs +195 -0
- data/rust/src/graph/box.rs +157 -0
- data/rust/src/graph/change_set.rs +115 -0
- data/rust/src/graph/mod.rs +7 -0
- data/rust/src/graph/vertex.rs +167 -0
- data/rust/src/lib.rs +24 -0
- data/rust/src/lsp/diagnostics.rs +133 -0
- data/rust/src/lsp/main.rs +8 -0
- data/rust/src/lsp/mod.rs +4 -0
- data/rust/src/lsp/server.rs +138 -0
- data/rust/src/main.rs +46 -0
- data/rust/src/parser.rs +96 -0
- data/rust/src/rbs/converter.rs +82 -0
- data/rust/src/rbs/error.rs +37 -0
- data/rust/src/rbs/loader.rs +183 -0
- data/rust/src/rbs/mod.rs +15 -0
- data/rust/src/source_map.rs +102 -0
- data/rust/src/types.rs +75 -0
- metadata +119 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
//! Node Dispatch - Dispatch AST nodes to appropriate handlers
|
|
2
|
+
//!
|
|
3
|
+
//! This module handles the pattern matching of Ruby AST nodes
|
|
4
|
+
//! and dispatches them to specialized handlers.
|
|
5
|
+
|
|
6
|
+
use crate::env::{GlobalEnv, LocalEnv};
|
|
7
|
+
use crate::graph::{ChangeSet, VertexId};
|
|
8
|
+
use crate::source_map::SourceLocation;
|
|
9
|
+
use ruby_prism::Node;
|
|
10
|
+
|
|
11
|
+
use super::calls::install_method_call;
|
|
12
|
+
use super::literals::install_literal;
|
|
13
|
+
use super::variables::{
|
|
14
|
+
install_ivar_read, install_ivar_write, install_local_var_read, install_local_var_write,
|
|
15
|
+
install_self,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/// Result of dispatching a simple node (no child processing needed)
|
|
19
|
+
pub enum DispatchResult {
|
|
20
|
+
/// Node produced a vertex
|
|
21
|
+
Vertex(VertexId),
|
|
22
|
+
/// Node was not handled
|
|
23
|
+
NotHandled,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/// Kind of child processing needed
|
|
27
|
+
pub enum NeedsChildKind<'a> {
|
|
28
|
+
/// Instance variable write: need to process value, then call finish_ivar_write
|
|
29
|
+
IvarWrite { ivar_name: String, value: Node<'a> },
|
|
30
|
+
/// Local variable write: need to process value, then call finish_local_var_write
|
|
31
|
+
LocalVarWrite { var_name: String, value: Node<'a> },
|
|
32
|
+
/// Method call: need to process receiver, then call finish_method_call
|
|
33
|
+
MethodCall {
|
|
34
|
+
receiver: Node<'a>,
|
|
35
|
+
method_name: String,
|
|
36
|
+
location: SourceLocation,
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/// First pass: check if node can be handled immediately without child processing
|
|
41
|
+
pub fn dispatch_simple(genv: &mut GlobalEnv, lenv: &mut LocalEnv, node: &Node) -> DispatchResult {
|
|
42
|
+
// Instance variable read: @name
|
|
43
|
+
if let Some(ivar_read) = node.as_instance_variable_read_node() {
|
|
44
|
+
let ivar_name = String::from_utf8_lossy(ivar_read.name().as_slice()).to_string();
|
|
45
|
+
return match install_ivar_read(genv, &ivar_name) {
|
|
46
|
+
Some(vtx) => DispatchResult::Vertex(vtx),
|
|
47
|
+
None => DispatchResult::NotHandled,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// self
|
|
52
|
+
if node.as_self_node().is_some() {
|
|
53
|
+
return DispatchResult::Vertex(install_self(genv));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Local variable read: x
|
|
57
|
+
if let Some(read_node) = node.as_local_variable_read_node() {
|
|
58
|
+
let var_name = String::from_utf8_lossy(read_node.name().as_slice()).to_string();
|
|
59
|
+
return match install_local_var_read(lenv, &var_name) {
|
|
60
|
+
Some(vtx) => DispatchResult::Vertex(vtx),
|
|
61
|
+
None => DispatchResult::NotHandled,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Literals (String, Integer, Array, Hash, nil, true, false, Symbol)
|
|
66
|
+
if let Some(vtx) = install_literal(genv, node) {
|
|
67
|
+
return DispatchResult::Vertex(vtx);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
DispatchResult::NotHandled
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/// Check if node needs child processing
|
|
74
|
+
pub fn dispatch_needs_child<'a>(node: &Node<'a>, source: &str) -> Option<NeedsChildKind<'a>> {
|
|
75
|
+
// Instance variable write: @name = value
|
|
76
|
+
if let Some(ivar_write) = node.as_instance_variable_write_node() {
|
|
77
|
+
let ivar_name = String::from_utf8_lossy(ivar_write.name().as_slice()).to_string();
|
|
78
|
+
return Some(NeedsChildKind::IvarWrite {
|
|
79
|
+
ivar_name,
|
|
80
|
+
value: ivar_write.value(),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Local variable write: x = value
|
|
85
|
+
if let Some(write_node) = node.as_local_variable_write_node() {
|
|
86
|
+
let var_name = String::from_utf8_lossy(write_node.name().as_slice()).to_string();
|
|
87
|
+
return Some(NeedsChildKind::LocalVarWrite {
|
|
88
|
+
var_name,
|
|
89
|
+
value: write_node.value(),
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Method call: x.upcase
|
|
94
|
+
if let Some(call_node) = node.as_call_node() {
|
|
95
|
+
if let Some(receiver) = call_node.receiver() {
|
|
96
|
+
let method_name = String::from_utf8_lossy(call_node.name().as_slice()).to_string();
|
|
97
|
+
let location =
|
|
98
|
+
SourceLocation::from_prism_location_with_source(&node.location(), source);
|
|
99
|
+
return Some(NeedsChildKind::MethodCall {
|
|
100
|
+
receiver,
|
|
101
|
+
method_name,
|
|
102
|
+
location,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
None
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/// Finish instance variable write after child is processed
|
|
111
|
+
pub fn finish_ivar_write(genv: &mut GlobalEnv, ivar_name: String, value_vtx: VertexId) -> VertexId {
|
|
112
|
+
install_ivar_write(genv, ivar_name, value_vtx)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/// Finish local variable write after child is processed
|
|
116
|
+
pub fn finish_local_var_write(
|
|
117
|
+
genv: &mut GlobalEnv,
|
|
118
|
+
lenv: &mut LocalEnv,
|
|
119
|
+
changes: &mut ChangeSet,
|
|
120
|
+
var_name: String,
|
|
121
|
+
value_vtx: VertexId,
|
|
122
|
+
) -> VertexId {
|
|
123
|
+
install_local_var_write(genv, lenv, changes, var_name, value_vtx)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/// Finish method call after receiver is processed
|
|
127
|
+
pub fn finish_method_call(
|
|
128
|
+
genv: &mut GlobalEnv,
|
|
129
|
+
recv_vtx: VertexId,
|
|
130
|
+
method_name: String,
|
|
131
|
+
location: SourceLocation,
|
|
132
|
+
) -> VertexId {
|
|
133
|
+
install_method_call(genv, recv_vtx, method_name, Some(location))
|
|
134
|
+
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
//! AST Installer - AST traversal and graph construction
|
|
2
|
+
//!
|
|
3
|
+
//! This module is responsible for:
|
|
4
|
+
//! - Traversing the Ruby AST (Abstract Syntax Tree)
|
|
5
|
+
//! - Coordinating the graph construction process
|
|
6
|
+
|
|
7
|
+
use crate::env::{GlobalEnv, LocalEnv};
|
|
8
|
+
use crate::graph::{ChangeSet, VertexId};
|
|
9
|
+
use ruby_prism::Node;
|
|
10
|
+
|
|
11
|
+
use super::definitions::{exit_scope, extract_class_name, install_class, install_method};
|
|
12
|
+
use super::dispatch::{
|
|
13
|
+
dispatch_needs_child, dispatch_simple, finish_ivar_write, finish_local_var_write,
|
|
14
|
+
finish_method_call, DispatchResult, NeedsChildKind,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/// Build graph from AST
|
|
18
|
+
pub struct AstInstaller<'a> {
|
|
19
|
+
genv: &'a mut GlobalEnv,
|
|
20
|
+
lenv: &'a mut LocalEnv,
|
|
21
|
+
changes: ChangeSet,
|
|
22
|
+
source: &'a str,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
impl<'a> AstInstaller<'a> {
|
|
26
|
+
pub fn new(genv: &'a mut GlobalEnv, lenv: &'a mut LocalEnv, source: &'a str) -> Self {
|
|
27
|
+
Self {
|
|
28
|
+
genv,
|
|
29
|
+
lenv,
|
|
30
|
+
changes: ChangeSet::new(),
|
|
31
|
+
source,
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/// Install node (returns Vertex ID)
|
|
36
|
+
pub fn install_node(&mut self, node: &Node) -> Option<VertexId> {
|
|
37
|
+
// Class definition
|
|
38
|
+
if let Some(class_node) = node.as_class_node() {
|
|
39
|
+
return self.install_class_node(&class_node);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Method definition
|
|
43
|
+
if let Some(def_node) = node.as_def_node() {
|
|
44
|
+
return self.install_def_node(&def_node);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Try simple dispatch first (no child processing needed)
|
|
48
|
+
match dispatch_simple(self.genv, self.lenv, node) {
|
|
49
|
+
DispatchResult::Vertex(vtx) => return Some(vtx),
|
|
50
|
+
DispatchResult::NotHandled => {}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Check if node needs child processing
|
|
54
|
+
if let Some(kind) = dispatch_needs_child(node, self.source) {
|
|
55
|
+
return self.process_needs_child(kind);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
None
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/// Process nodes that need child evaluation first
|
|
62
|
+
fn process_needs_child(&mut self, kind: NeedsChildKind) -> Option<VertexId> {
|
|
63
|
+
match kind {
|
|
64
|
+
NeedsChildKind::IvarWrite { ivar_name, value } => {
|
|
65
|
+
let value_vtx = self.install_node(&value)?;
|
|
66
|
+
Some(finish_ivar_write(self.genv, ivar_name, value_vtx))
|
|
67
|
+
}
|
|
68
|
+
NeedsChildKind::LocalVarWrite { var_name, value } => {
|
|
69
|
+
let value_vtx = self.install_node(&value)?;
|
|
70
|
+
Some(finish_local_var_write(
|
|
71
|
+
self.genv,
|
|
72
|
+
self.lenv,
|
|
73
|
+
&mut self.changes,
|
|
74
|
+
var_name,
|
|
75
|
+
value_vtx,
|
|
76
|
+
))
|
|
77
|
+
}
|
|
78
|
+
NeedsChildKind::MethodCall {
|
|
79
|
+
receiver,
|
|
80
|
+
method_name,
|
|
81
|
+
location,
|
|
82
|
+
} => {
|
|
83
|
+
let recv_vtx = self.install_node(&receiver)?;
|
|
84
|
+
Some(finish_method_call(
|
|
85
|
+
self.genv,
|
|
86
|
+
recv_vtx,
|
|
87
|
+
method_name,
|
|
88
|
+
location,
|
|
89
|
+
))
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/// Install class definition
|
|
95
|
+
fn install_class_node(&mut self, class_node: &ruby_prism::ClassNode) -> Option<VertexId> {
|
|
96
|
+
let class_name = extract_class_name(class_node);
|
|
97
|
+
install_class(self.genv, class_name);
|
|
98
|
+
|
|
99
|
+
if let Some(body) = class_node.body() {
|
|
100
|
+
if let Some(statements) = body.as_statements_node() {
|
|
101
|
+
self.install_statements(&statements);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
exit_scope(self.genv);
|
|
106
|
+
None
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// Install method definition
|
|
110
|
+
fn install_def_node(&mut self, def_node: &ruby_prism::DefNode) -> Option<VertexId> {
|
|
111
|
+
let method_name = String::from_utf8_lossy(def_node.name().as_slice()).to_string();
|
|
112
|
+
install_method(self.genv, method_name);
|
|
113
|
+
|
|
114
|
+
if let Some(body) = def_node.body() {
|
|
115
|
+
if let Some(statements) = body.as_statements_node() {
|
|
116
|
+
self.install_statements(&statements);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
exit_scope(self.genv);
|
|
121
|
+
None
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/// Process multiple statements
|
|
125
|
+
fn install_statements(&mut self, statements: &ruby_prism::StatementsNode) {
|
|
126
|
+
for stmt in &statements.body() {
|
|
127
|
+
self.install_node(&stmt);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/// Finish installation (apply changes and execute Boxes)
|
|
132
|
+
pub fn finish(self) {
|
|
133
|
+
self.genv.apply_changes(self.changes);
|
|
134
|
+
self.genv.run_all();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
#[cfg(test)]
|
|
139
|
+
mod tests {
|
|
140
|
+
use super::*;
|
|
141
|
+
use crate::parser::parse_ruby_source;
|
|
142
|
+
use crate::types::Type;
|
|
143
|
+
|
|
144
|
+
#[test]
|
|
145
|
+
fn test_install_literal() {
|
|
146
|
+
let source = r#"x = "hello""#;
|
|
147
|
+
let parse_result = parse_ruby_source(source, "test.rb".to_string()).unwrap();
|
|
148
|
+
|
|
149
|
+
let mut genv = GlobalEnv::new();
|
|
150
|
+
let mut lenv = LocalEnv::new();
|
|
151
|
+
let mut installer = AstInstaller::new(&mut genv, &mut lenv, source);
|
|
152
|
+
|
|
153
|
+
let root = parse_result.node();
|
|
154
|
+
if let Some(program_node) = root.as_program_node() {
|
|
155
|
+
let statements = program_node.statements();
|
|
156
|
+
for stmt in &statements.body() {
|
|
157
|
+
installer.install_node(&stmt);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
installer.finish();
|
|
162
|
+
|
|
163
|
+
let x_vtx = lenv.get_var("x").unwrap();
|
|
164
|
+
assert_eq!(genv.get_vertex(x_vtx).unwrap().show(), "String");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
#[test]
|
|
168
|
+
fn test_install_multiple_vars() {
|
|
169
|
+
let source = r#"
|
|
170
|
+
x = "hello"
|
|
171
|
+
y = 42
|
|
172
|
+
"#;
|
|
173
|
+
let parse_result = parse_ruby_source(source, "test.rb".to_string()).unwrap();
|
|
174
|
+
|
|
175
|
+
let mut genv = GlobalEnv::new();
|
|
176
|
+
let mut lenv = LocalEnv::new();
|
|
177
|
+
let mut installer = AstInstaller::new(&mut genv, &mut lenv, source);
|
|
178
|
+
|
|
179
|
+
let root = parse_result.node();
|
|
180
|
+
if let Some(program_node) = root.as_program_node() {
|
|
181
|
+
let statements = program_node.statements();
|
|
182
|
+
for stmt in &statements.body() {
|
|
183
|
+
installer.install_node(&stmt);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
installer.finish();
|
|
188
|
+
|
|
189
|
+
let x_vtx = lenv.get_var("x").unwrap();
|
|
190
|
+
let y_vtx = lenv.get_var("y").unwrap();
|
|
191
|
+
|
|
192
|
+
assert_eq!(genv.get_vertex(x_vtx).unwrap().show(), "String");
|
|
193
|
+
assert_eq!(genv.get_vertex(y_vtx).unwrap().show(), "Integer");
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
#[test]
|
|
197
|
+
fn test_install_method_call() {
|
|
198
|
+
let source = r#"
|
|
199
|
+
x = "hello"
|
|
200
|
+
y = x.upcase
|
|
201
|
+
"#;
|
|
202
|
+
let parse_result = parse_ruby_source(source, "test.rb".to_string()).unwrap();
|
|
203
|
+
|
|
204
|
+
let mut genv = GlobalEnv::new();
|
|
205
|
+
genv.register_builtin_method(Type::string(), "upcase", Type::string());
|
|
206
|
+
|
|
207
|
+
let mut lenv = LocalEnv::new();
|
|
208
|
+
let mut installer = AstInstaller::new(&mut genv, &mut lenv, source);
|
|
209
|
+
|
|
210
|
+
let root = parse_result.node();
|
|
211
|
+
if let Some(program_node) = root.as_program_node() {
|
|
212
|
+
let statements = program_node.statements();
|
|
213
|
+
for stmt in &statements.body() {
|
|
214
|
+
installer.install_node(&stmt);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
installer.finish();
|
|
219
|
+
|
|
220
|
+
let x_vtx = lenv.get_var("x").unwrap();
|
|
221
|
+
let y_vtx = lenv.get_var("y").unwrap();
|
|
222
|
+
|
|
223
|
+
assert_eq!(genv.get_vertex(x_vtx).unwrap().show(), "String");
|
|
224
|
+
assert_eq!(genv.get_vertex(y_vtx).unwrap().show(), "String");
|
|
225
|
+
}
|
|
226
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
//! Literal Handlers - Processing Ruby literal values
|
|
2
|
+
//!
|
|
3
|
+
//! This module is responsible for:
|
|
4
|
+
//! - String, Integer, Array, Hash literals
|
|
5
|
+
//! - nil, true, false, Symbol literals
|
|
6
|
+
//! - Creating Source vertices with fixed types
|
|
7
|
+
|
|
8
|
+
use crate::env::GlobalEnv;
|
|
9
|
+
use crate::graph::VertexId;
|
|
10
|
+
use crate::types::Type;
|
|
11
|
+
use ruby_prism::Node;
|
|
12
|
+
|
|
13
|
+
/// Install literal nodes and return their VertexId
|
|
14
|
+
pub fn install_literal(genv: &mut GlobalEnv, node: &Node) -> Option<VertexId> {
|
|
15
|
+
// "hello"
|
|
16
|
+
if node.as_string_node().is_some() {
|
|
17
|
+
return Some(genv.new_source(Type::string()));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// 42
|
|
21
|
+
if node.as_integer_node().is_some() {
|
|
22
|
+
return Some(genv.new_source(Type::integer()));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// [1, 2, 3]
|
|
26
|
+
if node.as_array_node().is_some() {
|
|
27
|
+
return Some(genv.new_source(Type::array()));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// {a: 1}
|
|
31
|
+
if node.as_hash_node().is_some() {
|
|
32
|
+
return Some(genv.new_source(Type::hash()));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// nil
|
|
36
|
+
if node.as_nil_node().is_some() {
|
|
37
|
+
return Some(genv.new_source(Type::Nil));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// true
|
|
41
|
+
if node.as_true_node().is_some() {
|
|
42
|
+
return Some(genv.new_source(Type::Instance {
|
|
43
|
+
class_name: "TrueClass".to_string(),
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// false
|
|
48
|
+
if node.as_false_node().is_some() {
|
|
49
|
+
return Some(genv.new_source(Type::Instance {
|
|
50
|
+
class_name: "FalseClass".to_string(),
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// :symbol
|
|
55
|
+
if node.as_symbol_node().is_some() {
|
|
56
|
+
return Some(genv.new_source(Type::Instance {
|
|
57
|
+
class_name: "Symbol".to_string(),
|
|
58
|
+
}));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
None
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
#[cfg(test)]
|
|
65
|
+
mod tests {
|
|
66
|
+
use super::*;
|
|
67
|
+
|
|
68
|
+
#[test]
|
|
69
|
+
fn test_install_string_literal() {
|
|
70
|
+
let mut genv = GlobalEnv::new();
|
|
71
|
+
|
|
72
|
+
// Create a mock string node - we test via integration instead
|
|
73
|
+
// Unit test just verifies the type creation
|
|
74
|
+
let vtx = genv.new_source(Type::string());
|
|
75
|
+
assert_eq!(genv.get_source(vtx).unwrap().ty.show(), "String");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
#[test]
|
|
79
|
+
fn test_install_integer_literal() {
|
|
80
|
+
let mut genv = GlobalEnv::new();
|
|
81
|
+
|
|
82
|
+
let vtx = genv.new_source(Type::integer());
|
|
83
|
+
assert_eq!(genv.get_source(vtx).unwrap().ty.show(), "Integer");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
//! Integration Tests - End-to-end analyzer tests
|
|
2
|
+
//!
|
|
3
|
+
//! This module contains integration tests that verify:
|
|
4
|
+
//! - Class/method definition handling
|
|
5
|
+
//! - Instance variable type tracking across methods
|
|
6
|
+
//! - Type error detection for undefined methods
|
|
7
|
+
//! - Method chain type inference
|
|
8
|
+
|
|
9
|
+
use crate::analyzer::AstInstaller;
|
|
10
|
+
use crate::env::{GlobalEnv, LocalEnv};
|
|
11
|
+
use crate::parser::parse_ruby_source;
|
|
12
|
+
use crate::types::Type;
|
|
13
|
+
|
|
14
|
+
/// Helper to run analysis on Ruby source code
|
|
15
|
+
fn analyze(source: &str) -> (GlobalEnv, LocalEnv) {
|
|
16
|
+
let parse_result = parse_ruby_source(source, "test.rb".to_string()).unwrap();
|
|
17
|
+
|
|
18
|
+
let mut genv = GlobalEnv::new();
|
|
19
|
+
|
|
20
|
+
// Register common methods
|
|
21
|
+
genv.register_builtin_method(Type::string(), "upcase", Type::string());
|
|
22
|
+
genv.register_builtin_method(Type::string(), "downcase", Type::string());
|
|
23
|
+
|
|
24
|
+
let mut lenv = LocalEnv::new();
|
|
25
|
+
let mut installer = AstInstaller::new(&mut genv, &mut lenv, source);
|
|
26
|
+
|
|
27
|
+
let root = parse_result.node();
|
|
28
|
+
|
|
29
|
+
if let Some(program_node) = root.as_program_node() {
|
|
30
|
+
let statements = program_node.statements();
|
|
31
|
+
for stmt in &statements.body() {
|
|
32
|
+
installer.install_node(&stmt);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
installer.finish();
|
|
37
|
+
|
|
38
|
+
(genv, lenv)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
#[test]
|
|
42
|
+
fn test_class_method_error_detection() {
|
|
43
|
+
let source = r#"
|
|
44
|
+
class User
|
|
45
|
+
def test
|
|
46
|
+
x = 123
|
|
47
|
+
y = x.upcase
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
"#;
|
|
51
|
+
|
|
52
|
+
let (genv, _lenv) = analyze(source);
|
|
53
|
+
|
|
54
|
+
// Type error should be detected: Integer doesn't have upcase method
|
|
55
|
+
assert_eq!(genv.type_errors.len(), 1);
|
|
56
|
+
assert_eq!(genv.type_errors[0].method_name, "upcase");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
#[test]
|
|
60
|
+
fn test_class_with_instance_variable() {
|
|
61
|
+
let source = r#"
|
|
62
|
+
class User
|
|
63
|
+
def initialize
|
|
64
|
+
@name = "John"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def greet
|
|
68
|
+
@name.upcase
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
"#;
|
|
72
|
+
|
|
73
|
+
let (genv, _lenv) = analyze(source);
|
|
74
|
+
|
|
75
|
+
// No type errors should occur - @name is String
|
|
76
|
+
assert_eq!(genv.type_errors.len(), 0);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
#[test]
|
|
80
|
+
fn test_instance_variable_type_error() {
|
|
81
|
+
let source = r#"
|
|
82
|
+
class User
|
|
83
|
+
def initialize
|
|
84
|
+
@name = 123
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def greet
|
|
88
|
+
@name.upcase
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
"#;
|
|
92
|
+
|
|
93
|
+
let (genv, _lenv) = analyze(source);
|
|
94
|
+
|
|
95
|
+
// Type error should be detected: @name is Integer, not String
|
|
96
|
+
assert_eq!(genv.type_errors.len(), 1);
|
|
97
|
+
assert_eq!(genv.type_errors[0].method_name, "upcase");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
#[test]
|
|
101
|
+
fn test_multiple_classes() {
|
|
102
|
+
let source = r#"
|
|
103
|
+
class User
|
|
104
|
+
def name
|
|
105
|
+
x = 123
|
|
106
|
+
x.upcase
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
class Post
|
|
111
|
+
def title
|
|
112
|
+
y = "hello"
|
|
113
|
+
y.upcase
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
"#;
|
|
117
|
+
|
|
118
|
+
let (genv, _lenv) = analyze(source);
|
|
119
|
+
|
|
120
|
+
// Only User#name should have error (Integer#upcase), Post#title is fine
|
|
121
|
+
assert_eq!(genv.type_errors.len(), 1);
|
|
122
|
+
assert_eq!(genv.type_errors[0].method_name, "upcase");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
#[test]
|
|
126
|
+
fn test_method_chain() {
|
|
127
|
+
let source = r#"
|
|
128
|
+
x = "hello"
|
|
129
|
+
y = x.upcase.downcase
|
|
130
|
+
"#;
|
|
131
|
+
|
|
132
|
+
let (genv, lenv) = analyze(source);
|
|
133
|
+
|
|
134
|
+
let y_vtx = lenv.get_var("y").unwrap();
|
|
135
|
+
assert_eq!(genv.get_vertex(y_vtx).unwrap().show(), "String");
|
|
136
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
mod integration_test;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
//! Variable Handlers - Processing Ruby variables
|
|
2
|
+
//!
|
|
3
|
+
//! This module is responsible for:
|
|
4
|
+
//! - Local variable read/write (x, x = value)
|
|
5
|
+
//! - Instance variable read/write (@name, @name = value)
|
|
6
|
+
//! - self node handling
|
|
7
|
+
|
|
8
|
+
use crate::env::{GlobalEnv, LocalEnv};
|
|
9
|
+
use crate::graph::{ChangeSet, VertexId};
|
|
10
|
+
use crate::types::Type;
|
|
11
|
+
|
|
12
|
+
/// Install local variable write: x = value
|
|
13
|
+
pub fn install_local_var_write(
|
|
14
|
+
genv: &mut GlobalEnv,
|
|
15
|
+
lenv: &mut LocalEnv,
|
|
16
|
+
changes: &mut ChangeSet,
|
|
17
|
+
var_name: String,
|
|
18
|
+
value_vtx: VertexId,
|
|
19
|
+
) -> VertexId {
|
|
20
|
+
let var_vtx = genv.new_vertex();
|
|
21
|
+
lenv.new_var(var_name, var_vtx);
|
|
22
|
+
changes.add_edge(value_vtx, var_vtx);
|
|
23
|
+
var_vtx
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/// Install local variable read: x
|
|
27
|
+
pub fn install_local_var_read(lenv: &LocalEnv, var_name: &str) -> Option<VertexId> {
|
|
28
|
+
lenv.get_var(var_name)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/// Install instance variable write: @name = value
|
|
32
|
+
pub fn install_ivar_write(
|
|
33
|
+
genv: &mut GlobalEnv,
|
|
34
|
+
ivar_name: String,
|
|
35
|
+
value_vtx: VertexId,
|
|
36
|
+
) -> VertexId {
|
|
37
|
+
genv.scope_manager
|
|
38
|
+
.set_instance_var_in_class(ivar_name, value_vtx);
|
|
39
|
+
value_vtx
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/// Install instance variable read: @name
|
|
43
|
+
pub fn install_ivar_read(genv: &GlobalEnv, ivar_name: &str) -> Option<VertexId> {
|
|
44
|
+
genv.scope_manager.lookup_instance_var(ivar_name)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/// Install self node
|
|
48
|
+
pub fn install_self(genv: &mut GlobalEnv) -> VertexId {
|
|
49
|
+
if let Some(class_name) = genv.scope_manager.current_class_name() {
|
|
50
|
+
genv.new_source(Type::Instance { class_name })
|
|
51
|
+
} else {
|
|
52
|
+
genv.new_source(Type::Instance {
|
|
53
|
+
class_name: "Object".to_string(),
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
#[cfg(test)]
|
|
59
|
+
mod tests {
|
|
60
|
+
use super::*;
|
|
61
|
+
|
|
62
|
+
#[test]
|
|
63
|
+
fn test_install_self_at_top_level() {
|
|
64
|
+
let mut genv = GlobalEnv::new();
|
|
65
|
+
|
|
66
|
+
let vtx = install_self(&mut genv);
|
|
67
|
+
assert_eq!(genv.get_source(vtx).unwrap().ty.show(), "Object");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
#[test]
|
|
71
|
+
fn test_local_var_read_not_found() {
|
|
72
|
+
let lenv = LocalEnv::new();
|
|
73
|
+
|
|
74
|
+
assert_eq!(install_local_var_read(&lenv, "unknown"), None);
|
|
75
|
+
}
|
|
76
|
+
}
|