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.
@@ -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
- /// Install literal nodes and return their VertexId
16
- ///
17
- /// Note: Array and Hash literals are NOT handled here because they require
18
- /// child processing for element type inference. See install.rs.
19
- pub fn install_literal(genv: &mut GlobalEnv, node: &Node) -> Option<VertexId> {
20
- // "hello"
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
  }
@@ -1,5 +1,7 @@
1
+ mod attributes;
1
2
  mod blocks;
2
3
  mod calls;
4
+ mod conditionals;
3
5
  mod definitions;
4
6
  mod dispatch;
5
7
  mod install;
@@ -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
- .set_instance_var_in_class(ivar_name, value_vtx);
39
- value_vtx
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
@@ -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
  }
@@ -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
- // Create return type as Source
90
- let ret_src_id = genv.new_source(method_info.return_type.clone());
91
-
92
- // Add edge to return value
93
- changes.add_edge(ret_src_id, self.ret);
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.3
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