method-ray 0.1.3 → 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 +16 -0
- data/ext/Cargo.toml +1 -1
- data/lib/methodray/version.rb +1 -1
- data/rust/Cargo.toml +1 -1
- data/rust/src/analyzer/attributes.rs +57 -0
- data/rust/src/analyzer/blocks.rs +104 -23
- data/rust/src/analyzer/calls.rs +7 -4
- data/rust/src/analyzer/conditionals.rs +466 -0
- data/rust/src/analyzer/definitions.rs +126 -8
- data/rust/src/analyzer/dispatch.rs +746 -11
- data/rust/src/analyzer/install.rs +52 -870
- data/rust/src/analyzer/literals.rs +179 -30
- data/rust/src/analyzer/mod.rs +2 -0
- data/rust/src/analyzer/parameters.rs +64 -0
- data/rust/src/analyzer/variables.rs +12 -3
- data/rust/src/env/global_env.rs +12 -0
- data/rust/src/env/method_registry.rs +55 -0
- data/rust/src/graph/box.rs +145 -5
- metadata +3 -1
|
@@ -3,60 +3,215 @@
|
|
|
3
3
|
//! This module is responsible for:
|
|
4
4
|
//! - String, Integer, Float, Regexp literals
|
|
5
5
|
//! - nil, true, false, Symbol literals
|
|
6
|
+
//! - Array, Hash, Range literals with element type inference
|
|
6
7
|
//! - Creating Source vertices with fixed types
|
|
7
|
-
//!
|
|
8
|
-
//! Note: Array, Hash, and Range literals are handled in install.rs for element type inference
|
|
9
8
|
|
|
10
|
-
use crate::env::GlobalEnv;
|
|
11
|
-
use crate::graph::VertexId;
|
|
9
|
+
use crate::env::{GlobalEnv, LocalEnv};
|
|
10
|
+
use crate::graph::{ChangeSet, VertexId};
|
|
12
11
|
use crate::types::Type;
|
|
13
12
|
use ruby_prism::Node;
|
|
13
|
+
use std::collections::HashSet;
|
|
14
|
+
|
|
15
|
+
/// Install literal node (including complex types: Array, Hash, Range)
|
|
16
|
+
pub(crate) fn install_literal_node(
|
|
17
|
+
genv: &mut GlobalEnv,
|
|
18
|
+
lenv: &mut LocalEnv,
|
|
19
|
+
changes: &mut ChangeSet,
|
|
20
|
+
source: &str,
|
|
21
|
+
node: &Node,
|
|
22
|
+
) -> Option<VertexId> {
|
|
23
|
+
if node.as_array_node().is_some() {
|
|
24
|
+
let elements: Vec<Node> = node.as_array_node().unwrap().elements().iter().collect();
|
|
25
|
+
return install_array_literal_elements(genv, lenv, changes, source, elements);
|
|
26
|
+
}
|
|
14
27
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
28
|
+
if node.as_hash_node().is_some() {
|
|
29
|
+
let elements: Vec<Node> = node.as_hash_node().unwrap().elements().iter().collect();
|
|
30
|
+
return install_hash_literal_elements(genv, lenv, changes, source, elements);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if let Some(range_node) = node.as_range_node() {
|
|
34
|
+
return install_range_literal(genv, lenv, changes, source, &range_node);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
install_simple_literal(genv, node)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/// Install simple literal nodes (String, Integer, Float, nil, true, false, Symbol, Regexp)
|
|
41
|
+
fn install_simple_literal(genv: &mut GlobalEnv, node: &Node) -> Option<VertexId> {
|
|
21
42
|
if node.as_string_node().is_some() {
|
|
22
43
|
return Some(genv.new_source(Type::string()));
|
|
23
44
|
}
|
|
24
|
-
|
|
25
|
-
// 42
|
|
26
45
|
if node.as_integer_node().is_some() {
|
|
27
46
|
return Some(genv.new_source(Type::integer()));
|
|
28
47
|
}
|
|
29
|
-
|
|
30
|
-
// 3.14
|
|
31
48
|
if node.as_float_node().is_some() {
|
|
32
49
|
return Some(genv.new_source(Type::float()));
|
|
33
50
|
}
|
|
34
|
-
|
|
35
|
-
// nil
|
|
36
51
|
if node.as_nil_node().is_some() {
|
|
37
52
|
return Some(genv.new_source(Type::Nil));
|
|
38
53
|
}
|
|
39
|
-
|
|
40
|
-
// true
|
|
41
54
|
if node.as_true_node().is_some() {
|
|
42
55
|
return Some(genv.new_source(Type::instance("TrueClass")));
|
|
43
56
|
}
|
|
44
|
-
|
|
45
|
-
// false
|
|
46
57
|
if node.as_false_node().is_some() {
|
|
47
58
|
return Some(genv.new_source(Type::instance("FalseClass")));
|
|
48
59
|
}
|
|
49
|
-
|
|
50
|
-
// :symbol
|
|
51
60
|
if node.as_symbol_node().is_some() {
|
|
52
61
|
return Some(genv.new_source(Type::symbol()));
|
|
53
62
|
}
|
|
54
|
-
|
|
55
|
-
// /pattern/
|
|
56
63
|
if node.as_regular_expression_node().is_some() {
|
|
57
64
|
return Some(genv.new_source(Type::regexp()));
|
|
58
65
|
}
|
|
66
|
+
None
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// Install array literal with element type inference
|
|
70
|
+
fn install_array_literal_elements(
|
|
71
|
+
genv: &mut GlobalEnv,
|
|
72
|
+
lenv: &mut LocalEnv,
|
|
73
|
+
changes: &mut ChangeSet,
|
|
74
|
+
source: &str,
|
|
75
|
+
elements: Vec<Node>,
|
|
76
|
+
) -> Option<VertexId> {
|
|
77
|
+
if elements.is_empty() {
|
|
78
|
+
return Some(genv.new_source(Type::array()));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let mut element_types: HashSet<Type> = HashSet::new();
|
|
82
|
+
|
|
83
|
+
for element in &elements {
|
|
84
|
+
if let Some(vtx) = super::install::install_node(genv, lenv, changes, source, element) {
|
|
85
|
+
if let Some(src) = genv.get_source(vtx) {
|
|
86
|
+
element_types.insert(src.ty.clone());
|
|
87
|
+
} else if let Some(vertex) = genv.get_vertex(vtx) {
|
|
88
|
+
for ty in vertex.types.keys() {
|
|
89
|
+
element_types.insert(ty.clone());
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let array_type = if element_types.is_empty() {
|
|
96
|
+
Type::array()
|
|
97
|
+
} else if element_types.len() == 1 {
|
|
98
|
+
let elem_type = element_types.into_iter().next().unwrap();
|
|
99
|
+
Type::array_of(elem_type)
|
|
100
|
+
} else {
|
|
101
|
+
let types_vec: Vec<Type> = element_types.into_iter().collect();
|
|
102
|
+
let union_type = Type::Union(types_vec);
|
|
103
|
+
Type::array_of(union_type)
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
Some(genv.new_source(array_type))
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// Install hash literal with key/value type inference
|
|
110
|
+
fn install_hash_literal_elements(
|
|
111
|
+
genv: &mut GlobalEnv,
|
|
112
|
+
lenv: &mut LocalEnv,
|
|
113
|
+
changes: &mut ChangeSet,
|
|
114
|
+
source: &str,
|
|
115
|
+
elements: Vec<Node>,
|
|
116
|
+
) -> Option<VertexId> {
|
|
117
|
+
if elements.is_empty() {
|
|
118
|
+
return Some(genv.new_source(Type::hash()));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
let mut key_types: HashSet<Type> = HashSet::new();
|
|
122
|
+
let mut value_types: HashSet<Type> = HashSet::new();
|
|
123
|
+
|
|
124
|
+
for element in &elements {
|
|
125
|
+
if let Some(assoc_node) = element.as_assoc_node() {
|
|
126
|
+
if let Some(key_vtx) =
|
|
127
|
+
super::install::install_node(genv, lenv, changes, source, &assoc_node.key())
|
|
128
|
+
{
|
|
129
|
+
if let Some(src) = genv.get_source(key_vtx) {
|
|
130
|
+
key_types.insert(src.ty.clone());
|
|
131
|
+
} else if let Some(vertex) = genv.get_vertex(key_vtx) {
|
|
132
|
+
for ty in vertex.types.keys() {
|
|
133
|
+
key_types.insert(ty.clone());
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if let Some(value_vtx) =
|
|
139
|
+
super::install::install_node(genv, lenv, changes, source, &assoc_node.value())
|
|
140
|
+
{
|
|
141
|
+
if let Some(src) = genv.get_source(value_vtx) {
|
|
142
|
+
value_types.insert(src.ty.clone());
|
|
143
|
+
} else if let Some(vertex) = genv.get_vertex(value_vtx) {
|
|
144
|
+
for ty in vertex.types.keys() {
|
|
145
|
+
value_types.insert(ty.clone());
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
59
151
|
|
|
152
|
+
let hash_type = if key_types.is_empty() || value_types.is_empty() {
|
|
153
|
+
Type::hash()
|
|
154
|
+
} else {
|
|
155
|
+
let key_type = if key_types.len() == 1 {
|
|
156
|
+
key_types.into_iter().next().unwrap()
|
|
157
|
+
} else {
|
|
158
|
+
let types_vec: Vec<Type> = key_types.into_iter().collect();
|
|
159
|
+
Type::Union(types_vec)
|
|
160
|
+
};
|
|
161
|
+
let value_type = if value_types.len() == 1 {
|
|
162
|
+
value_types.into_iter().next().unwrap()
|
|
163
|
+
} else {
|
|
164
|
+
let types_vec: Vec<Type> = value_types.into_iter().collect();
|
|
165
|
+
Type::Union(types_vec)
|
|
166
|
+
};
|
|
167
|
+
Type::hash_of(key_type, value_type)
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
Some(genv.new_source(hash_type))
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/// Install range literal with element type inference
|
|
174
|
+
fn install_range_literal(
|
|
175
|
+
genv: &mut GlobalEnv,
|
|
176
|
+
lenv: &mut LocalEnv,
|
|
177
|
+
changes: &mut ChangeSet,
|
|
178
|
+
source: &str,
|
|
179
|
+
range_node: &ruby_prism::RangeNode,
|
|
180
|
+
) -> Option<VertexId> {
|
|
181
|
+
let element_type = if let Some(left) = range_node.left() {
|
|
182
|
+
infer_range_element_type(genv, lenv, changes, source, &left)
|
|
183
|
+
} else if let Some(right) = range_node.right() {
|
|
184
|
+
infer_range_element_type(genv, lenv, changes, source, &right)
|
|
185
|
+
} else {
|
|
186
|
+
None
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
let range_type = match element_type {
|
|
190
|
+
Some(ty) => Type::range_of(ty),
|
|
191
|
+
None => Type::range(),
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
Some(genv.new_source(range_type))
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/// Infer element type from a range endpoint node
|
|
198
|
+
fn infer_range_element_type(
|
|
199
|
+
genv: &mut GlobalEnv,
|
|
200
|
+
lenv: &mut LocalEnv,
|
|
201
|
+
changes: &mut ChangeSet,
|
|
202
|
+
source: &str,
|
|
203
|
+
node: &Node,
|
|
204
|
+
) -> Option<Type> {
|
|
205
|
+
if let Some(vtx) = super::install::install_node(genv, lenv, changes, source, node) {
|
|
206
|
+
if let Some(src) = genv.get_source(vtx) {
|
|
207
|
+
return Some(src.ty.clone());
|
|
208
|
+
}
|
|
209
|
+
if let Some(vertex) = genv.get_vertex(vtx) {
|
|
210
|
+
if let Some(ty) = vertex.types.keys().next() {
|
|
211
|
+
return Some(ty.clone());
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
60
215
|
None
|
|
61
216
|
}
|
|
62
217
|
|
|
@@ -67,9 +222,6 @@ mod tests {
|
|
|
67
222
|
#[test]
|
|
68
223
|
fn test_install_string_literal() {
|
|
69
224
|
let mut genv = GlobalEnv::new();
|
|
70
|
-
|
|
71
|
-
// Create a mock string node - we test via integration instead
|
|
72
|
-
// Unit test just verifies the type creation
|
|
73
225
|
let vtx = genv.new_source(Type::string());
|
|
74
226
|
assert_eq!(genv.get_source(vtx).unwrap().ty.show(), "String");
|
|
75
227
|
}
|
|
@@ -77,7 +229,6 @@ mod tests {
|
|
|
77
229
|
#[test]
|
|
78
230
|
fn test_install_integer_literal() {
|
|
79
231
|
let mut genv = GlobalEnv::new();
|
|
80
|
-
|
|
81
232
|
let vtx = genv.new_source(Type::integer());
|
|
82
233
|
assert_eq!(genv.get_source(vtx).unwrap().ty.show(), "Integer");
|
|
83
234
|
}
|
|
@@ -85,7 +236,6 @@ mod tests {
|
|
|
85
236
|
#[test]
|
|
86
237
|
fn test_install_float_literal() {
|
|
87
238
|
let mut genv = GlobalEnv::new();
|
|
88
|
-
|
|
89
239
|
let vtx = genv.new_source(Type::float());
|
|
90
240
|
assert_eq!(genv.get_source(vtx).unwrap().ty.show(), "Float");
|
|
91
241
|
}
|
|
@@ -93,7 +243,6 @@ mod tests {
|
|
|
93
243
|
#[test]
|
|
94
244
|
fn test_install_regexp_literal() {
|
|
95
245
|
let mut genv = GlobalEnv::new();
|
|
96
|
-
|
|
97
246
|
let vtx = genv.new_source(Type::regexp());
|
|
98
247
|
assert_eq!(genv.get_source(vtx).unwrap().ty.show(), "Regexp");
|
|
99
248
|
}
|
data/rust/src/analyzer/mod.rs
CHANGED
|
@@ -113,6 +113,70 @@ pub fn install_keyword_rest_parameter(
|
|
|
113
113
|
param_vtx
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
/// Install method parameters as local variables
|
|
117
|
+
///
|
|
118
|
+
/// Returns a Vec of VertexId for required and optional parameters (positional),
|
|
119
|
+
/// which can be used for argument-to-parameter type propagation.
|
|
120
|
+
pub(crate) fn install_parameters(
|
|
121
|
+
genv: &mut GlobalEnv,
|
|
122
|
+
lenv: &mut LocalEnv,
|
|
123
|
+
changes: &mut ChangeSet,
|
|
124
|
+
source: &str,
|
|
125
|
+
params_node: &ruby_prism::ParametersNode,
|
|
126
|
+
) -> Vec<VertexId> {
|
|
127
|
+
let mut param_vtxs = Vec::new();
|
|
128
|
+
|
|
129
|
+
// Required parameters: def foo(a, b)
|
|
130
|
+
for node in params_node.requireds().iter() {
|
|
131
|
+
if let Some(req_param) = node.as_required_parameter_node() {
|
|
132
|
+
let name = String::from_utf8_lossy(req_param.name().as_slice()).to_string();
|
|
133
|
+
let vtx = install_required_parameter(genv, lenv, name);
|
|
134
|
+
param_vtxs.push(vtx);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Optional parameters: def foo(a = 1, b = "hello")
|
|
139
|
+
for node in params_node.optionals().iter() {
|
|
140
|
+
if let Some(opt_param) = node.as_optional_parameter_node() {
|
|
141
|
+
let name = String::from_utf8_lossy(opt_param.name().as_slice()).to_string();
|
|
142
|
+
let default_value = opt_param.value();
|
|
143
|
+
|
|
144
|
+
let vtx = if let Some(default_vtx) =
|
|
145
|
+
super::install::install_node(genv, lenv, changes, source, &default_value)
|
|
146
|
+
{
|
|
147
|
+
install_optional_parameter(genv, lenv, changes, name, default_vtx)
|
|
148
|
+
} else {
|
|
149
|
+
install_required_parameter(genv, lenv, name)
|
|
150
|
+
};
|
|
151
|
+
param_vtxs.push(vtx);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Rest parameter: def foo(*args)
|
|
156
|
+
// Not included in param_vtxs (variadic args need special handling)
|
|
157
|
+
if let Some(rest_node) = params_node.rest() {
|
|
158
|
+
if let Some(rest_param) = rest_node.as_rest_parameter_node() {
|
|
159
|
+
if let Some(name_id) = rest_param.name() {
|
|
160
|
+
let name = String::from_utf8_lossy(name_id.as_slice()).to_string();
|
|
161
|
+
install_rest_parameter(genv, lenv, name);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Keyword rest parameter: def foo(**kwargs)
|
|
167
|
+
// Not included in param_vtxs (keyword args need special handling)
|
|
168
|
+
if let Some(kwrest_node) = params_node.keyword_rest() {
|
|
169
|
+
if let Some(kwrest_param) = kwrest_node.as_keyword_rest_parameter_node() {
|
|
170
|
+
if let Some(name_id) = kwrest_param.name() {
|
|
171
|
+
let name = String::from_utf8_lossy(name_id.as_slice()).to_string();
|
|
172
|
+
install_keyword_rest_parameter(genv, lenv, name);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
param_vtxs
|
|
178
|
+
}
|
|
179
|
+
|
|
116
180
|
#[cfg(test)]
|
|
117
181
|
mod tests {
|
|
118
182
|
use super::*;
|
|
@@ -29,14 +29,23 @@ pub fn install_local_var_read(lenv: &LocalEnv, var_name: &str) -> Option<VertexI
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/// Install instance variable write: @name = value
|
|
32
|
+
///
|
|
33
|
+
/// If @name already has a pre-allocated VertexId (e.g., from attr_reader),
|
|
34
|
+
/// an edge is added from value_vtx to the existing vertex so types propagate.
|
|
35
|
+
/// Otherwise, value_vtx is registered directly as the ivar's VertexId.
|
|
32
36
|
pub fn install_ivar_write(
|
|
33
37
|
genv: &mut GlobalEnv,
|
|
34
38
|
ivar_name: String,
|
|
35
39
|
value_vtx: VertexId,
|
|
36
40
|
) -> VertexId {
|
|
37
|
-
genv.scope_manager
|
|
38
|
-
.
|
|
39
|
-
|
|
41
|
+
if let Some(existing_vtx) = genv.scope_manager.lookup_instance_var(&ivar_name) {
|
|
42
|
+
genv.add_edge(value_vtx, existing_vtx);
|
|
43
|
+
existing_vtx
|
|
44
|
+
} else {
|
|
45
|
+
genv.scope_manager
|
|
46
|
+
.set_instance_var_in_class(ivar_name, value_vtx);
|
|
47
|
+
value_vtx
|
|
48
|
+
}
|
|
40
49
|
}
|
|
41
50
|
|
|
42
51
|
/// Install instance variable read: @name
|
data/rust/src/env/global_env.rs
CHANGED
|
@@ -162,6 +162,18 @@ impl GlobalEnv {
|
|
|
162
162
|
.register_with_block(recv_ty, method_name, ret_ty, block_param_types);
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
+
/// Register a user-defined method (return type resolved via graph)
|
|
166
|
+
pub fn register_user_method(
|
|
167
|
+
&mut self,
|
|
168
|
+
recv_ty: Type,
|
|
169
|
+
method_name: &str,
|
|
170
|
+
return_vertex: VertexId,
|
|
171
|
+
param_vertices: Vec<VertexId>,
|
|
172
|
+
) {
|
|
173
|
+
self.method_registry
|
|
174
|
+
.register_user_method(recv_ty, method_name, return_vertex, param_vertices);
|
|
175
|
+
}
|
|
176
|
+
|
|
165
177
|
// ===== Type Errors =====
|
|
166
178
|
|
|
167
179
|
/// Record a type error (undefined method)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
//! Method registration and resolution
|
|
2
2
|
|
|
3
|
+
use crate::graph::VertexId;
|
|
3
4
|
use crate::types::Type;
|
|
4
5
|
use std::collections::HashMap;
|
|
5
6
|
|
|
@@ -8,6 +9,8 @@ use std::collections::HashMap;
|
|
|
8
9
|
pub struct MethodInfo {
|
|
9
10
|
pub return_type: Type,
|
|
10
11
|
pub block_param_types: Option<Vec<Type>>,
|
|
12
|
+
pub return_vertex: Option<VertexId>,
|
|
13
|
+
pub param_vertices: Option<Vec<VertexId>>,
|
|
11
14
|
}
|
|
12
15
|
|
|
13
16
|
/// Registry for method definitions
|
|
@@ -42,6 +45,27 @@ impl MethodRegistry {
|
|
|
42
45
|
MethodInfo {
|
|
43
46
|
return_type: ret_ty,
|
|
44
47
|
block_param_types,
|
|
48
|
+
return_vertex: None,
|
|
49
|
+
param_vertices: None,
|
|
50
|
+
},
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/// Register a user-defined method (return type resolved via graph)
|
|
55
|
+
pub fn register_user_method(
|
|
56
|
+
&mut self,
|
|
57
|
+
recv_ty: Type,
|
|
58
|
+
method_name: &str,
|
|
59
|
+
return_vertex: VertexId,
|
|
60
|
+
param_vertices: Vec<VertexId>,
|
|
61
|
+
) {
|
|
62
|
+
self.methods.insert(
|
|
63
|
+
(recv_ty, method_name.to_string()),
|
|
64
|
+
MethodInfo {
|
|
65
|
+
return_type: Type::Bot,
|
|
66
|
+
block_param_types: None,
|
|
67
|
+
return_vertex: Some(return_vertex),
|
|
68
|
+
param_vertices: Some(param_vertices),
|
|
45
69
|
},
|
|
46
70
|
);
|
|
47
71
|
}
|
|
@@ -87,4 +111,35 @@ mod tests {
|
|
|
87
111
|
let registry = MethodRegistry::new();
|
|
88
112
|
assert!(registry.resolve(&Type::string(), "unknown").is_none());
|
|
89
113
|
}
|
|
114
|
+
|
|
115
|
+
#[test]
|
|
116
|
+
fn test_register_user_method_and_resolve() {
|
|
117
|
+
let mut registry = MethodRegistry::new();
|
|
118
|
+
let return_vtx = VertexId(42);
|
|
119
|
+
registry.register_user_method(Type::instance("User"), "name", return_vtx, vec![]);
|
|
120
|
+
|
|
121
|
+
let info = registry.resolve(&Type::instance("User"), "name").unwrap();
|
|
122
|
+
assert_eq!(info.return_vertex, Some(VertexId(42)));
|
|
123
|
+
assert_eq!(info.return_type, Type::Bot);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
#[test]
|
|
127
|
+
fn test_register_user_method_with_param_vertices() {
|
|
128
|
+
let mut registry = MethodRegistry::new();
|
|
129
|
+
let return_vtx = VertexId(10);
|
|
130
|
+
let param_vtxs = vec![VertexId(20), VertexId(21)];
|
|
131
|
+
registry.register_user_method(
|
|
132
|
+
Type::instance("Calc"),
|
|
133
|
+
"add",
|
|
134
|
+
return_vtx,
|
|
135
|
+
param_vtxs,
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
let info = registry.resolve(&Type::instance("Calc"), "add").unwrap();
|
|
139
|
+
assert_eq!(info.return_vertex, Some(VertexId(10)));
|
|
140
|
+
let pvs = info.param_vertices.as_ref().unwrap();
|
|
141
|
+
assert_eq!(pvs.len(), 2);
|
|
142
|
+
assert_eq!(pvs[0], VertexId(20));
|
|
143
|
+
assert_eq!(pvs[1], VertexId(21));
|
|
144
|
+
}
|
|
90
145
|
}
|
data/rust/src/graph/box.rs
CHANGED
|
@@ -23,6 +23,7 @@ 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
|
|
27
28
|
/// Number of times this box has been rescheduled
|
|
28
29
|
reschedule_count: u8,
|
|
@@ -37,6 +38,7 @@ impl MethodCallBox {
|
|
|
37
38
|
recv: VertexId,
|
|
38
39
|
method_name: String,
|
|
39
40
|
ret: VertexId,
|
|
41
|
+
arg_vtxs: Vec<VertexId>,
|
|
40
42
|
location: Option<SourceLocation>,
|
|
41
43
|
) -> Self {
|
|
42
44
|
Self {
|
|
@@ -44,6 +46,7 @@ impl MethodCallBox {
|
|
|
44
46
|
recv,
|
|
45
47
|
method_name,
|
|
46
48
|
ret,
|
|
49
|
+
arg_vtxs,
|
|
47
50
|
location,
|
|
48
51
|
reschedule_count: 0,
|
|
49
52
|
}
|
|
@@ -86,11 +89,52 @@ impl BoxTrait for MethodCallBox {
|
|
|
86
89
|
for recv_ty in recv_types {
|
|
87
90
|
// Resolve method
|
|
88
91
|
if let Some(method_info) = genv.resolve_method(&recv_ty, &self.method_name) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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);
|
|
95
|
+
|
|
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;
|
|
94
138
|
} else {
|
|
95
139
|
// Record type error for diagnostic reporting
|
|
96
140
|
genv.record_type_error(
|
|
@@ -271,6 +315,7 @@ mod tests {
|
|
|
271
315
|
x_vtx,
|
|
272
316
|
"upcase".to_string(),
|
|
273
317
|
ret_vtx,
|
|
318
|
+
vec![],
|
|
274
319
|
None, // No location in test
|
|
275
320
|
);
|
|
276
321
|
|
|
@@ -302,6 +347,7 @@ mod tests {
|
|
|
302
347
|
x_vtx,
|
|
303
348
|
"unknown_method".to_string(),
|
|
304
349
|
ret_vtx,
|
|
350
|
+
vec![],
|
|
305
351
|
None, // No location in test
|
|
306
352
|
);
|
|
307
353
|
|
|
@@ -488,4 +534,98 @@ mod tests {
|
|
|
488
534
|
assert_eq!(genv.get_vertex(key_vtx).unwrap().show(), "String");
|
|
489
535
|
assert_eq!(genv.get_vertex(value_vtx).unwrap().show(), "Integer");
|
|
490
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
|
+
}
|
|
491
631
|
}
|
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
|
|
@@ -48,8 +48,10 @@ files:
|
|
|
48
48
|
- lib/methodray/commands.rb
|
|
49
49
|
- lib/methodray/version.rb
|
|
50
50
|
- rust/Cargo.toml
|
|
51
|
+
- rust/src/analyzer/attributes.rs
|
|
51
52
|
- rust/src/analyzer/blocks.rs
|
|
52
53
|
- rust/src/analyzer/calls.rs
|
|
54
|
+
- rust/src/analyzer/conditionals.rs
|
|
53
55
|
- rust/src/analyzer/definitions.rs
|
|
54
56
|
- rust/src/analyzer/dispatch.rs
|
|
55
57
|
- rust/src/analyzer/install.rs
|