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.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +23 -0
  3. data/LICENSE +21 -0
  4. data/README.md +39 -0
  5. data/exe/methodray +7 -0
  6. data/ext/Cargo.toml +24 -0
  7. data/ext/extconf.rb +40 -0
  8. data/ext/src/cli.rs +33 -0
  9. data/ext/src/lib.rs +79 -0
  10. data/lib/methodray/cli.rb +28 -0
  11. data/lib/methodray/commands.rb +78 -0
  12. data/lib/methodray/version.rb +5 -0
  13. data/lib/methodray.rb +9 -0
  14. data/rust/Cargo.toml +39 -0
  15. data/rust/src/analyzer/calls.rs +56 -0
  16. data/rust/src/analyzer/definitions.rs +70 -0
  17. data/rust/src/analyzer/dispatch.rs +134 -0
  18. data/rust/src/analyzer/install.rs +226 -0
  19. data/rust/src/analyzer/literals.rs +85 -0
  20. data/rust/src/analyzer/mod.rs +11 -0
  21. data/rust/src/analyzer/tests/integration_test.rs +136 -0
  22. data/rust/src/analyzer/tests/mod.rs +1 -0
  23. data/rust/src/analyzer/variables.rs +76 -0
  24. data/rust/src/cache/mod.rs +3 -0
  25. data/rust/src/cache/rbs_cache.rs +158 -0
  26. data/rust/src/checker.rs +139 -0
  27. data/rust/src/cli/args.rs +40 -0
  28. data/rust/src/cli/commands.rs +139 -0
  29. data/rust/src/cli/mod.rs +6 -0
  30. data/rust/src/diagnostics/diagnostic.rs +125 -0
  31. data/rust/src/diagnostics/formatter.rs +119 -0
  32. data/rust/src/diagnostics/mod.rs +5 -0
  33. data/rust/src/env/box_manager.rs +121 -0
  34. data/rust/src/env/global_env.rs +279 -0
  35. data/rust/src/env/local_env.rs +58 -0
  36. data/rust/src/env/method_registry.rs +63 -0
  37. data/rust/src/env/mod.rs +15 -0
  38. data/rust/src/env/scope.rs +330 -0
  39. data/rust/src/env/type_error.rs +23 -0
  40. data/rust/src/env/vertex_manager.rs +195 -0
  41. data/rust/src/graph/box.rs +157 -0
  42. data/rust/src/graph/change_set.rs +115 -0
  43. data/rust/src/graph/mod.rs +7 -0
  44. data/rust/src/graph/vertex.rs +167 -0
  45. data/rust/src/lib.rs +24 -0
  46. data/rust/src/lsp/diagnostics.rs +133 -0
  47. data/rust/src/lsp/main.rs +8 -0
  48. data/rust/src/lsp/mod.rs +4 -0
  49. data/rust/src/lsp/server.rs +138 -0
  50. data/rust/src/main.rs +46 -0
  51. data/rust/src/parser.rs +96 -0
  52. data/rust/src/rbs/converter.rs +82 -0
  53. data/rust/src/rbs/error.rs +37 -0
  54. data/rust/src/rbs/loader.rs +183 -0
  55. data/rust/src/rbs/mod.rs +15 -0
  56. data/rust/src/source_map.rs +102 -0
  57. data/rust/src/types.rs +75 -0
  58. 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,11 @@
1
+ mod calls;
2
+ mod definitions;
3
+ mod dispatch;
4
+ mod install;
5
+ mod literals;
6
+ mod variables;
7
+
8
+ #[cfg(test)]
9
+ mod tests;
10
+
11
+ pub use install::AstInstaller;
@@ -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
+ }
@@ -0,0 +1,3 @@
1
+ pub mod rbs_cache;
2
+
3
+ pub use rbs_cache::RbsCache;