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/graph/box.rs
CHANGED
|
@@ -23,15 +23,22 @@ pub struct MethodCallBox {
|
|
|
23
23
|
recv: VertexId,
|
|
24
24
|
method_name: String,
|
|
25
25
|
ret: VertexId,
|
|
26
|
+
arg_vtxs: Vec<VertexId>,
|
|
26
27
|
location: Option<SourceLocation>, // Source code location
|
|
28
|
+
/// Number of times this box has been rescheduled
|
|
29
|
+
reschedule_count: u8,
|
|
27
30
|
}
|
|
28
31
|
|
|
32
|
+
/// Maximum number of reschedules before giving up
|
|
33
|
+
const MAX_RESCHEDULE_COUNT: u8 = 3;
|
|
34
|
+
|
|
29
35
|
impl MethodCallBox {
|
|
30
36
|
pub fn new(
|
|
31
37
|
id: BoxId,
|
|
32
38
|
recv: VertexId,
|
|
33
39
|
method_name: String,
|
|
34
40
|
ret: VertexId,
|
|
41
|
+
arg_vtxs: Vec<VertexId>,
|
|
35
42
|
location: Option<SourceLocation>,
|
|
36
43
|
) -> Self {
|
|
37
44
|
Self {
|
|
@@ -39,7 +46,9 @@ impl MethodCallBox {
|
|
|
39
46
|
recv,
|
|
40
47
|
method_name,
|
|
41
48
|
ret,
|
|
49
|
+
arg_vtxs,
|
|
42
50
|
location,
|
|
51
|
+
reschedule_count: 0,
|
|
43
52
|
}
|
|
44
53
|
}
|
|
45
54
|
}
|
|
@@ -66,14 +75,66 @@ impl BoxTrait for MethodCallBox {
|
|
|
66
75
|
return;
|
|
67
76
|
};
|
|
68
77
|
|
|
78
|
+
// If receiver has no types yet, reschedule this box for later
|
|
79
|
+
// This handles cases like block parameters that are typed later
|
|
80
|
+
if recv_types.is_empty() {
|
|
81
|
+
if self.reschedule_count < MAX_RESCHEDULE_COUNT {
|
|
82
|
+
self.reschedule_count += 1;
|
|
83
|
+
changes.reschedule(self.id);
|
|
84
|
+
}
|
|
85
|
+
// If max reschedules reached, just skip (receiver type is unknown)
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
69
89
|
for recv_ty in recv_types {
|
|
70
90
|
// Resolve method
|
|
71
91
|
if let Some(method_info) = genv.resolve_method(&recv_ty, &self.method_name) {
|
|
72
|
-
|
|
73
|
-
|
|
92
|
+
if let Some(return_vtx) = method_info.return_vertex {
|
|
93
|
+
// User-defined: edge from body's last expr → call site return
|
|
94
|
+
changes.add_edge(return_vtx, self.ret);
|
|
74
95
|
|
|
75
|
-
|
|
76
|
-
|
|
96
|
+
// Propagate argument types to parameter vertices
|
|
97
|
+
if let Some(param_vtxs) = &method_info.param_vertices {
|
|
98
|
+
for (i, param_vtx) in param_vtxs.iter().enumerate() {
|
|
99
|
+
if let Some(arg_vtx) = self.arg_vtxs.get(i) {
|
|
100
|
+
changes.add_edge(*arg_vtx, *param_vtx);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
// RBS/builtin: create Source with fixed return type
|
|
106
|
+
let ret_src_id = genv.new_source(method_info.return_type.clone());
|
|
107
|
+
changes.add_edge(ret_src_id, self.ret);
|
|
108
|
+
}
|
|
109
|
+
} else if self.method_name == "new" {
|
|
110
|
+
if let Type::Singleton { name } = &recv_ty {
|
|
111
|
+
// singleton(User)#new → instance(User)
|
|
112
|
+
let instance_type = Type::instance(name.full_name());
|
|
113
|
+
let ret_src = genv.new_source(instance_type.clone());
|
|
114
|
+
changes.add_edge(ret_src, self.ret);
|
|
115
|
+
|
|
116
|
+
// Propagate arguments to initialize parameters
|
|
117
|
+
if let Some(init_info) = genv.resolve_method(&instance_type, "initialize") {
|
|
118
|
+
if let Some(param_vtxs) = &init_info.param_vertices {
|
|
119
|
+
for (i, param_vtx) in param_vtxs.iter().enumerate() {
|
|
120
|
+
if let Some(arg_vtx) = self.arg_vtxs.get(i) {
|
|
121
|
+
changes.add_edge(*arg_vtx, *param_vtx);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
// Non-singleton .new: record error
|
|
129
|
+
genv.record_type_error(
|
|
130
|
+
recv_ty.clone(),
|
|
131
|
+
self.method_name.clone(),
|
|
132
|
+
self.location.clone(),
|
|
133
|
+
);
|
|
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)
|
|
137
|
+
continue;
|
|
77
138
|
} else {
|
|
78
139
|
// Record type error for diagnostic reporting
|
|
79
140
|
genv.record_type_error(
|
|
@@ -86,6 +147,148 @@ impl BoxTrait for MethodCallBox {
|
|
|
86
147
|
}
|
|
87
148
|
}
|
|
88
149
|
|
|
150
|
+
/// Box for resolving block parameter types from method call receiver
|
|
151
|
+
///
|
|
152
|
+
/// When a method with a block is called (e.g., `str.each_char { |c| ... }`),
|
|
153
|
+
/// this box resolves the block parameter types from the method's RBS definition
|
|
154
|
+
/// and propagates them to the block parameter vertices.
|
|
155
|
+
#[allow(dead_code)]
|
|
156
|
+
pub struct BlockParameterTypeBox {
|
|
157
|
+
id: BoxId,
|
|
158
|
+
/// Receiver vertex of the method call
|
|
159
|
+
recv_vtx: VertexId,
|
|
160
|
+
/// Method name being called
|
|
161
|
+
method_name: String,
|
|
162
|
+
/// Block parameter vertices (in order)
|
|
163
|
+
block_param_vtxs: Vec<VertexId>,
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
impl BlockParameterTypeBox {
|
|
167
|
+
pub fn new(
|
|
168
|
+
id: BoxId,
|
|
169
|
+
recv_vtx: VertexId,
|
|
170
|
+
method_name: String,
|
|
171
|
+
block_param_vtxs: Vec<VertexId>,
|
|
172
|
+
) -> Self {
|
|
173
|
+
Self {
|
|
174
|
+
id,
|
|
175
|
+
recv_vtx,
|
|
176
|
+
method_name,
|
|
177
|
+
block_param_vtxs,
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/// Check if a type is a type variable name (e.g., Elem, K, V)
|
|
182
|
+
fn is_type_variable_name(name: &str) -> bool {
|
|
183
|
+
matches!(
|
|
184
|
+
name,
|
|
185
|
+
"Elem" | "K" | "V" | "T" | "U" | "A" | "B" | "Element" | "Key" | "Value" | "Out" | "In"
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/// Try to resolve a type variable from receiver's type arguments.
|
|
190
|
+
///
|
|
191
|
+
/// For `Array[Integer]#each { |x| }`, the block param type is `Elem`.
|
|
192
|
+
/// This resolves `Elem` → `Integer` using Array's type argument.
|
|
193
|
+
///
|
|
194
|
+
/// Type variable mapping for common generic classes:
|
|
195
|
+
/// - Array[Elem]: Elem → type_args[0]
|
|
196
|
+
/// - Hash[K, V]: K → type_args[0], V → type_args[1]
|
|
197
|
+
fn resolve_type_variable(ty: &Type, recv_ty: &Type) -> Option<Type> {
|
|
198
|
+
let type_var_name = match ty {
|
|
199
|
+
Type::Instance { name } if Self::is_type_variable_name(name.full_name()) => {
|
|
200
|
+
name.full_name()
|
|
201
|
+
}
|
|
202
|
+
_ => return None, // Not a type variable
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
// Get type arguments from receiver
|
|
206
|
+
let type_args = recv_ty.type_args()?;
|
|
207
|
+
let class_name = recv_ty.base_class_name()?;
|
|
208
|
+
|
|
209
|
+
// Map type variable to type argument index based on class
|
|
210
|
+
let index = match (class_name, type_var_name) {
|
|
211
|
+
// Array[Elem]
|
|
212
|
+
("Array", "Elem") => 0,
|
|
213
|
+
("Array", "T") => 0,
|
|
214
|
+
("Array", "Element") => 0,
|
|
215
|
+
// Hash[K, V]
|
|
216
|
+
("Hash", "K") | ("Hash", "Key") => 0,
|
|
217
|
+
("Hash", "V") | ("Hash", "Value") => 1,
|
|
218
|
+
// Generic fallback: first type arg for common names
|
|
219
|
+
(_, "Elem") | (_, "T") | (_, "Element") => 0,
|
|
220
|
+
_ => return None,
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
type_args.get(index).cloned()
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
impl BoxTrait for BlockParameterTypeBox {
|
|
228
|
+
fn id(&self) -> BoxId {
|
|
229
|
+
self.id
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
fn ret(&self) -> VertexId {
|
|
233
|
+
// This box doesn't have a single return value
|
|
234
|
+
// Return first param vtx as a placeholder
|
|
235
|
+
self.block_param_vtxs
|
|
236
|
+
.first()
|
|
237
|
+
.copied()
|
|
238
|
+
.unwrap_or(VertexId(0))
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
fn run(&mut self, genv: &mut GlobalEnv, changes: &mut ChangeSet) {
|
|
242
|
+
// Get receiver types
|
|
243
|
+
let recv_types: Vec<Type> = if let Some(recv_vertex) = genv.get_vertex(self.recv_vtx) {
|
|
244
|
+
recv_vertex.types.keys().cloned().collect()
|
|
245
|
+
} else if let Some(recv_source) = genv.get_source(self.recv_vtx) {
|
|
246
|
+
vec![recv_source.ty.clone()]
|
|
247
|
+
} else {
|
|
248
|
+
return;
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
for recv_ty in recv_types {
|
|
252
|
+
// Resolve method to get block parameter types
|
|
253
|
+
// Clone the block_param_types to avoid borrow issues
|
|
254
|
+
let block_param_types = genv
|
|
255
|
+
.resolve_method(&recv_ty, &self.method_name)
|
|
256
|
+
.and_then(|info| info.block_param_types.clone());
|
|
257
|
+
|
|
258
|
+
if let Some(param_types) = block_param_types {
|
|
259
|
+
// Map block parameter types to vertices
|
|
260
|
+
for (i, param_type) in param_types.iter().enumerate() {
|
|
261
|
+
if i < self.block_param_vtxs.len() {
|
|
262
|
+
let param_vtx = self.block_param_vtxs[i];
|
|
263
|
+
|
|
264
|
+
// Try to resolve type variable from receiver's type arguments
|
|
265
|
+
let resolved_type =
|
|
266
|
+
if let Some(resolved) = Self::resolve_type_variable(param_type, &recv_ty) {
|
|
267
|
+
// Type variable resolved (e.g., Elem → Integer)
|
|
268
|
+
resolved
|
|
269
|
+
} else if let Type::Instance { name } = ¶m_type {
|
|
270
|
+
if Self::is_type_variable_name(name.full_name()) {
|
|
271
|
+
// Type variable couldn't be resolved, skip
|
|
272
|
+
continue;
|
|
273
|
+
} else {
|
|
274
|
+
// Regular type, use as-is
|
|
275
|
+
param_type.clone()
|
|
276
|
+
}
|
|
277
|
+
} else {
|
|
278
|
+
// Other type (Union, Generic, etc.), use as-is
|
|
279
|
+
param_type.clone()
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
// Create source with the resolved type
|
|
283
|
+
let src_id = genv.new_source(resolved_type);
|
|
284
|
+
changes.add_edge(src_id, param_vtx);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
89
292
|
#[cfg(test)]
|
|
90
293
|
mod tests {
|
|
91
294
|
use super::*;
|
|
@@ -112,6 +315,7 @@ mod tests {
|
|
|
112
315
|
x_vtx,
|
|
113
316
|
"upcase".to_string(),
|
|
114
317
|
ret_vtx,
|
|
318
|
+
vec![],
|
|
115
319
|
None, // No location in test
|
|
116
320
|
);
|
|
117
321
|
|
|
@@ -143,6 +347,7 @@ mod tests {
|
|
|
143
347
|
x_vtx,
|
|
144
348
|
"unknown_method".to_string(),
|
|
145
349
|
ret_vtx,
|
|
350
|
+
vec![],
|
|
146
351
|
None, // No location in test
|
|
147
352
|
);
|
|
148
353
|
|
|
@@ -154,4 +359,273 @@ mod tests {
|
|
|
154
359
|
let ret_vertex = genv.get_vertex(ret_vtx).unwrap();
|
|
155
360
|
assert_eq!(ret_vertex.show(), "untyped");
|
|
156
361
|
}
|
|
362
|
+
|
|
363
|
+
#[test]
|
|
364
|
+
fn test_block_param_type_box_simple() {
|
|
365
|
+
let mut genv = GlobalEnv::new();
|
|
366
|
+
|
|
367
|
+
// Register String#each_char with block param type String
|
|
368
|
+
genv.register_builtin_method_with_block(
|
|
369
|
+
Type::string(),
|
|
370
|
+
"each_char",
|
|
371
|
+
Type::string(),
|
|
372
|
+
Some(vec![Type::string()]),
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
// Create receiver vertex with String type
|
|
376
|
+
let recv_vtx = genv.new_vertex();
|
|
377
|
+
let str_src = genv.new_source(Type::string());
|
|
378
|
+
genv.add_edge(str_src, recv_vtx);
|
|
379
|
+
|
|
380
|
+
// Create block parameter vertex
|
|
381
|
+
let param_vtx = genv.new_vertex();
|
|
382
|
+
|
|
383
|
+
// Create and run BlockParameterTypeBox
|
|
384
|
+
let box_id = genv.alloc_box_id();
|
|
385
|
+
let block_box = BlockParameterTypeBox::new(
|
|
386
|
+
box_id,
|
|
387
|
+
recv_vtx,
|
|
388
|
+
"each_char".to_string(),
|
|
389
|
+
vec![param_vtx],
|
|
390
|
+
);
|
|
391
|
+
genv.register_box(box_id, Box::new(block_box));
|
|
392
|
+
|
|
393
|
+
// Run all boxes
|
|
394
|
+
genv.run_all();
|
|
395
|
+
|
|
396
|
+
// Block parameter should now have String type
|
|
397
|
+
assert_eq!(genv.get_vertex(param_vtx).unwrap().show(), "String");
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
#[test]
|
|
401
|
+
fn test_block_param_type_variable_skipped() {
|
|
402
|
+
let mut genv = GlobalEnv::new();
|
|
403
|
+
|
|
404
|
+
// Register Array#each with block param type Elem (type variable)
|
|
405
|
+
genv.register_builtin_method_with_block(
|
|
406
|
+
Type::array(),
|
|
407
|
+
"each",
|
|
408
|
+
Type::array(),
|
|
409
|
+
Some(vec![Type::instance("Elem")]),
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
let recv_vtx = genv.new_vertex();
|
|
413
|
+
let arr_src = genv.new_source(Type::array());
|
|
414
|
+
genv.add_edge(arr_src, recv_vtx);
|
|
415
|
+
|
|
416
|
+
let param_vtx = genv.new_vertex();
|
|
417
|
+
|
|
418
|
+
let box_id = genv.alloc_box_id();
|
|
419
|
+
let block_box = BlockParameterTypeBox::new(
|
|
420
|
+
box_id,
|
|
421
|
+
recv_vtx,
|
|
422
|
+
"each".to_string(),
|
|
423
|
+
vec![param_vtx],
|
|
424
|
+
);
|
|
425
|
+
genv.register_box(box_id, Box::new(block_box));
|
|
426
|
+
|
|
427
|
+
genv.run_all();
|
|
428
|
+
|
|
429
|
+
// Block parameter should remain untyped (type variable skipped)
|
|
430
|
+
assert_eq!(genv.get_vertex(param_vtx).unwrap().show(), "untyped");
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
#[test]
|
|
434
|
+
fn test_block_param_multiple_params() {
|
|
435
|
+
let mut genv = GlobalEnv::new();
|
|
436
|
+
|
|
437
|
+
// Register a method with multiple block params
|
|
438
|
+
genv.register_builtin_method_with_block(
|
|
439
|
+
Type::string(),
|
|
440
|
+
"each_with_index",
|
|
441
|
+
Type::string(),
|
|
442
|
+
Some(vec![Type::string(), Type::integer()]),
|
|
443
|
+
);
|
|
444
|
+
|
|
445
|
+
let recv_vtx = genv.new_vertex();
|
|
446
|
+
let str_src = genv.new_source(Type::string());
|
|
447
|
+
genv.add_edge(str_src, recv_vtx);
|
|
448
|
+
|
|
449
|
+
let param1_vtx = genv.new_vertex();
|
|
450
|
+
let param2_vtx = genv.new_vertex();
|
|
451
|
+
|
|
452
|
+
let box_id = genv.alloc_box_id();
|
|
453
|
+
let block_box = BlockParameterTypeBox::new(
|
|
454
|
+
box_id,
|
|
455
|
+
recv_vtx,
|
|
456
|
+
"each_with_index".to_string(),
|
|
457
|
+
vec![param1_vtx, param2_vtx],
|
|
458
|
+
);
|
|
459
|
+
genv.register_box(box_id, Box::new(block_box));
|
|
460
|
+
|
|
461
|
+
genv.run_all();
|
|
462
|
+
|
|
463
|
+
// Both params should have their types
|
|
464
|
+
assert_eq!(genv.get_vertex(param1_vtx).unwrap().show(), "String");
|
|
465
|
+
assert_eq!(genv.get_vertex(param2_vtx).unwrap().show(), "Integer");
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
#[test]
|
|
469
|
+
fn test_block_param_type_variable_resolved() {
|
|
470
|
+
let mut genv = GlobalEnv::new();
|
|
471
|
+
|
|
472
|
+
// Register Array#each with block param type Elem (type variable)
|
|
473
|
+
genv.register_builtin_method_with_block(
|
|
474
|
+
Type::array(),
|
|
475
|
+
"each",
|
|
476
|
+
Type::array(),
|
|
477
|
+
Some(vec![Type::instance("Elem")]),
|
|
478
|
+
);
|
|
479
|
+
|
|
480
|
+
// Create receiver vertex with Array[Integer] type
|
|
481
|
+
let recv_vtx = genv.new_vertex();
|
|
482
|
+
let arr_src = genv.new_source(Type::array_of(Type::integer()));
|
|
483
|
+
genv.add_edge(arr_src, recv_vtx);
|
|
484
|
+
|
|
485
|
+
let param_vtx = genv.new_vertex();
|
|
486
|
+
|
|
487
|
+
let box_id = genv.alloc_box_id();
|
|
488
|
+
let block_box = BlockParameterTypeBox::new(
|
|
489
|
+
box_id,
|
|
490
|
+
recv_vtx,
|
|
491
|
+
"each".to_string(),
|
|
492
|
+
vec![param_vtx],
|
|
493
|
+
);
|
|
494
|
+
genv.register_box(box_id, Box::new(block_box));
|
|
495
|
+
|
|
496
|
+
genv.run_all();
|
|
497
|
+
|
|
498
|
+
// Block parameter should be Integer (resolved from Array[Integer])
|
|
499
|
+
assert_eq!(genv.get_vertex(param_vtx).unwrap().show(), "Integer");
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
#[test]
|
|
503
|
+
fn test_hash_type_variable_resolved() {
|
|
504
|
+
let mut genv = GlobalEnv::new();
|
|
505
|
+
|
|
506
|
+
// Register Hash#each with block param types K, V
|
|
507
|
+
genv.register_builtin_method_with_block(
|
|
508
|
+
Type::hash(),
|
|
509
|
+
"each",
|
|
510
|
+
Type::hash(),
|
|
511
|
+
Some(vec![Type::instance("K"), Type::instance("V")]),
|
|
512
|
+
);
|
|
513
|
+
|
|
514
|
+
// Create receiver vertex with Hash[String, Integer] type
|
|
515
|
+
let recv_vtx = genv.new_vertex();
|
|
516
|
+
let hash_src = genv.new_source(Type::hash_of(Type::string(), Type::integer()));
|
|
517
|
+
genv.add_edge(hash_src, recv_vtx);
|
|
518
|
+
|
|
519
|
+
let key_vtx = genv.new_vertex();
|
|
520
|
+
let value_vtx = genv.new_vertex();
|
|
521
|
+
|
|
522
|
+
let box_id = genv.alloc_box_id();
|
|
523
|
+
let block_box = BlockParameterTypeBox::new(
|
|
524
|
+
box_id,
|
|
525
|
+
recv_vtx,
|
|
526
|
+
"each".to_string(),
|
|
527
|
+
vec![key_vtx, value_vtx],
|
|
528
|
+
);
|
|
529
|
+
genv.register_box(box_id, Box::new(block_box));
|
|
530
|
+
|
|
531
|
+
genv.run_all();
|
|
532
|
+
|
|
533
|
+
// Block parameters should be resolved from Hash[String, Integer]
|
|
534
|
+
assert_eq!(genv.get_vertex(key_vtx).unwrap().show(), "String");
|
|
535
|
+
assert_eq!(genv.get_vertex(value_vtx).unwrap().show(), "Integer");
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
#[test]
|
|
539
|
+
fn test_method_call_box_user_defined_method() {
|
|
540
|
+
let mut genv = GlobalEnv::new();
|
|
541
|
+
|
|
542
|
+
// Simulate: def name; "Alice"; end
|
|
543
|
+
let body_src = genv.new_source(Type::string());
|
|
544
|
+
|
|
545
|
+
// Register user-defined method User#name with return_vertex
|
|
546
|
+
genv.register_user_method(Type::instance("User"), "name", body_src, vec![]);
|
|
547
|
+
|
|
548
|
+
// Simulate: user.name (receiver has type User)
|
|
549
|
+
let recv_vtx = genv.new_vertex();
|
|
550
|
+
let recv_src = genv.new_source(Type::instance("User"));
|
|
551
|
+
genv.add_edge(recv_src, recv_vtx);
|
|
552
|
+
|
|
553
|
+
let ret_vtx = genv.new_vertex();
|
|
554
|
+
let box_id = genv.alloc_box_id();
|
|
555
|
+
let call_box = MethodCallBox::new(
|
|
556
|
+
box_id,
|
|
557
|
+
recv_vtx,
|
|
558
|
+
"name".to_string(),
|
|
559
|
+
ret_vtx,
|
|
560
|
+
vec![],
|
|
561
|
+
None,
|
|
562
|
+
);
|
|
563
|
+
genv.register_box(box_id, Box::new(call_box));
|
|
564
|
+
|
|
565
|
+
genv.run_all();
|
|
566
|
+
|
|
567
|
+
// Return type should be String (propagated from body's last expression)
|
|
568
|
+
assert_eq!(genv.get_vertex(ret_vtx).unwrap().show(), "String");
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
#[test]
|
|
572
|
+
fn test_method_call_box_param_type_propagation() {
|
|
573
|
+
let mut genv = GlobalEnv::new();
|
|
574
|
+
|
|
575
|
+
// Simulate: def format(value); value.to_s; end
|
|
576
|
+
// 1. Create parameter vertex for 'value'
|
|
577
|
+
let param_vtx = genv.new_vertex();
|
|
578
|
+
|
|
579
|
+
// 2. Register Integer#to_s -> String (builtin)
|
|
580
|
+
genv.register_builtin_method(Type::integer(), "to_s", Type::string());
|
|
581
|
+
|
|
582
|
+
// 3. Create MethodCallBox for value.to_s (inside method body)
|
|
583
|
+
let inner_ret_vtx = genv.new_vertex();
|
|
584
|
+
let inner_box_id = genv.alloc_box_id();
|
|
585
|
+
let inner_call = MethodCallBox::new(
|
|
586
|
+
inner_box_id,
|
|
587
|
+
param_vtx,
|
|
588
|
+
"to_s".to_string(),
|
|
589
|
+
inner_ret_vtx,
|
|
590
|
+
vec![],
|
|
591
|
+
None,
|
|
592
|
+
);
|
|
593
|
+
genv.register_box(inner_box_id, Box::new(inner_call));
|
|
594
|
+
|
|
595
|
+
// 4. Register user-defined method Formatter#format with return_vertex and param_vertices
|
|
596
|
+
genv.register_user_method(
|
|
597
|
+
Type::instance("Formatter"),
|
|
598
|
+
"format",
|
|
599
|
+
inner_ret_vtx,
|
|
600
|
+
vec![param_vtx],
|
|
601
|
+
);
|
|
602
|
+
|
|
603
|
+
// 5. Simulate call: Formatter.new.format(42)
|
|
604
|
+
let recv_vtx = genv.new_vertex();
|
|
605
|
+
let recv_src = genv.new_source(Type::instance("Formatter"));
|
|
606
|
+
genv.add_edge(recv_src, recv_vtx);
|
|
607
|
+
|
|
608
|
+
let arg_vtx = genv.new_source(Type::integer()); // argument: 42
|
|
609
|
+
|
|
610
|
+
let call_ret_vtx = genv.new_vertex();
|
|
611
|
+
let call_box_id = genv.alloc_box_id();
|
|
612
|
+
let call_box = MethodCallBox::new(
|
|
613
|
+
call_box_id,
|
|
614
|
+
recv_vtx,
|
|
615
|
+
"format".to_string(),
|
|
616
|
+
call_ret_vtx,
|
|
617
|
+
vec![arg_vtx],
|
|
618
|
+
None,
|
|
619
|
+
);
|
|
620
|
+
genv.register_box(call_box_id, Box::new(call_box));
|
|
621
|
+
|
|
622
|
+
// Run all boxes
|
|
623
|
+
genv.run_all();
|
|
624
|
+
|
|
625
|
+
// param_vtx should have Integer type (propagated from argument)
|
|
626
|
+
assert_eq!(genv.get_vertex(param_vtx).unwrap().show(), "Integer");
|
|
627
|
+
|
|
628
|
+
// Return type should be String (Integer#to_s -> String)
|
|
629
|
+
assert_eq!(genv.get_vertex(call_ret_vtx).unwrap().show(), "String");
|
|
630
|
+
}
|
|
157
631
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
use super::r#box::BoxId;
|
|
1
2
|
use super::VertexId;
|
|
2
3
|
|
|
3
4
|
/// Manages edge changes for type propagation
|
|
@@ -5,6 +6,8 @@ use super::VertexId;
|
|
|
5
6
|
pub struct ChangeSet {
|
|
6
7
|
new_edges: Vec<(VertexId, VertexId)>,
|
|
7
8
|
edges: Vec<(VertexId, VertexId)>,
|
|
9
|
+
/// Boxes to reschedule for later execution
|
|
10
|
+
reschedule_boxes: Vec<BoxId>,
|
|
8
11
|
}
|
|
9
12
|
|
|
10
13
|
impl ChangeSet {
|
|
@@ -12,6 +15,7 @@ impl ChangeSet {
|
|
|
12
15
|
Self {
|
|
13
16
|
new_edges: Vec::new(),
|
|
14
17
|
edges: Vec::new(),
|
|
18
|
+
reschedule_boxes: Vec::new(),
|
|
15
19
|
}
|
|
16
20
|
}
|
|
17
21
|
|
|
@@ -20,6 +24,16 @@ impl ChangeSet {
|
|
|
20
24
|
self.new_edges.push((src, dst));
|
|
21
25
|
}
|
|
22
26
|
|
|
27
|
+
/// Request to reschedule a Box for later execution
|
|
28
|
+
pub fn reschedule(&mut self, box_id: BoxId) {
|
|
29
|
+
self.reschedule_boxes.push(box_id);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/// Get and clear boxes that need to be rescheduled
|
|
33
|
+
pub fn take_reschedule_boxes(&mut self) -> Vec<BoxId> {
|
|
34
|
+
std::mem::take(&mut self.reschedule_boxes)
|
|
35
|
+
}
|
|
36
|
+
|
|
23
37
|
/// Commit changes and return list of added/removed edges
|
|
24
38
|
pub fn reinstall(&mut self) -> Vec<EdgeUpdate> {
|
|
25
39
|
// Remove duplicates
|
data/rust/src/graph/mod.rs
CHANGED
data/rust/src/lib.rs
CHANGED
|
@@ -11,7 +11,8 @@ pub mod parser;
|
|
|
11
11
|
pub mod source_map;
|
|
12
12
|
pub mod types;
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
// rbs module is always available (converter has no Ruby FFI dependency)
|
|
15
|
+
// but loader and error require ruby-ffi feature
|
|
15
16
|
pub mod rbs;
|
|
16
17
|
|
|
17
18
|
#[cfg(any(feature = "cli", feature = "lsp"))]
|