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
@@ -0,0 +1,347 @@
1
+ use std::collections::HashMap;
2
+
3
+ use crate::env::GlobalEnv;
4
+ use crate::graph::change_set::ChangeSet;
5
+ use crate::graph::vertex::VertexId;
6
+ use crate::source_map::SourceLocation;
7
+ use crate::types::Type;
8
+
9
+ /// Unique ID for Box
10
+ #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
11
+ pub struct BoxId(pub usize);
12
+
13
+ /// Box trait: represents constraints such as method calls
14
+ pub trait BoxTrait: Send + Sync {
15
+ fn id(&self) -> BoxId;
16
+ fn run(&mut self, genv: &mut GlobalEnv, changes: &mut ChangeSet);
17
+ fn ret(&self) -> VertexId;
18
+ }
19
+
20
+ /// Propagate argument types to parameter vertices by adding edges
21
+ /// from each argument vertex to the corresponding parameter vertex.
22
+ fn propagate_arguments(
23
+ arg_vtxs: &[VertexId],
24
+ param_vtxs: Option<&[VertexId]>,
25
+ changes: &mut ChangeSet,
26
+ ) {
27
+ for (arg_vtx, param_vtx) in arg_vtxs.iter().zip(param_vtxs.unwrap_or_default()) {
28
+ changes.add_edge(*arg_vtx, *param_vtx);
29
+ }
30
+ }
31
+
32
+ /// Propagate keyword argument types to keyword parameter vertices by name
33
+ fn propagate_keyword_arguments(
34
+ kwarg_vtxs: Option<&HashMap<String, VertexId>>,
35
+ kw_param_vtxs: Option<&HashMap<String, VertexId>>,
36
+ changes: &mut ChangeSet,
37
+ ) {
38
+ let (Some(args), Some(params)) = (kwarg_vtxs, kw_param_vtxs) else {
39
+ return;
40
+ };
41
+ for (name, arg_vtx) in args {
42
+ if let Some(&param_vtx) = params.get(name) {
43
+ changes.add_edge(*arg_vtx, param_vtx);
44
+ }
45
+ }
46
+ }
47
+
48
+ /// Box representing a method call
49
+ pub struct MethodCallBox {
50
+ id: BoxId,
51
+ recv: VertexId,
52
+ method_name: String,
53
+ ret: VertexId,
54
+ arg_vtxs: Vec<VertexId>,
55
+ kwarg_vtxs: Option<HashMap<String, VertexId>>,
56
+ location: Option<SourceLocation>, // Source code location
57
+ /// Whether this is a safe navigation call (`&.`)
58
+ safe_navigation: bool,
59
+ /// Number of times this box has been rescheduled
60
+ reschedule_count: u8,
61
+ }
62
+
63
+ /// Maximum number of reschedules before giving up
64
+ const MAX_RESCHEDULE_COUNT: u8 = 3;
65
+
66
+ impl MethodCallBox {
67
+ #[allow(clippy::too_many_arguments)]
68
+ pub fn new(
69
+ id: BoxId,
70
+ recv: VertexId,
71
+ method_name: String,
72
+ ret: VertexId,
73
+ arg_vtxs: Vec<VertexId>,
74
+ kwarg_vtxs: Option<HashMap<String, VertexId>>,
75
+ location: Option<SourceLocation>,
76
+ safe_navigation: bool,
77
+ ) -> Self {
78
+ Self {
79
+ id,
80
+ recv,
81
+ method_name,
82
+ ret,
83
+ arg_vtxs,
84
+ kwarg_vtxs,
85
+ location,
86
+ safe_navigation,
87
+ reschedule_count: 0,
88
+ }
89
+ }
90
+
91
+ /// Reschedule this box for re-execution if the limit hasn't been reached.
92
+ /// Handles cases where the receiver has no types yet (e.g., block parameters
93
+ /// that get typed by a later box). If max reschedules are reached, the box
94
+ /// is silently dropped (receiver type remains unknown).
95
+ fn try_reschedule(&mut self, changes: &mut ChangeSet) {
96
+ if self.reschedule_count < MAX_RESCHEDULE_COUNT {
97
+ self.reschedule_count += 1;
98
+ changes.reschedule(self.id);
99
+ }
100
+ }
101
+
102
+ fn process_recv_type(
103
+ &self,
104
+ recv_ty: &Type,
105
+ genv: &mut GlobalEnv,
106
+ changes: &mut ChangeSet,
107
+ ) {
108
+ // Safe navigation (`&.`): skip nil receiver entirely.
109
+ // Ruby's &. short-circuits: no method resolution, no argument evaluation, no error.
110
+ // The nil return type is added in run() after processing all receiver types.
111
+ if self.safe_navigation && matches!(recv_ty, Type::Nil) {
112
+ return;
113
+ }
114
+
115
+ if let Some(method_info) = genv.resolve_method(recv_ty, &self.method_name) {
116
+ if let Some(return_vtx) = method_info.return_vertex {
117
+ // User-defined method: connect body's return vertex to call site
118
+ changes.add_edge(return_vtx, self.ret);
119
+ propagate_arguments(
120
+ &self.arg_vtxs,
121
+ method_info.param_vertices.as_deref(),
122
+ changes,
123
+ );
124
+ propagate_keyword_arguments(
125
+ self.kwarg_vtxs.as_ref(),
126
+ method_info.keyword_param_vertices.as_ref(),
127
+ changes,
128
+ );
129
+ } else {
130
+ // Builtin/RBS method: create source with fixed return type
131
+ let ret_src_id = genv.new_source(method_info.return_type.clone());
132
+ changes.add_edge(ret_src_id, self.ret);
133
+ }
134
+ } else if self.method_name == "new" {
135
+ self.handle_new_call(recv_ty, genv, changes);
136
+ } else if !matches!(recv_ty, Type::Singleton { .. }) {
137
+ // Singleton types with unresolved methods are silently skipped;
138
+ // these are typically RBS class methods not yet supported.
139
+ self.report_type_error(recv_ty, genv);
140
+ }
141
+ }
142
+
143
+ /// Handle `.new` calls: singleton(Foo)#new produces instance(Foo),
144
+ /// and propagates arguments to the `initialize` method's parameters.
145
+ fn handle_new_call(
146
+ &self,
147
+ recv_ty: &Type,
148
+ genv: &mut GlobalEnv,
149
+ changes: &mut ChangeSet,
150
+ ) {
151
+ if let Type::Singleton { name } = recv_ty {
152
+ let instance_type = Type::instance(name.full_name());
153
+
154
+ let ret_src = genv.new_source(instance_type.clone());
155
+ changes.add_edge(ret_src, self.ret);
156
+
157
+ let init_info = genv.resolve_method(&instance_type, "initialize");
158
+ propagate_arguments(
159
+ &self.arg_vtxs,
160
+ init_info.and_then(|info| info.param_vertices.as_deref()),
161
+ changes,
162
+ );
163
+ propagate_keyword_arguments(
164
+ self.kwarg_vtxs.as_ref(),
165
+ init_info.and_then(|info| info.keyword_param_vertices.as_ref()),
166
+ changes,
167
+ );
168
+ } else {
169
+ self.report_type_error(recv_ty, genv);
170
+ }
171
+ }
172
+
173
+ fn report_type_error(&self, recv_ty: &Type, genv: &mut GlobalEnv) {
174
+ genv.record_type_error(
175
+ recv_ty.clone(),
176
+ self.method_name.clone(),
177
+ self.location.clone(),
178
+ );
179
+ }
180
+ }
181
+
182
+ impl BoxTrait for MethodCallBox {
183
+ fn id(&self) -> BoxId {
184
+ self.id
185
+ }
186
+
187
+ fn ret(&self) -> VertexId {
188
+ self.ret
189
+ }
190
+
191
+ fn run(&mut self, genv: &mut GlobalEnv, changes: &mut ChangeSet) {
192
+ let Some(recv_types) = genv.get_receiver_types(self.recv) else {
193
+ return;
194
+ };
195
+
196
+ if recv_types.is_empty() {
197
+ self.try_reschedule(changes);
198
+ return;
199
+ }
200
+
201
+ for recv_ty in &recv_types {
202
+ self.process_recv_type(recv_ty, genv, changes);
203
+ }
204
+
205
+ // Safe navigation (`&.`): if receiver can be nil, return type includes nil
206
+ if self.safe_navigation && recv_types.iter().any(|t| matches!(t, Type::Nil)) {
207
+ let nil_src = genv.new_source(Type::Nil);
208
+ changes.add_edge(nil_src, self.ret);
209
+ }
210
+ }
211
+ }
212
+
213
+ /// Box for resolving block parameter types from method call receiver
214
+ ///
215
+ /// When a method with a block is called (e.g., `str.each_char { |c| ... }`),
216
+ /// this box resolves the block parameter types from the method's RBS definition
217
+ /// and propagates them to the block parameter vertices.
218
+ pub struct BlockParameterTypeBox {
219
+ id: BoxId,
220
+ /// Receiver vertex of the method call
221
+ recv_vtx: VertexId,
222
+ /// Method name being called
223
+ method_name: String,
224
+ /// Block parameter vertices (in order)
225
+ block_param_vtxs: Vec<VertexId>,
226
+ }
227
+
228
+ impl BlockParameterTypeBox {
229
+ pub fn new(
230
+ id: BoxId,
231
+ recv_vtx: VertexId,
232
+ method_name: String,
233
+ block_param_vtxs: Vec<VertexId>,
234
+ ) -> Self {
235
+ Self {
236
+ id,
237
+ recv_vtx,
238
+ method_name,
239
+ block_param_vtxs,
240
+ }
241
+ }
242
+
243
+ /// Check if a type is a type variable name (e.g., Elem, K, V)
244
+ fn is_type_variable_name(name: &str) -> bool {
245
+ matches!(
246
+ name,
247
+ "Elem" | "K" | "V" | "T" | "U" | "A" | "B" | "Element" | "Key" | "Value" | "Out" | "In"
248
+ )
249
+ }
250
+
251
+ /// Try to resolve a type variable from receiver's type arguments.
252
+ ///
253
+ /// For `Array[Integer]#each { |x| }`, the block param type is `Elem`.
254
+ /// This resolves `Elem` → `Integer` using Array's type argument.
255
+ ///
256
+ /// Type variable mapping for common generic classes:
257
+ /// - Array[Elem]: Elem → type_args[0]
258
+ /// - Hash[K, V]: K → type_args[0], V → type_args[1]
259
+ fn resolve_type_variable(ty: &Type, recv_ty: &Type) -> Option<Type> {
260
+ let type_var_name = match ty {
261
+ Type::Instance { name } if Self::is_type_variable_name(name.full_name()) => {
262
+ name.full_name()
263
+ }
264
+ _ => return None, // Not a type variable
265
+ };
266
+
267
+ // Get type arguments from receiver
268
+ let type_args = recv_ty.type_args()?;
269
+ let class_name = recv_ty.base_class_name()?;
270
+
271
+ // Map type variable to type argument index based on class
272
+ let index = match (class_name, type_var_name) {
273
+ // Array[Elem]
274
+ ("Array", "Elem") => 0,
275
+ ("Array", "T") => 0,
276
+ ("Array", "Element") => 0,
277
+ // Hash[K, V]
278
+ ("Hash", "K") | ("Hash", "Key") => 0,
279
+ ("Hash", "V") | ("Hash", "Value") => 1,
280
+ // Generic fallback: first type arg for common names
281
+ (_, "Elem") | (_, "T") | (_, "Element") => 0,
282
+ _ => return None,
283
+ };
284
+
285
+ type_args.get(index).cloned()
286
+ }
287
+ }
288
+
289
+ impl BoxTrait for BlockParameterTypeBox {
290
+ fn id(&self) -> BoxId {
291
+ self.id
292
+ }
293
+
294
+ fn ret(&self) -> VertexId {
295
+ // This box doesn't have a single return value
296
+ // Return first param vtx as a placeholder
297
+ self.block_param_vtxs
298
+ .first()
299
+ .copied()
300
+ .unwrap_or(VertexId(0))
301
+ }
302
+
303
+ fn run(&mut self, genv: &mut GlobalEnv, changes: &mut ChangeSet) {
304
+ let Some(recv_types) = genv.get_receiver_types(self.recv_vtx) else {
305
+ return;
306
+ };
307
+
308
+ for recv_ty in recv_types {
309
+ // Resolve method to get block parameter types
310
+ // Clone the block_param_types to avoid borrow issues
311
+ let block_param_types = genv
312
+ .resolve_method(&recv_ty, &self.method_name)
313
+ .and_then(|info| info.block_param_types.clone());
314
+
315
+ if let Some(param_types) = block_param_types {
316
+ // Map block parameter types to vertices
317
+ for (i, param_type) in param_types.iter().enumerate() {
318
+ if i < self.block_param_vtxs.len() {
319
+ let param_vtx = self.block_param_vtxs[i];
320
+
321
+ // Try to resolve type variable from receiver's type arguments
322
+ let resolved_type =
323
+ if let Some(resolved) = Self::resolve_type_variable(param_type, &recv_ty) {
324
+ // Type variable resolved (e.g., Elem → Integer)
325
+ resolved
326
+ } else if let Type::Instance { name } = &param_type {
327
+ if Self::is_type_variable_name(name.full_name()) {
328
+ // Type variable couldn't be resolved, skip
329
+ continue;
330
+ } else {
331
+ // Regular type, use as-is
332
+ param_type.clone()
333
+ }
334
+ } else {
335
+ // Other type (Union, Generic, etc.), use as-is
336
+ param_type.clone()
337
+ };
338
+
339
+ // Create source with the resolved type
340
+ let src_id = genv.new_source(resolved_type);
341
+ changes.add_edge(src_id, param_vtx);
342
+ }
343
+ }
344
+ }
345
+ }
346
+ }
347
+ }
@@ -76,68 +76,3 @@ pub enum EdgeUpdate {
76
76
  Add { src: VertexId, dst: VertexId },
77
77
  Remove { src: VertexId, dst: VertexId },
78
78
  }
