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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +41 -0
- data/README.md +27 -1
- data/ext/Cargo.toml +1 -1
- data/ext/src/lib.rs +7 -6
- data/lib/methodray/binary_locator.rb +29 -0
- data/lib/methodray/commands.rb +3 -20
- data/lib/methodray/version.rb +1 -1
- data/lib/methodray.rb +1 -1
- data/rust/Cargo.toml +3 -1
- data/rust/src/analyzer/attributes.rs +57 -0
- data/rust/src/analyzer/blocks.rs +175 -0
- data/rust/src/analyzer/calls.rs +7 -4
- data/rust/src/analyzer/conditionals.rs +466 -0
- data/rust/src/analyzer/definitions.rs +280 -13
- data/rust/src/analyzer/dispatch.rs +754 -11
- data/rust/src/analyzer/install.rs +58 -176
- data/rust/src/analyzer/literals.rs +201 -37
- data/rust/src/analyzer/mod.rs +4 -3
- data/rust/src/analyzer/parameters.rs +218 -0
- data/rust/src/analyzer/variables.rs +16 -8
- data/rust/src/cache/rbs_cache.rs +11 -4
- data/rust/src/checker.rs +20 -8
- data/rust/src/env/global_env.rs +42 -2
- data/rust/src/env/method_registry.rs +86 -4
- data/rust/src/env/mod.rs +1 -0
- data/rust/src/env/scope.rs +291 -25
- data/rust/src/graph/box.rs +478 -4
- data/rust/src/graph/change_set.rs +14 -0
- data/rust/src/graph/mod.rs +1 -1
- data/rust/src/lib.rs +2 -1
- data/rust/src/parser.rs +99 -39
- data/rust/src/rbs/converter.rs +16 -11
- data/rust/src/rbs/loader.rs +35 -5
- data/rust/src/rbs/mod.rs +4 -3
- data/rust/src/types.rs +344 -9
- metadata +6 -3
- data/rust/src/analyzer/tests/integration_test.rs +0 -136
- data/rust/src/analyzer/tests/mod.rs +0 -1
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 {
|
|
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 {
|
|
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 {
|
|
22
|
-
Type::
|
|
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
|
-
|
|
211
|
+
name: QualifiedName::simple("String"),
|
|
36
212
|
}
|
|
37
213
|
}
|
|
38
214
|
|
|
39
215
|
pub fn integer() -> Self {
|
|
40
216
|
Type::Instance {
|
|
41
|
-
|
|
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
|
-
|
|
235
|
+
name: QualifiedName::simple("Array"),
|
|
48
236
|
}
|
|
49
237
|
}
|
|
50
238
|
|
|
51
239
|
pub fn hash() -> Self {
|
|
52
240
|
Type::Instance {
|
|
53
|
-
|
|
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.
|
|
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/
|
|
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;
|