method-ray 0.1.2 → 0.1.4

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.
data/rust/src/types.rs CHANGED
@@ -1,11 +1,141 @@
1
+ use smallvec::SmallVec;
2
+
3
+ /// Qualified name for classes and modules (e.g., "Api::V1::User")
4
+ /// Uses compact representation: stores full name as single String with segment offsets
5
+ #[derive(Clone, Debug, PartialEq, Eq, Hash)]
6
+ pub struct QualifiedName {
7
+ /// Full qualified name (e.g., "Api::V1::User")
8
+ full: String,
9
+ /// Start offset of each segment (e.g., [0, 5, 10] for "Api::V1::User")
10
+ /// Up to 16 segments stored inline on stack
11
+ offsets: SmallVec<[u16; 16]>,
12
+ }
13
+
14
+ impl QualifiedName {
15
+ /// Create a new QualifiedName from a full qualified name string
16
+ pub fn new(full: &str) -> Self {
17
+ let mut offsets = SmallVec::new();
18
+ offsets.push(0);
19
+ for (i, _) in full.match_indices("::") {
20
+ offsets.push((i + 2) as u16);
21
+ }
22
+ Self {
23
+ full: full.to_string(),
24
+ offsets,
25
+ }
26
+ }
27
+
28
+ /// Create a QualifiedName for a simple (non-namespaced) name
29
+ pub fn simple(name: &str) -> Self {
30
+ Self {
31
+ full: name.to_string(),
32
+ offsets: smallvec::smallvec![0],
33
+ }
34
+ }
35
+
36
+ /// Get the last segment (class/module name without namespace)
37
+ pub fn name(&self) -> &str {
38
+ let start = *self.offsets.last().unwrap() as usize;
39
+ &self.full[start..]
40
+ }
41
+
42
+ /// Get the full qualified name string
43
+ pub fn full_name(&self) -> &str {
44
+ &self.full
45
+ }
46
+
47
+ /// Get the number of segments
48
+ pub fn depth(&self) -> usize {
49
+ self.offsets.len()
50
+ }
51
+
52
+ /// Check if this is a simple (non-namespaced) name
53
+ pub fn is_simple(&self) -> bool {
54
+ self.offsets.len() == 1
55
+ }
56
+
57
+ /// Get the n-th segment (0-indexed)
58
+ pub fn segment(&self, n: usize) -> Option<&str> {
59
+ if n >= self.offsets.len() {
60
+ return None;
61
+ }
62
+ let start = self.offsets[n] as usize;
63
+ let end = self
64
+ .offsets
65
+ .get(n + 1)
66
+ .map(|&o| o as usize - 2) // subtract "::"
67
+ .unwrap_or(self.full.len());
68
+ Some(&self.full[start..end])
69
+ }
70
+
71
+ /// Get the parent namespace (e.g., "Api::V1" for "Api::V1::User")
72
+ pub fn parent(&self) -> Option<Self> {
73
+ if self.offsets.len() <= 1 {
74
+ return None;
75
+ }
76
+ let last_offset = self.offsets[self.offsets.len() - 1] as usize;
77
+ let parent_full = &self.full[..last_offset - 2]; // exclude "::"
78
+ Some(Self {
79
+ full: parent_full.to_string(),
80
+ offsets: self.offsets[..self.offsets.len() - 1].into(),
81
+ })
82
+ }
83
+
84
+ /// Create a child by appending a name segment
85
+ pub fn child(&self, name: &str) -> Self {
86
+ let mut full = self.full.clone();
87
+ full.push_str("::");
88
+ full.push_str(name);
89
+
90
+ let mut offsets = self.offsets.clone();
91
+ offsets.push((self.full.len() + 2) as u16);
92
+
93
+ Self { full, offsets }
94
+ }
95
+
96
+ /// Join two qualified names
97
+ pub fn join(&self, other: &QualifiedName) -> Self {
98
+ let mut result = self.clone();
99
+ for i in 0..other.depth() {
100
+ if let Some(seg) = other.segment(i) {
101
+ result = result.child(seg);
102
+ }
103
+ }
104
+ result
105
+ }
106
+ }
107
+
108
+ impl std::fmt::Display for QualifiedName {
109
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110
+ write!(f, "{}", self.full)
111
+ }
112
+ }
113
+
114
+ impl From<&str> for QualifiedName {
115
+ fn from(s: &str) -> Self {
116
+ QualifiedName::new(s)
117
+ }
118
+ }
119
+
120
+ impl From<String> for QualifiedName {
121
+ fn from(s: String) -> Self {
122
+ QualifiedName::new(&s)
123
+ }
124
+ }
125
+
1
126
  /// Type system for graph-based type inference