79
-
80
- #[cfg(test)]
81
- mod tests {
82
- use super::*;
83
-
84
- #[test]
85
- fn test_change_set_default() {
86
- let mut cs = ChangeSet::default();
87
- cs.add_edge(VertexId(1), VertexId(2));
88
- let updates = cs.reinstall();
89
- assert_eq!(updates.len(), 1);
90
- }
91
-
92
- #[test]
93
- fn test_change_set_add() {
94
- let mut cs = ChangeSet::new();
95
-
96
- cs.add_edge(VertexId(1), VertexId(2));
97
- cs.add_edge(VertexId(2), VertexId(3));
98
-
99
- let updates = cs.reinstall();
100
-
101
- assert_eq!(updates.len(), 2);
102
- assert!(updates.contains(&EdgeUpdate::Add {
103
- src: VertexId(1),
104
- dst: VertexId(2)
105
- }));
106
- assert!(updates.contains(&EdgeUpdate::Add {
107
- src: VertexId(2),
108
- dst: VertexId(3)
109
- }));
110
- }
111
-
112
- #[test]
113
- fn test_change_set_dedup() {
114
- let mut cs = ChangeSet::new();
115
-
116
- cs.add_edge(VertexId(1), VertexId(2));
117
- cs.add_edge(VertexId(1), VertexId(2)); // Duplicate
118
-
119
- let updates = cs.reinstall();
120
-
121
- assert_eq!(updates.len(), 1); // Duplicates removed
122
- }
123
-
124
- #[test]
125
- fn test_change_set_remove() {
126
- let mut cs = ChangeSet::new();
127
-
128
- // First commit
129
- cs.add_edge(VertexId(1), VertexId(2));
130
- cs.add_edge(VertexId(2), VertexId(3));
131
- cs.reinstall();
132
-
133
- // Second time: keep only (1,2)
134
- cs.add_edge(VertexId(1), VertexId(2));
135
- let updates = cs.reinstall();
136
-
137
- assert_eq!(updates.len(), 1);
138
- assert!(updates.contains(&EdgeUpdate::Remove {
139
- src: VertexId(2),
140
- dst: VertexId(3)
141
- }));
142
- }
143
- }
@@ -96,72 +96,3 @@ impl Default for Vertex {
96
96
  Self::new()
97
97
  }
