method-ray 0.1.4 → 0.1.6

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.
@@ -0,0 +1,192 @@
1
+ //! Operators - logical operator type inference (&&, ||)
2
+
3
+ use crate::env::{GlobalEnv, LocalEnv};
4
+ use crate::graph::{ChangeSet, VertexId};
5
+ use ruby_prism::{AndNode, OrNode};
6
+
7
+ use super::install::install_node;
8
+
9
+ /// Process AndNode (a && b): Union(type(a), type(b))
10
+ ///
11
+ /// Short-circuit semantics: if `a` is falsy, returns `a`; otherwise returns `b`.
12
+ /// Static approximation: we cannot determine truthiness at compile time,
13
+ /// so we conservatively produce Union(type(a), type(b)).
14
+ pub(crate) fn process_and_node(
15
+ genv: &mut GlobalEnv,
16
+ lenv: &mut LocalEnv,
17
+ changes: &mut ChangeSet,
18
+ source: &str,
19
+ and_node: &AndNode,
20
+ ) -> Option<VertexId> {
21
+ let result_vtx = genv.new_vertex();
22
+
23
+ let left_vtx = install_node(genv, lenv, changes, source, &and_node.left());
24
+ if let Some(vtx) = left_vtx {
25
+ genv.add_edge(vtx, result_vtx);
26
+ }
27
+
28
+ let right_vtx = install_node(genv, lenv, changes, source, &and_node.right());
29
+ if let Some(vtx) = right_vtx {
30
+ genv.add_edge(vtx, result_vtx);
31
+ }
32
+
33
+ Some(result_vtx)
34
+ }
35
+
36
+ /// Process OrNode (a || b): Union(type(a), type(b))
37
+ ///
38
+ /// Short-circuit semantics: if `a` is truthy, returns `a`; otherwise returns `b`.
39
+ /// Static approximation: identical to AndNode — Union of both sides.
40
+ pub(crate) fn process_or_node(
41
+ genv: &mut GlobalEnv,
42
+ lenv: &mut LocalEnv,
43
+ changes: &mut ChangeSet,
44
+ source: &str,
45
+ or_node: &OrNode,
46
+ ) -> Option<VertexId> {
47
+ let result_vtx = genv.new_vertex();
48
+
49
+ let left_vtx = install_node(genv, lenv, changes, source, &or_node.left());
50
+ if let Some(vtx) = left_vtx {
51
+ genv.add_edge(vtx, result_vtx);
52
+ }
53
+
54
+ let right_vtx = install_node(genv, lenv, changes, source, &or_node.right());
55
+ if let Some(vtx) = right_vtx {
56
+ genv.add_edge(vtx, result_vtx);
57
+ }
58
+
59
+ Some(result_vtx)
60
+ }
61
+
62
+ #[cfg(test)]
63
+ mod tests {
64
+ use crate::analyzer::install::AstInstaller;
65
+ use crate::env::{GlobalEnv, LocalEnv};
66
+ use crate::graph::VertexId;
67
+ use crate::parser::ParseSession;
68
+ use crate::types::Type;
69
+
70
+ /// Helper: parse Ruby source, process with AstInstaller, and return GlobalEnv
71
+ fn analyze(source: &str) -> GlobalEnv {
72
+ let session = ParseSession::new();
73
+ let parse_result = session.parse_source(source, "test.rb").unwrap();
74
+ let root = parse_result.node();
75
+ let program = root.as_program_node().unwrap();
76
+
77
+ let mut genv = GlobalEnv::new();
78
+ let mut lenv = LocalEnv::new();
79
+
80
+ let mut installer = AstInstaller::new(&mut genv, &mut lenv, source);
81
+ for stmt in &program.statements().body() {
82
+ installer.install_node(&stmt);
83
+ }
84
+ installer.finish();
85
+
86
+ genv
87
+ }
88
+
89
+ /// Helper: get the type string for a vertex ID
90
+ fn get_type_show(genv: &GlobalEnv, vtx: VertexId) -> String {
91
+ if let Some(vertex) = genv.get_vertex(vtx) {
92
+ vertex.show()
93
+ } else if let Some(source) = genv.get_source(vtx) {
94
+ source.ty.show()
95
+ } else {
96
+ panic!("vertex {:?} not found as either Vertex or Source", vtx);
97
+ }
98
+ }
99
+
100
+ #[test]
101
+ fn test_and_node_union_type() {
102
+ let source = r#"
103
+ class Foo
104
+ def bar
105
+ true && "hello"
106
+ end
107
+ end
108
+ "#;
109
+ let genv = analyze(source);
110
+ let info = genv
111
+ .resolve_method(&Type::instance("Foo"), "bar")
112
+ .expect("Foo#bar should be registered");
113
+ let ret_vtx = info.return_vertex.unwrap();
114
+ let type_str = get_type_show(&genv, ret_vtx);
115
+ assert!(type_str.contains("TrueClass"), "should contain TrueClass: {}", type_str);
116
+ assert!(type_str.contains("String"), "should contain String: {}", type_str);
117
+ }
118
+
119
+ #[test]
120
+ fn test_and_node_same_type() {
121
+ let source = r#"
122
+ class Foo
123
+ def bar
124
+ "a" && "b"
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
+ assert_eq!(get_type_show(&genv, ret_vtx), "String");
134
+ }
135
+
136
+ #[test]
137
+ fn test_or_node_union_type() {
138
+ let source = r#"
139
+ class Foo
140
+ def bar
141
+ 42 || "hello"
142
+ end
143
+ end
144
+ "#;
145
+ let genv = analyze(source);
146
+ let info = genv
147
+ .resolve_method(&Type::instance("Foo"), "bar")
148
+ .expect("Foo#bar should be registered");
149
+ let ret_vtx = info.return_vertex.unwrap();
150
+ let type_str = get_type_show(&genv, ret_vtx);
151
+ assert!(type_str.contains("Integer"), "should contain Integer: {}", type_str);
152
+ assert!(type_str.contains("String"), "should contain String: {}", type_str);
153
+ }
154
+
155
+ #[test]
156
+ fn test_or_node_same_type() {
157
+ let source = r#"
158
+ class Foo
159
+ def bar
160
+ 1 || 2
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
+ assert_eq!(get_type_show(&genv, ret_vtx), "Integer");
170
+ }
171
+
172
+ #[test]
173
+ fn test_nested_logical_operators() {
174
+ let source = r#"
175
+ class Foo
176
+ def bar
177
+ 1 && "a" || :b
178
+ end
179
+ end
180
+ "#;
181
+ let genv = analyze(source);
182
+ let info = genv
183
+ .resolve_method(&Type::instance("Foo"), "bar")
184
+ .expect("Foo#bar should be registered");
185
+ let ret_vtx = info.return_vertex.unwrap();
186
+ let type_str = get_type_show(&genv, ret_vtx);
187
+ assert!(type_str.contains("Integer"), "should contain Integer: {}", type_str);
188
+ assert!(type_str.contains("String"), "should contain String: {}", type_str);
189
+ assert!(type_str.contains("Symbol"), "should contain Symbol: {}", type_str);
190
+ }
191
+
192
+ }
@@ -0,0 +1,113 @@
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
+ }
@@ -0,0 +1,191 @@
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
+ }
data/rust/src/checker.rs CHANGED
@@ -17,8 +17,7 @@ impl FileChecker {
17
17
  // Just verify cache exists
18
18
  use crate::cache::RbsCache;
19
19
  RbsCache::load().context(
20
- "Failed to load RBS cache. Please run from Ruby first to generate cache:\n\
21
- ruby -rmethodray -e 'MethodRay::Analyzer.new(\".\").infer_types(\"x=1\")'",
20
+ "Failed to load RBS cache.",
22
21
  )?;
23
22
 
24
23
  Ok(Self {})
@@ -70,8 +69,7 @@ fn load_rbs_from_cache(genv: &mut GlobalEnv) -> Result<()> {
70
69
  use crate::types::Type;
71
70
 
72
71
  let cache = RbsCache::load().context(
73
- "Failed to load RBS cache. Please run from Ruby first to generate cache:\n\
74
- ruby -rmethodray -e 'MethodRay::Analyzer.new(\".\").infer_types(\"x=1\")'",
72
+ "Failed to load RBS cache.",
75
73
  )?;
76
74
 
77
75
  let methods = cache.methods();
data/rust/src/cli/args.rs CHANGED
@@ -35,6 +35,6 @@ pub enum Commands {
35
35
  /// Show version information
36
36
  Version,
37
37
 
38
- /// Clear RBS cache
38
+ /// [DEPRECATED] Clear RBS cache
39
39
  ClearCache,
40
40
  }
@@ -209,13 +209,12 @@ impl GlobalEnv {
209
209
  /// Enter a method scope
210
210
  pub fn enter_method(&mut self, name: String) -> ScopeId {
211
211
  // Look for class or module context
212
- let receiver_type = self
213
- .scope_manager
214
- .current_class_name()
215
- .or_else(|| self.scope_manager.current_module_name());
212
+ let receiver_type = self.scope_manager.current_qualified_name();
213
+ let return_vertex = Some(self.new_vertex());
216
214
  let scope_id = self.scope_manager.new_scope(ScopeKind::Method {
217
215
  name,
218
216
  receiver_type,
217
+ return_vertex,
219
218
  });
220
219
  self.scope_manager.enter_scope(scope_id);
221
220
  scope_id
@@ -20,6 +20,7 @@ pub enum ScopeKind {
20
20
  Method {
21
21
  name: String,
22
22
  receiver_type: Option<String>, // Receiver class/module name
23
+ return_vertex: Option<VertexId>, // Merge vertex for return statements
23
24
  },
24
25
  Block,
25
26
  }
@@ -309,6 +310,22 @@ impl ScopeManager {
309
310
  Some(result)
310
311
  }
311
312
 
313
+ /// Get return_vertex from the nearest enclosing method scope
314
+ pub fn current_method_return_vertex(&self) -> Option<VertexId> {
315
+ let mut current = Some(self.current_scope);
316
+ while let Some(scope_id) = current {
317
+ if let Some(scope) = self.scopes.get(&scope_id) {
318
+ if let ScopeKind::Method { return_vertex, .. } = &scope.kind {
319
+ return *return_vertex;
320
+ }
321
+ current = scope.parent;
322
+ } else {
323
+ break;
324
+ }
325
+ }
326
+ None
327
+ }
328
+
312
329
  /// Lookup instance variable in enclosing module scope
313
330
  pub fn lookup_instance_var_in_module(&self, name: &str) -> Option<VertexId> {
314
331
  let mut current = Some(self.current_scope);
@@ -448,6 +465,7 @@ mod tests {
448
465
  let method_id = sm.new_scope(ScopeKind::Method {
449
466
  name: "test".to_string(),
450
467
  receiver_type: None,
468
+ return_vertex: None,
451
469
  });
452
470
  sm.enter_scope(method_id);
453
471
 
@@ -472,6 +490,7 @@ mod tests {
472
490
  let method_id = sm.new_scope(ScopeKind::Method {
473
491
  name: "helper".to_string(),
474
492
  receiver_type: Some("Utils".to_string()),
493
+ return_vertex: None,
475
494
  });
476
495
  sm.enter_scope(method_id);
477
496
 
@@ -500,6 +519,7 @@ mod tests {
500
519
  let method_id = sm.new_scope(ScopeKind::Method {
501
520
  name: "get_setting".to_string(),
502
521
  receiver_type: Some("Config".to_string()),
522
+ return_vertex: None,
503
523
  });
504
524
  sm.enter_scope(method_id);
505
525
 
@@ -559,6 +579,7 @@ mod tests {
559
579
  let method_id = sm.new_scope(ScopeKind::Method {
560
580
  name: "greet".to_string(),
561
581
  receiver_type: None,
582
+ return_vertex: None,
562
583
  });
563
584
  sm.enter_scope(method_id);
564
585
 
@@ -132,8 +132,10 @@ impl BoxTrait for MethodCallBox {
132
132
  self.location.clone(),
133
133
  );
134
134
  } else if matches!(&recv_ty, Type::Singleton { .. }) {
135
- // Skip error for unknown class methods on Singleton types
136
- // (class method RBS registration is not yet supported)
135
+ // Skip error for unknown class methods on Singleton types.
136
+ // User-defined class methods (def self.foo) are resolved by
137
+ // resolve_method above. Only unresolved methods reach here
138
+ // (e.g., RBS class methods not yet supported).
137
139
  continue;
138
140
  } else {
139
141
  // Record type error for diagnostic reporting
data/rust/src/main.rs CHANGED
@@ -38,6 +38,7 @@ fn main() -> Result<()> {
38
38
  commands::print_version();
39
39
  }
40
40
  Commands::ClearCache => {
41
+ eprintln!("WARNING: 'clear-cache' is deprecated and will be removed in a future version.");
41
42
  commands::clear_cache()?;
42
43
  }
43
44
  }
@@ -37,12 +37,11 @@ impl<'a> RbsLoader<'a> {
37
37
 
38
38
  /// Load all method definitions from RBS
39
39
  pub fn load_methods(&self) -> Result<Vec<RbsMethodInfo>, RbsError> {
40
- // Load method_loader.rb
41
- let rb_path = concat!(env!("CARGO_MANIFEST_DIR"), "/src/rbs/method_loader.rb");
42
- let load_code = format!("require '{}'", rb_path);
40
+ // Load method_loader.rb (embedded at compile time to avoid hardcoded paths)
41
+ let ruby_code = include_str!("method_loader.rb");
43
42
  let _: Value = self
44
43
  .ruby
45
- .eval(&load_code)
44
+ .eval(ruby_code)
46
45
  .map_err(|e| RbsError::LoadError(format!("Failed to load method_loader.rb: {}", e)))?;
47
46
 
48
47
  // Instantiate Rbs::MethodLoader class and call method
data/rust/src/rbs/mod.rs CHANGED
@@ -14,3 +14,32 @@ pub mod loader;
14
14
  pub use error::RbsError;
15
15
  #[cfg(feature = "ruby-ffi")]
16
16
  pub use loader::{register_rbs_methods, RbsLoader, RbsMethodInfo};
17
+
18
+ #[cfg(test)]
19
+ mod tests {
20
+ #[test]
21
+ fn test_embedded_method_loader_contains_expected_class() {
22
+ let ruby_code = include_str!("method_loader.rb");
23
+ assert!(
24
+ ruby_code.contains("class MethodLoader"),
25
+ "Embedded Ruby code should contain MethodLoader class definition"
26
+ );
27
+ assert!(
28
+ ruby_code.contains("def load_methods"),
29
+ "Embedded Ruby code should contain load_methods method"
30
+ );
31
+ }
32
+
33
+ #[test]
34
+ fn test_embedded_method_loader_has_no_absolute_paths() {
35
+ let ruby_code = include_str!("method_loader.rb");
36
+ let forbidden_patterns = ["/home/runner/", "/Users/", "/tmp/build/"];
37
+ for pattern in &forbidden_patterns {
38
+ assert!(
39
+ !ruby_code.contains(pattern),
40
+ "Embedded Ruby code should not contain absolute path: {}",
41
+ pattern
42
+ );
43
+ }
44
+ }
45
+ }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: method-ray
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - dak2
@@ -57,7 +57,10 @@ files:
57
57
  - rust/src/analyzer/install.rs
58
58
  - rust/src/analyzer/literals.rs
59
59
  - rust/src/analyzer/mod.rs
60
+ - rust/src/analyzer/operators.rs
60
61
  - rust/src/analyzer/parameters.rs
62
+ - rust/src/analyzer/parentheses.rs
63
+ - rust/src/analyzer/returns.rs
61
64
  - rust/src/analyzer/variables.rs
62
65
  - rust/src/cache/mod.rs
63
66
  - rust/src/cache/rbs_cache.rs