2
127
  #[derive(Clone, Debug, PartialEq, Eq, Hash)]
3
128
  #[allow(dead_code)]
4
129
  pub enum Type {
5
- /// Instance type: String, Integer, etc.
6
- Instance { class_name: String },
130
+ /// Instance type: String, Integer, Api::User, etc.
131
+ Instance { name: QualifiedName },
132
+ /// Generic instance type: Array[Integer], Hash[String, Integer], etc.
133
+ Generic {
134
+ name: QualifiedName,
135
+ type_args: Vec<Type>,
136
+ },
7
137
  /// Singleton type: for class methods
8
- Singleton { class_name: String },
138
+ Singleton { name: QualifiedName },
9
139
  /// nil type
10
140
  Nil,
11
141
  /// Union type: sum of multiple types
@@ -18,8 +148,12 @@ impl Type {
18
148
  /// Convert type to string representation
19
149
  pub fn show(&self) -> String {
20
150
  match self {
21
- Type::Instance { class_name } => class_name.clone(),
22
- Type::Singleton { class_name } => format!("singleton({})", class_name),
151
+ Type::Instance { name } => name.full_name().to_string(),
152
+ Type::Generic { name, type_args } => {
153
+ let args: Vec<_> = type_args.iter().map(|t| t.show()).collect();
154
+ format!("{}[{}]", name.full_name(), args.join(", "))
155
+ }
156
+ Type::Singleton { name } => format!("singleton({})", name.full_name()),
23
157
  Type::Nil => "nil".to_string(),
24
158
  Type::Union(types) => {
25
159
  let names: Vec<_> = types.iter().map(|t| t.show()).collect();
@@ -29,28 +163,118 @@ impl Type {
29
163
  }
30
164
  }
31
165
 
166
+ /// Get the qualified name for this type
167
+ pub fn qualified_name(&self) -> Option<&QualifiedName> {
168
+ match self {
169
+ Type::Instance { name } => Some(name),
170
+ Type::Generic { name, .. } => Some(name),
171
+ Type::Singleton { name } => Some(name),
172
+ _ => None,
173
+ }
174
+ }
175
+
176
+ /// Get the base class name (full qualified name, without type arguments)
177
+ pub fn base_class_name(&self) -> Option<&str> {
178
+ self.qualified_name().map(|n| n.full_name())
179
+ }
180
+
181
+ /// Get just the simple name (without namespace)
182
+ pub fn simple_name(&self) -> Option<&str> {
183
+ self.qualified_name().map(|n| n.name())
184
+ }
185
+
186
+ /// Get type arguments for generic types
187
+ pub fn type_args(&self) -> Option<&[Type]> {
188
+ match self {
189
+ Type::Generic { type_args, .. } => Some(type_args),
190
+ _ => None,
191
+ }
192
+ }
193
+
194
+ /// Create an instance type from a qualified name string
195
+ pub fn instance(name: &str) -> Self {
196
+ Type::Instance {
197
+ name: QualifiedName::new(name),
198
+ }
199
+ }
200
+
201
+ /// Create a singleton type from a qualified name string
202
+ pub fn singleton(name: &str) -> Self {
203
+ Type::Singleton {
204
+ name: QualifiedName::new(name),
205
+ }
206
+ }
207
+
32
208
  /// Convenience constructors
33
209
  pub fn string() -> Self {
34
210
  Type::Instance {
35
- class_name: "String".to_string(),
211
+ name: QualifiedName::simple("String"),
36
212
  }
37
213
  }
38
214
 
39
215
  pub fn integer() -> Self {
40
216
  Type::Instance {
41
- class_name: "Integer".to_string(),
217
+ name: QualifiedName::simple("Integer"),
218
+ }
219
+ }
220
+
221
+ pub fn float() -> Self {
222
+ Type::Instance {
223
+ name: QualifiedName::simple("Float"),
224
+ }
225
+ }
226
+
227
+ pub fn symbol() -> Self {
228
+ Type::Instance {
229
+ name: QualifiedName::simple("Symbol"),
42
230
  }
43
231
  }
44
232
 
45
233
  pub fn array() -> Self {
46
234
  Type::Instance {
47
- class_name: "Array".to_string(),
235
+ name: QualifiedName::simple("Array"),
48
236
  }
49
237
  }
50
238
 
51
239
  pub fn hash() -> Self {
52
240
  Type::Instance {
53
- class_name: "Hash".to_string(),
241
+ name: QualifiedName::simple("Hash"),
242
+ }
243
+ }
244
+
245
+ pub fn regexp() -> Self {
246
+ Type::Instance {
247
+ name: QualifiedName::simple("Regexp"),
248
+ }
249
+ }
250
+
251
+ pub fn range() -> Self {
252
+ Type::Instance {
253
+ name: QualifiedName::simple("Range"),
254
+ }
255
+ }
256
+
257
+ /// Create a generic Array type: Array[element_type]
258
+ pub fn array_of(element_type: Type) -> Self {
259
+ Type::Generic {
260
+ name: QualifiedName::simple("Array"),
261
+ type_args: vec![element_type],
262
+ }
263
+ }
264
+
265
+ /// Create a generic Hash type: Hash[key_type, value_type]
266
+ pub fn hash_of(key_type: Type, value_type: Type) -> Self {
267
+ Type::Generic {
268
+ name: QualifiedName::simple("Hash"),
269
+ type_args: vec![key_type, value_type],
270
+ }
271
+ }
272
+
273
+ /// Create a generic Range type: Range[element_type]
274
+ pub fn range_of(element_type: Type) -> Self {
275
+ Type::Generic {
276
+ name: QualifiedName::simple("Range"),
277
+ type_args: vec![element_type],
54
278
  }
55
279
  }
56
280
  }
@@ -59,6 +283,71 @@ impl Type {
59
283
  mod tests {
60
284
  use super::*;
61
285
 
286
+ // QualifiedName tests
287
+ #[test]
288
+ fn test_qualified_name_simple() {
289
+ let name = QualifiedName::simple("User");
290
+ assert_eq!(name.name(), "User");
291
+ assert_eq!(name.full_name(), "User");
292
+ assert_eq!(name.depth(), 1);
293
+ assert!(name.is_simple());
294
+ assert!(name.parent().is_none());
295
+ }
296
+
297
+ #[test]
298
+ fn test_qualified_name_nested() {
299
+ let name = QualifiedName::new("Api::V1::User");
300
+ assert_eq!(name.name(), "User");
301
+ assert_eq!(name.full_name(), "Api::V1::User");
302
+ assert_eq!(name.depth(), 3);
303
+ assert!(!name.is_simple());
304
+
305
+ // Test segments
306
+ assert_eq!(name.segment(0), Some("Api"));
307
+ assert_eq!(name.segment(1), Some("V1"));
308
+ assert_eq!(name.segment(2), Some("User"));
309
+ assert_eq!(name.segment(3), None);
310
+ }
311
+
312
+ #[test]
313
+ fn test_qualified_name_parent() {
314
+ let name = QualifiedName::new("Api::V1::User");
315
+ let parent = name.parent().unwrap();
316
+ assert_eq!(parent.full_name(), "Api::V1");
317
+ assert_eq!(parent.name(), "V1");
318
+
319
+ let grandparent = parent.parent().unwrap();
320
+ assert_eq!(grandparent.full_name(), "Api");
321
+ assert!(grandparent.parent().is_none());
322
+ }
323
+
324
+ #[test]
325
+ fn test_qualified_name_child() {
326
+ let name = QualifiedName::simple("Api");
327
+ let child = name.child("V1");
328
+ assert_eq!(child.full_name(), "Api::V1");
329
+
330
+ let grandchild = child.child("User");
331
+ assert_eq!(grandchild.full_name(), "Api::V1::User");
332
+ assert_eq!(grandchild.depth(), 3);
333
+ }
334
+
335
+ #[test]
336
+ fn test_qualified_name_display() {
337
+ let name = QualifiedName::new("Api::V1::User");
338
+ assert_eq!(format!("{}", name), "Api::V1::User");
339
+ }
340
+
341
+ #[test]
342
+ fn test_qualified_name_from() {
343
+ let name: QualifiedName = "Api::User".into();
344
+ assert_eq!(name.full_name(), "Api::User");
345
+
346
+ let name2: QualifiedName = String::from("Module::Class").into();
347
+ assert_eq!(name2.full_name(), "Module::Class");
348
+ }
349
+
350
+ // Type tests
62
351
  #[test]
63
352
  fn test_type_show() {
64
353
  assert_eq!(Type::string().show(), "String");
@@ -67,9 +356,55 @@ mod tests {
67
356
  assert_eq!(Type::Bot.show(), "untyped");
68
357
  }
69
358
 
359
+ #[test]
360
+ fn test_type_instance_qualified() {
361
+ let user_type = Type::instance("Api::V1::User");
362
+ assert_eq!(user_type.show(), "Api::V1::User");
363
+ assert_eq!(user_type.base_class_name(), Some("Api::V1::User"));
364
+ assert_eq!(user_type.simple_name(), Some("User"));
365
+ }
366
+
70
367
  #[test]
71
368
  fn test_type_union() {
72
369
  let union = Type::Union(vec![Type::string(), Type::integer()]);
73
370
  assert_eq!(union.show(), "String | Integer");
74
371
  }
372
+
373
+ #[test]
374
+ fn test_generic_type_show() {
375
+ let array_int = Type::array_of(Type::integer());
376
+ assert_eq!(array_int.show(), "Array[Integer]");
377
+
378
+ let hash_str_int = Type::hash_of(Type::string(), Type::integer());
379
+ assert_eq!(hash_str_int.show(), "Hash[String, Integer]");
380
+ }
381
+
382
+ #[test]
383
+ fn test_base_class_name() {
384
+ assert_eq!(Type::string().base_class_name(), Some("String"));
385
+ assert_eq!(
386
+ Type::array_of(Type::integer()).base_class_name(),
387
+ Some("Array")
388
+ );
389
+ assert_eq!(Type::Nil.base_class_name(), None);
390
+ assert_eq!(Type::Bot.base_class_name(), None);
391
+ }
392
+
393
+ #[test]
394
+ fn test_type_args() {
395
+ let array_int = Type::array_of(Type::integer());
396
+ let args = array_int.type_args().unwrap();
397
+ assert_eq!(args.len(), 1);
398
+ assert_eq!(args[0].show(), "Integer");
399
+
400
+ // Non-generic types have no type args
401
+ assert!(Type::string().type_args().is_none());
402
+ }
403
+
404
+ #[test]
405
+ fn test_singleton_type() {
406
+ let singleton = Type::singleton("Api::User");
407
+ assert_eq!(singleton.show(), "singleton(Api::User)");
408
+ assert_eq!(singleton.base_class_name(), Some("Api::User"));
409
+ }
75
410
  }
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.2
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - dak2
@@ -43,18 +43,21 @@ files:
43
43
  - ext/src/cli.rs
44
44
  - ext/src/lib.rs
45
45
  - lib/methodray.rb
46
+ - lib/methodray/binary_locator.rb
46
47
  - lib/methodray/cli.rb
47
48
  - lib/methodray/commands.rb
48
49
  - lib/methodray/version.rb
49
50
  - rust/Cargo.toml
51
+ - rust/src/analyzer/attributes.rs
52
+ - rust/src/analyzer/blocks.rs
50
53
  - rust/src/analyzer/calls.rs
54
+ - rust/src/analyzer/conditionals.rs
51
55
  - rust/src/analyzer/definitions.rs
52
56
  - rust/src/analyzer/dispatch.rs
53
57
  - rust/src/analyzer/install.rs
54
58
  - rust/src/analyzer/literals.rs
55
59
  - rust/src/analyzer/mod.rs
56
- - rust/src/analyzer/tests/integration_test.rs
57
- - rust/src/analyzer/tests/mod.rs
60
+ - rust/src/analyzer/parameters.rs
58
61
  - rust/src/analyzer/variables.rs
59
62
  - rust/src/cache/mod.rs
60
63
  - rust/src/cache/rbs_cache.rs
@@ -1,136 +0,0 @@
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
- }
@@ -1 +0,0 @@
1
- mod integration_test;