98
98
  }
99
-
100
- #[cfg(test)]
101
- mod tests {
102
- use super::*;
103
-
104
- #[test]
105
- fn test_source_type() {
106
- let src = Source::new(Type::string());
107
- assert_eq!(src.ty.show(), "String");
108
- }
109
-
110
- #[test]
111
- fn test_vertex_empty() {
112
- let vtx = Vertex::new();
113
- assert_eq!(vtx.show(), "untyped");
114
- }
115
-
116
- #[test]
117
- fn test_vertex_single_type() {
118
- let mut vtx = Vertex::new();
119
-
120
- // Add String type
121
- let propagations = vtx.on_type_added(VertexId(1), vec![Type::string()]);
122
- assert_eq!(vtx.show(), "String");
123
- assert_eq!(propagations.len(), 0); // No propagation since no connections
124
- }
125
-
126
- #[test]
127
- fn test_vertex_union_type() {
128
- let mut vtx = Vertex::new();
129
-
130
- // Add String type
131
- vtx.on_type_added(VertexId(1), vec![Type::string()]);
132
- assert_eq!(vtx.show(), "String");
133
-
134
- // Add Integer type → becomes Union type
135
- vtx.on_type_added(VertexId(1), vec![Type::integer()]);
136
- assert_eq!(vtx.show(), "(Integer | String)");
137
- }
138
-
139
- #[test]
140
- fn test_vertex_propagation() {
141
- let mut vtx = Vertex::new();
142
-
143
- // Add connections
144
- vtx.add_next(VertexId(3));
145
- vtx.add_next(VertexId(4));
146
-
147
- // Add type → propagated to connections
148
- let propagations = vtx.on_type_added(VertexId(1), vec![Type::string()]);
149
-
150
- assert_eq!(propagations.len(), 2);
151
- assert!(propagations.contains(&(VertexId(3), vec![Type::string()])));
152
- assert!(propagations.contains(&(VertexId(4), vec![Type::string()])));
153
- }
154
-
155
- #[test]
156
- fn test_vertex_no_duplicate_propagation() {
157
- let mut vtx = Vertex::new();
158
- vtx.add_next(VertexId(3));
159
-
160
- // Add same type twice → only first time propagates
161
- let prop1 = vtx.on_type_added(VertexId(1), vec![Type::string()]);
162
- assert_eq!(prop1.len(), 1);
163
-
164
- let prop2 = vtx.on_type_added(VertexId(1), vec![Type::string()]);
165
- assert_eq!(prop2.len(), 0); // No propagation since already exists
166
- }
167
- }
@@ -77,80 +77,3 @@ impl Default for ParseSession {
77
77
  Self::new()
78
78
  }
