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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +38 -0
- data/README.md +9 -11
- data/{rust → core}/Cargo.toml +1 -1
- data/core/src/analyzer/assignments.rs +219 -0
- data/{rust → core}/src/analyzer/blocks.rs +0 -50
- data/{rust → core}/src/analyzer/calls.rs +3 -32
- data/core/src/analyzer/conditionals.rs +190 -0
- data/core/src/analyzer/definitions.rs +205 -0
- data/core/src/analyzer/dispatch.rs +455 -0
- data/core/src/analyzer/exceptions.rs +168 -0
- data/{rust → core}/src/analyzer/install.rs +16 -1
- data/{rust → core}/src/analyzer/literals.rs +3 -71
- data/core/src/analyzer/loops.rs +94 -0
- data/{rust → core}/src/analyzer/mod.rs +1 -15
- data/core/src/analyzer/operators.rs +79 -0
- data/{rust → core}/src/analyzer/parameters.rs +4 -67
- data/core/src/analyzer/parentheses.rs +25 -0
- data/core/src/analyzer/returns.rs +39 -0
- data/core/src/analyzer/super_calls.rs +74 -0
- data/{rust → core}/src/analyzer/variables.rs +5 -25
- data/{rust → core}/src/checker.rs +0 -13
- data/{rust → core}/src/diagnostics/diagnostic.rs +0 -41
- data/{rust → core}/src/diagnostics/formatter.rs +0 -38
- data/{rust → core}/src/env/box_manager.rs +0 -30
- data/{rust → core}/src/env/global_env.rs +67 -80
- data/core/src/env/local_env.rs +42 -0
- data/core/src/env/method_registry.rs +173 -0
- data/core/src/env/scope.rs +299 -0
- data/{rust → core}/src/env/vertex_manager.rs +0 -73
- data/core/src/graph/box.rs +347 -0
- data/{rust → core}/src/graph/change_set.rs +0 -65
- data/{rust → core}/src/graph/vertex.rs +0 -69
- data/{rust → core}/src/parser.rs +0 -77
- data/{rust → core}/src/types.rs +11 -0
- data/ext/Cargo.toml +2 -2
- data/lib/methodray/binary_locator.rb +2 -2
- data/lib/methodray/commands.rb +1 -1
- data/lib/methodray/version.rb +1 -1
- metadata +58 -56
- data/rust/src/analyzer/assignments.rs +0 -152
- data/rust/src/analyzer/conditionals.rs +0 -538
- data/rust/src/analyzer/definitions.rs +0 -719
- data/rust/src/analyzer/dispatch.rs +0 -1137
- data/rust/src/analyzer/exceptions.rs +0 -521
- data/rust/src/analyzer/loops.rs +0 -176
- data/rust/src/analyzer/operators.rs +0 -284
- data/rust/src/analyzer/parentheses.rs +0 -113
- data/rust/src/analyzer/returns.rs +0 -191
- data/rust/src/env/local_env.rs +0 -92
- data/rust/src/env/method_registry.rs +0 -268
- data/rust/src/env/scope.rs +0 -596
- data/rust/src/graph/box.rs +0 -766
- /data/{rust → core}/src/analyzer/attributes.rs +0 -0
- /data/{rust → core}/src/cache/mod.rs +0 -0
- /data/{rust → core}/src/cache/rbs_cache.rs +0 -0
- /data/{rust → core}/src/cli/args.rs +0 -0
- /data/{rust → core}/src/cli/commands.rs +0 -0
- /data/{rust → core}/src/cli/mod.rs +0 -0
- /data/{rust → core}/src/diagnostics/mod.rs +0 -0
- /data/{rust → core}/src/env/mod.rs +0 -0
- /data/{rust → core}/src/env/type_error.rs +0 -0
- /data/{rust → core}/src/graph/mod.rs +0 -0
- /data/{rust → core}/src/lib.rs +0 -0
- /data/{rust → core}/src/lsp/diagnostics.rs +0 -0
- /data/{rust → core}/src/lsp/main.rs +0 -0
- /data/{rust → core}/src/lsp/mod.rs +0 -0
- /data/{rust → core}/src/lsp/server.rs +0 -0
- /data/{rust → core}/src/main.rs +0 -0
- /data/{rust → core}/src/rbs/converter.rs +0 -0
- /data/{rust → core}/src/rbs/error.rs +0 -0
- /data/{rust → core}/src/rbs/loader.rs +0 -0
- /data/{rust → core}/src/rbs/mod.rs +0 -0
- /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(¶m_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 } = ¶m_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
|
-
}
|
data/{rust → core}/src/parser.rs
RENAMED
|
@@ -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
|
-
}
|
data/{rust → core}/src/types.rs
RENAMED
|
@@ -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.
|
|
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 = "../
|
|
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:
|
|
25
|
-
File.expand_path("../../
|
|
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
|
data/lib/methodray/commands.rb
CHANGED
|
@@ -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
|
|
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'
|
data/lib/methodray/version.rb
CHANGED