method-ray 0.1.8 → 0.1.10

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 (74) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +38 -0
  3. data/README.md +9 -11
  4. data/{rust → core}/Cargo.toml +1 -1
  5. data/core/src/analyzer/assignments.rs +219 -0
  6. data/{rust → core}/src/analyzer/blocks.rs +0 -50
  7. data/{rust → core}/src/analyzer/calls.rs +3 -32
  8. data/core/src/analyzer/conditionals.rs +190 -0
  9. data/core/src/analyzer/definitions.rs +205 -0
  10. data/core/src/analyzer/dispatch.rs +455 -0
  11. data/core/src/analyzer/exceptions.rs +168 -0
  12. data/{rust → core}/src/analyzer/install.rs +16 -1
  13. data/{rust → core}/src/analyzer/literals.rs +3 -71
  14. data/core/src/analyzer/loops.rs +94 -0
  15. data/{rust → core}/src/analyzer/mod.rs +1 -15
  16. data/core/src/analyzer/operators.rs +79 -0
  17. data/{rust → core}/src/analyzer/parameters.rs +4 -67
  18. data/core/src/analyzer/parentheses.rs +25 -0
  19. data/core/src/analyzer/returns.rs +39 -0
  20. data/core/src/analyzer/super_calls.rs +74 -0
  21. data/{rust → core}/src/analyzer/variables.rs +5 -25
  22. data/{rust → core}/src/checker.rs +0 -13
  23. data/{rust → core}/src/diagnostics/diagnostic.rs +0 -41
  24. data/{rust → core}/src/diagnostics/formatter.rs +0 -38
  25. data/{rust → core}/src/env/box_manager.rs +0 -30
  26. data/{rust → core}/src/env/global_env.rs +67 -80
  27. data/core/src/env/local_env.rs +42 -0
  28. data/core/src/env/method_registry.rs +173 -0
  29. data/core/src/env/scope.rs +299 -0
  30. data/{rust → core}/src/env/vertex_manager.rs +0 -73
  31. data/core/src/graph/box.rs +347 -0
  32. data/{rust → core}/src/graph/change_set.rs +0 -65
  33. data/{rust → core}/src/graph/vertex.rs +0 -69
  34. data/{rust → core}/src/parser.rs +0 -77
  35. data/{rust → core}/src/types.rs +11 -0
  36. data/ext/Cargo.toml +2 -2
  37. data/lib/methodray/binary_locator.rb +2 -2
  38. data/lib/methodray/commands.rb +1 -1
  39. data/lib/methodray/version.rb +1 -1
  40. metadata +58 -56
  41. data/rust/src/analyzer/assignments.rs +0 -152
  42. data/rust/src/analyzer/conditionals.rs +0 -538
  43. data/rust/src/analyzer/definitions.rs +0 -719
  44. data/rust/src/analyzer/dispatch.rs +0 -1137
  45. data/rust/src/analyzer/exceptions.rs +0 -521
  46. data/rust/src/analyzer/loops.rs +0 -176
  47. data/rust/src/analyzer/operators.rs +0 -284
  48. data/rust/src/analyzer/parentheses.rs +0 -113
  49. data/rust/src/analyzer/returns.rs +0 -191
  50. data/rust/src/env/local_env.rs +0 -92
  51. data/rust/src/env/method_registry.rs +0 -268
  52. data/rust/src/env/scope.rs +0 -596
  53. data/rust/src/graph/box.rs +0 -766
  54. /data/{rust → core}/src/analyzer/attributes.rs +0 -0
  55. /data/{rust → core}/src/cache/mod.rs +0 -0
  56. /data/{rust → core}/src/cache/rbs_cache.rs +0 -0
  57. /data/{rust → core}/src/cli/args.rs +0 -0
  58. /data/{rust → core}/src/cli/commands.rs +0 -0
  59. /data/{rust → core}/src/cli/mod.rs +0 -0
  60. /data/{rust → core}/src/diagnostics/mod.rs +0 -0
  61. /data/{rust → core}/src/env/mod.rs +0 -0
  62. /data/{rust → core}/src/env/type_error.rs +0 -0
  63. /data/{rust → core}/src/graph/mod.rs +0 -0
  64. /data/{rust → core}/src/lib.rs +0 -0
  65. /data/{rust → core}/src/lsp/diagnostics.rs +0 -0
  66. /data/{rust → core}/src/lsp/main.rs +0 -0
  67. /data/{rust → core}/src/lsp/mod.rs +0 -0
  68. /data/{rust → core}/src/lsp/server.rs +0 -0
  69. /data/{rust → core}/src/main.rs +0 -0
  70. /data/{rust → core}/src/rbs/converter.rs +0 -0
  71. /data/{rust → core}/src/rbs/error.rs +0 -0
  72. /data/{rust → core}/src/rbs/loader.rs +0 -0
  73. /data/{rust → core}/src/rbs/mod.rs +0 -0
  74. /data/{rust → core}/src/source_map.rs +0 -0