79
79
  }
80
-
81
- #[cfg(test)]
82
- mod tests {
83
- use super::*;
84
-
85
- #[test]
86
- fn test_parse_simple_ruby() {
87
- let source = r#"x = 1
88
- puts x"#;
89
- let session = ParseSession::new();
90
- let result = session.parse_source(source, "test.rb");
91
- assert!(result.is_ok());
92
- }
93
-
94
- #[test]
95
- fn test_parse_string_literal() {
96
- let source = r#""hello".upcase"#;
97
- let session = ParseSession::new();
98
- let result = session.parse_source(source, "test.rb");
99
- assert!(result.is_ok());
100
- }
101
-
102
- #[test]
103
- fn test_parse_array_literal() {
104
- let source = r#"[1, 2, 3].map { |x| x * 2 }"#;
105
- let session = ParseSession::new();
106
- let result = session.parse_source(source, "test.rb");
107
- assert!(result.is_ok());
108
- }
109
-
110
- #[test]
111
- fn test_parse_method_definition() {
112
- let source = r#"def test_method
113
- x = "hello"
114
- x.upcase
115
- end"#;
116
- let session = ParseSession::new();
117
- let result = session.parse_source(source, "test.rb");
118
- assert!(result.is_ok());
119
- }
120
-
121
- #[test]
122
- fn test_parse_invalid_ruby() {
123
- let source = "def\nend end";
124
- let session = ParseSession::new();
125
- let result = session.parse_source(source, "test.rb");
126
- assert!(result.is_err());
127
- }
128
-
129
- #[test]
130
- fn test_parse_method_call() {
131
- let source = r#"user = User.new
132
- user.save"#;
133
- let session = ParseSession::new();
134
- let result = session.parse_source(source, "test.rb");
135
- assert!(result.is_ok());
136
- }
137
-
138
- #[test]
139
- fn test_parse_session_memory_tracking() {
140
- let session = ParseSession::new();
141
- let source = "x = 1";
142
- let _ = session.parse_source(source, "test.rb").unwrap();
143
- assert!(session.allocated_bytes() > 0);
144
- }
145
-
146
- #[test]
147
- fn test_parse_session_reset() {
148
- let mut session = ParseSession::new();
149
- let source = "x = 1";
150
- let _ = session.parse_source(source, "test.rb").unwrap();
151
- let before_reset = session.allocated_bytes();
152
- session.reset();
153
- // After reset, allocated_bytes may still report used chunks but internal data is cleared
154
- assert!(before_reset > 0);
155
- }
156
- }
@@ -276,6 +276,17 @@ impl Type {
276
276
  type_args: vec![element_type],
277
277
  }