@@ -1,284 +0,0 @@
1
- //! Operators - logical operator type inference (&&, ||, !)
2
-
3
- use crate::env::{GlobalEnv, LocalEnv};
4
- use crate::graph::{ChangeSet, VertexId};
5
- use crate::types::Type;
6
- use ruby_prism::{AndNode, Node, OrNode};
7
-
8
- use super::install::install_node;
9
-
10
- /// Merge two branch nodes into a union type vertex.
11
- fn process_binary_logical_op<'a>(
12
- genv: &mut GlobalEnv,
13
- lenv: &mut LocalEnv,
14
- changes: &mut ChangeSet,
15
- source: &str,
16
- left: Node<'a>,
17
- right: Node<'a>,
18
- ) -> Option<VertexId> {
19
- let result_vtx = genv.new_vertex();
20
-
21
- if let Some(vtx) = install_node(genv, lenv, changes, source, &left) {
22
- genv.add_edge(vtx, result_vtx);
23
- }
24
-
25
- if let Some(vtx) = install_node(genv, lenv, changes, source, &right) {
26
- genv.add_edge(vtx, result_vtx);
27
- }
28
-
29
- Some(result_vtx)
30
- }
31
-
32
- /// Process AndNode (a && b): Union(type(a), type(b))
33
- ///
34
- /// Runtime: if `a` is falsy, returns `a`; otherwise returns `b`.
35
- /// Static: conservatively produce Union(type(a), type(b)).
36
- pub(crate) fn process_and_node(
37
- genv: &mut GlobalEnv,
38
- lenv: &mut LocalEnv,
39
- changes: &mut ChangeSet,
40
- source: &str,
41
- and_node: &AndNode,
42
- ) -> Option<VertexId> {
43
- process_binary_logical_op(genv, lenv, changes, source, and_node.left(), and_node.right())
44
- }
45
-
46
- /// Process OrNode (a || b): Union(type(a), type(b))
47
- ///
48
- /// Runtime: if `a` is truthy, returns `a`; otherwise returns `b`.
49
- /// Static: conservatively produce Union(type(a), type(b)).
50
- pub(crate) fn process_or_node(
51
- genv: &mut GlobalEnv,
52
- lenv: &mut LocalEnv,
53
- changes: &mut ChangeSet,
54
- source: &str,
55
- or_node: &OrNode,
56
- ) -> Option<VertexId> {
57
- process_binary_logical_op(genv, lenv, changes, source, or_node.left(), or_node.right())
58
- }
59
-
60
- /// Process not operator (!expr): TrueClass | FalseClass
61
- ///
62
- /// In ruby-prism, `!expr` is represented as a CallNode with method name "!".
63
- /// Static approximation: we cannot determine the receiver's truthiness at
64
- /// compile time, so conservatively return TrueClass | FalseClass for any `!` call.
65
- /// In practice, `!nil` and `!false` are always true, but we do not track that here.
66
- ///
67
- /// Receiver side effects are already analyzed by the caller (process_needs_child).
68
- ///
69
- /// TODO: Ruby allows overriding `BasicObject#!`. Currently we always return
70
- /// TrueClass | FalseClass, ignoring user-defined `!` methods. If needed, look up
71
- /// the receiver's RBS definition and use its return type instead.
72
- pub(crate) fn process_not_operator(genv: &mut GlobalEnv) -> VertexId {
73
- let result_vtx = genv.new_vertex();
74
- let true_vtx = genv.new_source(Type::instance("TrueClass"));
75
- let false_vtx = genv.new_source(Type::instance("FalseClass"));
76
- genv.add_edge(true_vtx, result_vtx);
77
- genv.add_edge(false_vtx, result_vtx);
78
- result_vtx
79
- }
80
-
81
- #[cfg(test)]
82
- mod tests {
83
- use crate::analyzer::install::AstInstaller;
84
- use crate::env::{GlobalEnv, LocalEnv};
85
- use crate::graph::VertexId;
86
- use crate::parser::ParseSession;
87
- use crate::types::Type;
88
-
89
- /// Helper: parse Ruby source, process with AstInstaller, and return GlobalEnv
90
- fn analyze(source: &str) -> GlobalEnv {
91
- let session = ParseSession::new();
92
- let parse_result = session.parse_source(source, "test.rb").unwrap();
93
- let root = parse_result.node();
94
- let program = root.as_program_node().unwrap();
95
-
96
- let mut genv = GlobalEnv::new();
97
- let mut lenv = LocalEnv::new();
98
-
99
- let mut installer = AstInstaller::new(&mut genv, &mut lenv, source);
100
- for stmt in &program.statements().body() {
101
- installer.install_node(&stmt);
102
- }
103
- installer.finish();
104
-
105
- genv
106
- }
107
-
108
- /// Helper: get the type string for a vertex ID
109
- fn get_type_show(genv: &GlobalEnv, vtx: VertexId) -> String {
110
- if let Some(vertex) = genv.get_vertex(vtx) {
111
- vertex.show()
112
- } else if let Some(source) = genv.get_source(vtx) {
113
- source.ty.show()
114
- } else {
115
- panic!("vertex {:?} not found as either Vertex or Source", vtx);
116
- }
117
- }
118
-
119
- #[test]
120
- fn test_and_node_union_type() {
121
- let source = r#"
122
- class Foo
123
- def bar
124
- true && "hello"
125
- end
126
- end
127
- "#;
128
- let genv = analyze(source);
129
- let info = genv
130
- .resolve_method(&Type::instance("Foo"), "bar")
131
- .expect("Foo#bar should be registered");
132
- let ret_vtx = info.return_vertex.unwrap();
133
- let type_str = get_type_show(&genv, ret_vtx);
134
- assert!(type_str.contains("TrueClass"), "should contain TrueClass: {}", type_str);
135
- assert!(type_str.contains("String"), "should contain String: {}", type_str);
136
- }
137
-
138
- #[test]
139
- fn test_and_node_same_type() {
140
- let source = r#"
141
- class Foo
142
- def bar
143
- "a" && "b"
144
- end
145
- end
146
- "#;
147
- let genv = analyze(source);
148
- let info = genv
149
- .resolve_method(&Type::instance("Foo"), "bar")
150
- .expect("Foo#bar should be registered");
151
- let ret_vtx = info.return_vertex.unwrap();
152
- assert_eq!(get_type_show(&genv, ret_vtx), "String");
153
- }
154
-
155
- #[test]
156
- fn test_or_node_union_type() {
157
- let source = r#"
158
- class Foo
159
- def bar
160
- 42 || "hello"
161
- end
162
- end
163
- "#;
164
- let genv = analyze(source);
165
- let info = genv
166
- .resolve_method(&Type::instance("Foo"), "bar")
167
- .expect("Foo#bar should be registered");
168
- let ret_vtx = info.return_vertex.unwrap();
169
- let type_str = get_type_show(&genv, ret_vtx);
170
- assert!(type_str.contains("Integer"), "should contain Integer: {}", type_str);
171
- assert!(type_str.contains("String"), "should contain String: {}", type_str);
172
- }
173
-
174
- #[test]
175
- fn test_or_node_same_type() {
176
- let source = r#"
177
- class Foo
178
- def bar
179
- 1 || 2
180
- end
181
- end
182
- "#;
183
- let genv = analyze(source);
184
- let info = genv
185
- .resolve_method(&Type::instance("Foo"), "bar")
186
- .expect("Foo#bar should be registered");
187
- let ret_vtx = info.return_vertex.unwrap();
188
- assert_eq!(get_type_show(&genv, ret_vtx), "Integer");
189
- }
190
-
191
- #[test]
192
- fn test_nested_logical_operators() {
193
- let source = r#"
194
- class Foo
195
- def bar
196
- 1 && "a" || :b
197
- end
198
- end
199
- "#;
200
- let genv = analyze(source);
201
- let info = genv
202
- .resolve_method(&Type::instance("Foo"), "bar")
203
- .expect("Foo#bar should be registered");
204
- let ret_vtx = info.return_vertex.unwrap();
205
- let type_str = get_type_show(&genv, ret_vtx);
206
- assert!(type_str.contains("Integer"), "should contain Integer: {}", type_str);
207
- assert!(type_str.contains("String"), "should contain String: {}", type_str);
208
- assert!(type_str.contains("Symbol"), "should contain Symbol: {}", type_str);
209
- }
210
-
211
- // ============================================
212
- // Not operator (!) tests
213
- // ============================================
214
-
215
- #[test]
216
- fn test_not_operator_returns_boolean_union() {
217
- let source = r#"
218
- class Foo
219
- def bar
220
- !true
221
- end
222
- end
223
- "#;
224
- let genv = analyze(source);
225
- let info = genv
226
- .resolve_method(&Type::instance("Foo"), "bar")
227
- .expect("bar should be registered");
228
- let ty = get_type_show(&genv, info.return_vertex.unwrap());
229
- assert!(ty.contains("TrueClass"), "expected TrueClass in {}", ty);
230
- assert!(ty.contains("FalseClass"), "expected FalseClass in {}", ty);
231
- }
232
-
233
- #[test]
234
- fn test_not_operator_receiver_side_effects_analyzed() {
235
- let source = r#"
236
- class Foo
237
- def bar
238
- !(1.upcase)
239
- end
240
- end
241
- "#;
242
- let genv = analyze(source);
243
- assert!(
244
- !genv.type_errors.is_empty(),
245
- "expected type error for Integer#upcase"
246
- );
247
- }
248
-
249
- #[test]
250
- fn test_double_not_operator_union() {
251
- let source = r#"
252
- class Foo
253
- def bar
254
- !!true
255
- end
256
- end
257
- "#;
258
- let genv = analyze(source);
259
- let info = genv
260
- .resolve_method(&Type::instance("Foo"), "bar")
261
- .expect("bar should be registered");
262
- let ty = get_type_show(&genv, info.return_vertex.unwrap());
263
- assert!(ty.contains("TrueClass"), "expected TrueClass in {}", ty);
264
- assert!(ty.contains("FalseClass"), "expected FalseClass in {}", ty);
265
- }
266
-
267
- #[test]
268
- fn test_not_nil_returns_boolean() {
269
- let source = r#"
270
- class Foo
271
- def bar
272
- !nil
273
- end
274
- end
275
- "#;
276
- let genv = analyze(source);
277
- let info = genv
278
- .resolve_method(&Type::instance("Foo"), "bar")
279
- .expect("bar should be registered");
280
- let ty = get_type_show(&genv, info.return_vertex.unwrap());
281
- assert!(ty.contains("TrueClass"), "expected TrueClass in {}", ty);
282
- assert!(ty.contains("FalseClass"), "expected FalseClass in {}", ty);
283
- }
284
- }
@@ -1,113 +0,0 @@
1
- //! Parentheses - pass-through type propagation for parenthesized expressions
2
-
3
- use crate::env::{GlobalEnv, LocalEnv};
4
- use crate::graph::{ChangeSet, VertexId};
5
-
6
- use super::install::{install_node, install_statements};
7
-
8
- /// Process ParenthesesNode: propagate inner expression's type
9
- pub(crate) fn process_parentheses_node(
10
- genv: &mut GlobalEnv,
11
- lenv: &mut LocalEnv,
12
- changes: &mut ChangeSet,
13
- source: &str,
14
- paren_node: &ruby_prism::ParenthesesNode,
15
- ) -> Option<VertexId> {
16
- let body = paren_node.body()?;
17
-
18
- if let Some(stmts) = body.as_statements_node() {
19
- // (expr1; expr2) → process all, return last expression's type
20
- install_statements(genv, lenv, changes, source, &stmts)
21
- } else {
22
- // (expr) → propagate inner expression's type directly
23
- install_node(genv, lenv, changes, source, &body)
24
- }
25
- }
26
-
27
- #[cfg(test)]
28
- mod tests {
29
- use crate::analyzer::install::AstInstaller;
30
- use crate::env::{GlobalEnv, LocalEnv};
31
- use crate::graph::VertexId;
32
- use crate::parser::ParseSession;
33
- use crate::types::Type;
34
-
35
- fn analyze(source: &str) -> GlobalEnv {
36
- let session = ParseSession::new();
37
- let parse_result = session.parse_source(source, "test.rb").unwrap();
38
- let root = parse_result.node();
39
- let program = root.as_program_node().unwrap();
40
-
41
- let mut genv = GlobalEnv::new();
42
- let mut lenv = LocalEnv::new();
43
-
44
- let mut installer = AstInstaller::new(&mut genv, &mut lenv, source);
45
- for stmt in &program.statements().body() {
46
- installer.install_node(&stmt);
47
- }
48
- installer.finish();
49
-
50
- genv
51
- }
52
-
53
- fn get_type_show(genv: &GlobalEnv, vtx: VertexId) -> String {
54
- if let Some(vertex) = genv.get_vertex(vtx) {
55
- vertex.show()
56
- } else if let Some(source) = genv.get_source(vtx) {
57
- source.ty.show()
58
- } else {
59
- panic!("vertex {:?} not found as either Vertex or Source", vtx);
60
- }
61
- }
62
-
63
- #[test]
64
- fn test_parenthesized_integer() {
65
- let source = r#"
66
- class Foo
67
- def bar
68
- x = (42)
69
- end
70
- end
71
- "#;
72
- let genv = analyze(source);
73
- let info = genv
74
- .resolve_method(&Type::instance("Foo"), "bar")
75
- .expect("Foo#bar should be registered");
76
- let ret_vtx = info.return_vertex.unwrap();
77
- assert_eq!(get_type_show(&genv, ret_vtx), "Integer");
78
- }
79
-
80
- #[test]
81
- fn test_parenthesized_string() {
82
- let source = r#"
83
- class Foo
84
- def bar
85
- x = ("hello")
86
- end
87
- end
88
- "#;
89
- let genv = analyze(source);
90
- let info = genv
91
- .resolve_method(&Type::instance("Foo"), "bar")
92
- .expect("Foo#bar should be registered");
93
- let ret_vtx = info.return_vertex.unwrap();
94
- assert_eq!(get_type_show(&genv, ret_vtx), "String");
95
- }
96
-
97
- #[test]
98
- fn test_parenthesized_multiple_statements() {
99
- let source = r#"
100
- class Foo
101
- def bar
102
- x = (a = 1; "hello")
103
- end
104
- end
105
- "#;
106
- let genv = analyze(source);
107
- let info = genv
108
- .resolve_method(&Type::instance("Foo"), "bar")
109
- .expect("Foo#bar should be registered");
110
- let ret_vtx = info.return_vertex.unwrap();
111
- assert_eq!(get_type_show(&genv, ret_vtx), "String");
112
- }
113
- }
@@ -1,191 +0,0 @@
1
- //! Return statement handling
2
- //!
3
- //! Processes `return expr` by connecting the expression's vertex
4
- //! to the enclosing method's merge vertex.
5
-
6
- use crate::env::{GlobalEnv, LocalEnv};
7
- use crate::graph::{ChangeSet, VertexId};
8
-
9
- use super::install::install_node;
10
-
11
- /// Process ReturnNode: connect return value to method's merge vertex
12
- pub(crate) fn process_return_node(
13
- genv: &mut GlobalEnv,
14
- lenv: &mut LocalEnv,
15
- changes: &mut ChangeSet,
16
- source: &str,
17
- return_node: &ruby_prism::ReturnNode,
18
- ) -> Option<VertexId> {
19
- // Process return value (first argument only; multi-value return not yet supported)
20
- let value_vtx = if let Some(arguments) = return_node.arguments() {
21
- arguments
22
- .arguments()
23
- .iter()
24
- .next()
25
- .and_then(|arg| install_node(genv, lenv, changes, source, &arg))
26
- } else {
27
- // `return` without value → nil
28
- Some(genv.new_source(crate::types::Type::Nil))
29
- };
30
-
31
- // Connect return value to method's merge vertex
32
- if let Some(vtx) = value_vtx {
33
- if let Some(merge_vtx) = genv.scope_manager.current_method_return_vertex() {
34
- genv.add_edge(vtx, merge_vtx);
35
- }
36
- }
37
-
38
- None
39
- }
40
-
41
- #[cfg(test)]
42
- mod tests {
43
- use crate::env::{GlobalEnv, LocalEnv};
44
- use crate::graph::ChangeSet;
45
- use crate::parser::ParseSession;
46
- use crate::types::Type;
47
-
48
- fn setup_and_infer(source: &str) -> GlobalEnv {
49
- let session = ParseSession::new();
50
- let parse_result = session.parse_source(source, "test.rb").unwrap();
51
- let root = parse_result.node();
52
- let program = root.as_program_node().unwrap();
53
-
54
- let mut genv = GlobalEnv::new();
55
- let mut lenv = LocalEnv::new();
56
- let mut changes = ChangeSet::new();
57
-
58
- for stmt in &program.statements().body() {
59
- crate::analyzer::install::install_node(
60
- &mut genv, &mut lenv, &mut changes, source, &stmt,
61
- );
62
- }
63
-
64
- genv.apply_changes(changes);
65
- genv.run_all();
66
- genv
67
- }
68
-
69
- fn get_return_type(genv: &GlobalEnv, class_name: &str, method_name: &str) -> String {
70
- let info = genv
71
- .resolve_method(&Type::instance(class_name), method_name)
72
- .unwrap_or_else(|| panic!("{}#{} should be registered", class_name, method_name));
73
- let vtx = info
74
- .return_vertex
75
- .expect("return_vertex should be Some");
76
-
77
- if let Some(source) = genv.get_source(vtx) {
78
- source.ty.show()
79
- } else if let Some(vertex) = genv.get_vertex(vtx) {
80
- vertex.show()
81
- } else {
82
- panic!("return_vertex not found");
83
- }
84
- }
85
-
86
- #[test]
87
- fn test_simple_return() {
88
- let source = r#"
89
- class Foo
90
- def bar
91
- return "hello"
92
- end
93
- end
94
- "#;
95
- let genv = setup_and_infer(source);
96
- assert_eq!(get_return_type(&genv, "Foo", "bar"), "String");
97
- }
98
-
99
- #[test]
100
- fn test_return_with_implicit_return_union() {
101
- let source = r#"
102
- class Foo
103
- def bar
104
- return "hello" if true
105
- 42
106
- end
107
- end
108
- "#;
109
- let genv = setup_and_infer(source);
110
- let ty = get_return_type(&genv, "Foo", "bar");
111
- assert!(ty.contains("Integer"), "should contain Integer, got: {}", ty);
112
- assert!(ty.contains("String"), "should contain String, got: {}", ty);
113
- }
114
-
115
- #[test]
116
- fn test_multiple_returns() {
117
- let source = r#"
118
- class Foo
119
- def bar
120
- return "a" if true
121
- return :b if false
122
- 42
123
- end
124
- end
125
- "#;
126
- let genv = setup_and_infer(source);
127
- let ty = get_return_type(&genv, "Foo", "bar");
128
- assert!(ty.contains("Integer"), "should contain Integer, got: {}", ty);
129
- assert!(ty.contains("String"), "should contain String, got: {}", ty);
130
- assert!(ty.contains("Symbol"), "should contain Symbol, got: {}", ty);
131
- }
132
-
133
- #[test]
134
- fn test_return_without_value() {
135
- let source = r#"
136
- class Foo
137
- def bar
138
- return if true
139
- 42
140
- end
141
- end
142
- "#;
143
- let genv = setup_and_infer(source);
144
- let ty = get_return_type(&genv, "Foo", "bar");
145
- assert!(ty.contains("Integer"), "should contain Integer, got: {}", ty);
146
- assert!(ty.contains("nil"), "should contain nil, got: {}", ty);
147
- }
148
-
149
- #[test]
150
- fn test_no_return_backward_compat() {
151
- let source = r#"
152
- class Foo
153
- def bar
154
- "hello"
155
- end
156
- end
157
- "#;
158
- let genv = setup_and_infer(source);
159
- assert_eq!(get_return_type(&genv, "Foo", "bar"), "String");
160
- }
161
-
162
- #[test]
163
- fn test_return_only_method() {
164
- let source = r#"
165
- class Foo
166
- def bar
167
- return "hello"
168
- end
169
- end
170
- "#;
171
- let genv = setup_and_infer(source);
172
- assert_eq!(get_return_type(&genv, "Foo", "bar"), "String");
173
- }
174
-
175
- #[test]
176
- fn test_return_dead_code_over_approximation() {
177
- let source = r#"
178
- class Foo
179
- def bar
180
- return "hello"
181
- 42
182
- end
183
- end
184
- "#;
185
- let genv = setup_and_infer(source);
186
- let ty = get_return_type(&genv, "Foo", "bar");
187
- // Dead code after return is still processed (over-approximation)
188
- assert!(ty.contains("Integer"), "should contain Integer (dead code), got: {}", ty);
189
- assert!(ty.contains("String"), "should contain String, got: {}", ty);
190
- }
191
- }
@@ -1,92 +0,0 @@
1
- use crate::graph::VertexId;
2
- use std::collections::HashMap;
3
-
4
- /// Local environment: mapping of local variable names to VertexIDs
5
- pub struct LocalEnv {
6
- locals: HashMap<String, VertexId>,
7
- }
8
-
9
- impl Default for LocalEnv {
10
- fn default() -> Self {
11
- Self::new()
12
- }
13
- }
14
-
15
- impl LocalEnv {
16
- pub fn new() -> Self {
17
- Self {
18
- locals: HashMap::new(),
19
- }
20
- }
21
-
22
- /// Register variable
23
- pub fn new_var(&mut self, name: String, vtx_id: VertexId) {
24
- self.locals.insert(name, vtx_id);
25
- }
26
-
27
- /// Get variable
28
- pub fn get_var(&self, name: &str) -> Option<VertexId> {
29
- self.locals.get(name).copied()
30
- }
31
-
32
- /// Remove a variable from the local environment.
33
- /// Used for scoped variables like rescue's `=> e` binding.
34
- pub fn remove_var(&mut self, name: &str) {
35
- self.locals.remove(name);
36
- }
37
-
38
- /// Get all variables
39
- pub fn all_vars(&self) -> impl Iterator<Item = (&String, &VertexId)> {
40
- self.locals.iter()
41
- }
42
- }
43
-
44
- #[cfg(test)]
45
- mod tests {
46
- use super::*;
47
-
48
- #[test]
49
- fn test_local_env_default() {
50
- let lenv = LocalEnv::default();
51
- assert_eq!(lenv.get_var("x"), None);
52
- }
53
-
54
- #[test]
55
- fn test_local_env() {
56
- let mut lenv = LocalEnv::new();
57
-
58
- lenv.new_var("x".to_string(), VertexId(1));
59
- lenv.new_var("y".to_string(), VertexId(2));
60
-
61
- assert_eq!(lenv.get_var("x"), Some(VertexId(1)));
62
- assert_eq!(lenv.get_var("y"), Some(VertexId(2)));
63
- assert_eq!(lenv.get_var("z"), None);
64
- }
65
-
66
- #[test]
67
- fn test_local_env_remove_var() {
68
- let mut lenv = LocalEnv::new();
69
-
70
- lenv.new_var("e".to_string(), VertexId(1));
71
- assert_eq!(lenv.get_var("e"), Some(VertexId(1)));
72
-
73
- lenv.remove_var("e");
74
- assert_eq!(lenv.get_var("e"), None);
75
- }
76
-
77
- #[test]
78
- fn test_local_env_remove_nonexistent() {
79
- let mut lenv = LocalEnv::new();
80
- lenv.remove_var("x"); // should not panic
81
- }
82
-
83
- #[test]
84
- fn test_local_env_override() {
85
- let mut lenv = LocalEnv::new();
86
-
87
- lenv.new_var("x".to_string(), VertexId(1));
88
- lenv.new_var("x".to_string(), VertexId(2)); // Override
89
-
90
- assert_eq!(lenv.get_var("x"), Some(VertexId(2)));
91
- }
92
- }