278
278
  }
279
+
280
+ /// Collapse a Vec<Type> into a single Type or Union.
281
+ /// Returns the single element if len==1, or Union if len>1.
282
+ /// Panics if the vec is empty.
283
+ pub fn union_of(mut types: Vec<Type>) -> Self {
284
+ match types.len() {
285
+ 0 => panic!("union_of called with empty types"),
286
+ 1 => types.pop().unwrap(),
287
+ _ => Type::Union(types),
288
+ }
289
+ }
279
290
  }
280
291
 
281
292
  #[cfg(test)]
data/ext/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "methodray"
3
- version = "0.1.8"
3
+ version = "0.1.10"
4
4
  edition = "2021"
5
5
 
6
6
  [lib]
@@ -18,7 +18,7 @@ default = []
18
18
  cli = ["methodray-core/cli", "dep:clap", "dep:anyhow"]
19
19
 
20
20
  [dependencies]
21
- methodray-core = { path = "../rust", features = ["ruby-ffi"] }
21
+ methodray-core = { path = "../core", features = ["ruby-ffi"] }
22
22
  magnus = "0.8"
23
23
  clap = { version = "4", features = ["derive"], optional = true }
24
24
  anyhow = { version = "1", optional = true }
@@ -21,8 +21,8 @@ module MethodRay
21
21
  File.expand_path(@binary_name, LIB_DIR),
22
22
  # Development: target/release (project root)
23
23
  File.expand_path("../../target/release/#{@binary_name}", LIB_DIR),
24
- # Development: rust/target/release (legacy standalone binary)
25
- File.expand_path("../../rust/target/release/#{@legacy_binary_name}", LIB_DIR)
24
+ # Development: core/target/release (legacy standalone binary)
25
+ File.expand_path("../../core/target/release/#{@legacy_binary_name}", LIB_DIR)
26
26
  ]
27
27
  end
28
28
  end
@@ -46,7 +46,7 @@ module MethodRay
46
46
  warn 'Error: CLI binary not found.'
47
47
  warn ''
48
48
  warn 'For development, build with:'
49
- warn ' cd rust && cargo build --release --bin methodray --features cli'
49
+ warn ' cd core && cargo build --release --bin methodray --features cli'
50
50
  warn ''
51
51
  warn 'If installed via gem, this might be a platform compatibility issue.'
52
52
  warn 'Please report at: https://github.com/dak2/method-ray/issues'
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MethodRay
4
- VERSION = '0.1.8'
4
+ VERSION = '0.1.10'
5
5
  end