method-ray 0.1.6 → 0.1.8

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,6 +3,8 @@
3
3
  //! This module provides a unified interface for managing vertices, boxes,
4
4
  //! methods, type errors, and scopes during type inference.
5
5
 
6
+ use std::collections::HashMap;
7
+
6
8
  use crate::env::box_manager::BoxManager;
7
9
  use crate::env::method_registry::{MethodInfo, MethodRegistry};
8
10
  use crate::env::scope::{Scope, ScopeId, ScopeKind, ScopeManager};
@@ -37,7 +39,6 @@ pub struct GlobalEnv {
37
39
  pub scope_manager: ScopeManager,
38
40
  }
39
41
 
40
- #[allow(dead_code)]
41
42
  impl GlobalEnv {
42
43
  pub fn new() -> Self {
43
44
  Self {
@@ -71,6 +72,21 @@ impl GlobalEnv {
71
72
  self.vertex_manager.get_source(id)
72
73
  }
73
74
 
75
+ /// Get the types associated with a vertex ID (handles both Vertex and Source)
76
+ ///
77
+ /// Returns `None` if neither a Vertex nor Source exists for this ID.
78
+ /// Returns `Some(vec![])` if a Vertex exists but has no types yet (e.g., unresolved block parameters).
79
+ /// Returns `Some(vec![ty])` if a Source exists (always exactly one type).
80
+ pub fn get_receiver_types(&self, id: VertexId) -> Option<Vec<Type>> {
81
+ if let Some(vertex) = self.get_vertex(id) {
82
+ Some(vertex.types.keys().cloned().collect())
83
+ } else if let Some(source) = self.get_source(id) {
84
+ Some(vec![source.ty.clone()])
85
+ } else {
86
+ None
87
+ }
88
+ }
89
+
74
90
  /// Add edge (immediate type propagation)
75
91
  pub fn add_edge(&mut self, src: VertexId, dst: VertexId) {
76
92
  self.vertex_manager.add_edge(src, dst);
@@ -169,9 +185,15 @@ impl GlobalEnv {
169
185
  method_name: &str,
170
186
  return_vertex: VertexId,
171
187
  param_vertices: Vec<VertexId>,
188
+ keyword_param_vertices: Option<HashMap<String, VertexId>>,
172
189
  ) {
173
- self.method_registry
174
- .register_user_method(recv_ty, method_name, return_vertex, param_vertices);
190
+ self.method_registry.register_user_method(
191
+ recv_ty,
192
+ method_name,
193
+ return_vertex,
194
+ param_vertices,
195
+ keyword_param_vertices,
196
+ );
175
197
  }
176
198
 
177
199
  // ===== Type Errors =====
@@ -189,20 +211,36 @@ impl GlobalEnv {
189
211
 
190
212
  // ===== Scope Management =====
191
213
 
214
+ /// Register a constant (simple name → qualified name) in the parent scope
215
+ fn register_constant_in_parent(&mut self, scope_id: ScopeId, name: &str) {
216
+ if name.contains("::") { return; }
217
+ let qualified = self.scope_manager.current_qualified_name()
218
+ .unwrap_or_else(|| name.to_string());
219
+ if let Some(parent_id) = self.scope_manager.get_scope(scope_id)
220
+ .and_then(|s| s.parent)
221
+ {
222
+ if let Some(parent_scope) = self.scope_manager.get_scope_mut(parent_id) {
223
+ parent_scope.constants.insert(name.to_string(), qualified);
224
+ }
225
+ }
226
+ }
227
+
192
228
  /// Enter a class scope
193
229
  pub fn enter_class(&mut self, name: String) -> ScopeId {
194
230
  let scope_id = self.scope_manager.new_scope(ScopeKind::Class {
195
- name,
231
+ name: name.clone(),
196
232
  superclass: None,
197
233
  });
198
234
  self.scope_manager.enter_scope(scope_id);
235
+ self.register_constant_in_parent(scope_id, &name);
199
236
  scope_id
200
237
  }
201
238
 
202
239
  /// Enter a module scope
203
240
  pub fn enter_module(&mut self, name: String) -> ScopeId {
204
- let scope_id = self.scope_manager.new_scope(ScopeKind::Module { name });
241
+ let scope_id = self.scope_manager.new_scope(ScopeKind::Module { name: name.clone() });
205
242
  self.scope_manager.enter_scope(scope_id);
243
+ self.register_constant_in_parent(scope_id, &name);
206
244
  scope_id
207
245
  }
208
246
 
@@ -6,7 +6,12 @@ pub struct LocalEnv {
6
6
  locals: HashMap<String, VertexId>,
7
7
  }
8
8
 
9
- #[allow(dead_code)]
9
+ impl Default for LocalEnv {
10
+ fn default() -> Self {
11
+ Self::new()
12
+ }
13
+ }
14
+
10
15
  impl LocalEnv {
11
16
  pub fn new() -> Self {
12
17
  Self {
@@ -24,6 +29,12 @@ impl LocalEnv {
24
29
  self.locals.get(name).copied()
25
30
  }
26
31
 
32
+ /// Remove a variable from the local environment.
33
+ /// Used for scoped variables like rescue's `=> e` binding.
34
+ pub fn remove_var(&mut self, name: &str) {
35
+ self.locals.remove(name);
36
+ }
37
+
27
38
  /// Get all variables
28
39
  pub fn all_vars(&self) -> impl Iterator<Item = (&String, &VertexId)> {
29
40
  self.locals.iter()
@@ -34,6 +45,12 @@ impl LocalEnv {
34
45
  mod tests {
35
46
  use super::*;
36
47
 
48
+ #[test]
49
+ fn test_local_env_default() {
50
+ let lenv = LocalEnv::default();
51
+ assert_eq!(lenv.get_var("x"), None);
52
+ }
53
+
37
54
  #[test]
38
55
  fn test_local_env() {
39
56
  let mut lenv = LocalEnv::new();
@@ -46,6 +63,23 @@ mod tests {
46
63
  assert_eq!(lenv.get_var("z"), None);
47
64
  }
48
65
 
66
+ #[test]
67
+ fn test_local_env_remove_var() {
68
+ let mut lenv = LocalEnv::new();
69
+
70
+ lenv.new_var("e".to_string(), VertexId(1));
71
+ assert_eq!(lenv.get_var("e"), Some(VertexId(1)));
72
+
73
+ lenv.remove_var("e");
74
+ assert_eq!(lenv.get_var("e"), None);
75
+ }
76
+
77
+ #[test]
78
+ fn test_local_env_remove_nonexistent() {
79
+ let mut lenv = LocalEnv::new();
80
+ lenv.remove_var("x"); // should not panic
81
+ }
82
+
49
83
  #[test]
50
84
  fn test_local_env_override() {
51
85
  let mut lenv = LocalEnv::new();
@@ -1,8 +1,13 @@
1
1
  //! Method registration and resolution
2
2
 
3
+ use std::collections::HashMap;
4
+
3
5
  use crate::graph::VertexId;
4
6
  use crate::types::Type;
5
- use std::collections::HashMap;
7
+ use smallvec::SmallVec;
8
+
9
+ const OBJECT_CLASS: &str = "Object";
10
+ const KERNEL_MODULE: &str = "Kernel";
6
11
 
7
12
  /// Method information
8
13
  #[derive(Debug, Clone)]
@@ -11,6 +16,7 @@ pub struct MethodInfo {
11
16
  pub block_param_types: Option<Vec<Type>>,
12
17
  pub return_vertex: Option<VertexId>,
13
18
  pub param_vertices: Option<Vec<VertexId>>,
19
+ pub keyword_param_vertices: Option<HashMap<String, VertexId>>,
14
20
  }
15
21
 
16
22
  /// Registry for method definitions
@@ -47,6 +53,7 @@ impl MethodRegistry {
47
53
  block_param_types,
48
54
  return_vertex: None,
49
55
  param_vertices: None,
56
+ keyword_param_vertices: None,
50
57
  },
51
58
  );
52
59
  }
@@ -58,6 +65,7 @@ impl MethodRegistry {
58
65
  method_name: &str,
59
66
  return_vertex: VertexId,
60
67
  param_vertices: Vec<VertexId>,
68
+ keyword_param_vertices: Option<HashMap<String, VertexId>>,
61
69
  ) {
62
70
  self.methods.insert(
63
71
  (recv_ty, method_name.to_string()),
@@ -66,30 +74,45 @@ impl MethodRegistry {
66
74
  block_param_types: None,
67
75
  return_vertex: Some(return_vertex),
68
76
  param_vertices: Some(param_vertices),
77
+ keyword_param_vertices,
69
78
  },
70
79
  );
71
80
  }
72
81
 
73
- /// Resolve a method for a receiver type
82
+ /// Build the method resolution order (MRO) fallback chain for a receiver type.
74
83
  ///
75
- /// For generic types like `Array[Integer]`, first tries exact match,
76
- /// then falls back to base class match (`Array`).
77
- pub fn resolve(&self, recv_ty: &Type, method_name: &str) -> Option<&MethodInfo> {
78
- // First, try exact match
79
- if let Some(info) = self
80
- .methods
81
- .get(&(recv_ty.clone(), method_name.to_string()))
82
- {
83
- return Some(info);
84
- }
84
+ /// Returns a list of types to search in order:
85
+ /// 1. Exact receiver type
86
+ /// 2. Generic base class (e.g., Array[Integer] Array)
87
+ /// 3. Object (for Instance/Generic types only)
88
+ /// 4. Kernel (for Instance/Generic types only)
89
+ fn fallback_chain(recv_ty: &Type) -> SmallVec<[Type; 4]> {
90
+ let mut chain = SmallVec::new();
91
+ chain.push(recv_ty.clone());
85
92
 
86
- // For generic types, fall back to base class
87
93
  if let Type::Generic { name, .. } = recv_ty {
88
- let base_type = Type::Instance { name: name.clone() };
89
- return self.methods.get(&(base_type, method_name.to_string()));
94
+ chain.push(Type::Instance { name: name.clone() });
95
+ }
96
+
97
+ // NOTE: Kernel is a module, not a class. Represented as Type::Instance
98
+ // due to lack of Type::Module variant.
99
+ if matches!(recv_ty, Type::Instance { .. } | Type::Generic { .. }) {
100
+ chain.push(Type::instance(OBJECT_CLASS));
101
+ chain.push(Type::instance(KERNEL_MODULE));
90
102
  }
91
103
 
92
- None
104
+ chain
105
+ }
106
+
107
+ /// Resolve a method for a receiver type
108
+ ///
109
+ /// Searches the MRO fallback chain: exact type → base class (for generics) → Object → Kernel.
110
+ /// For non-instance types (Singleton, Nil, Union, Bot), only exact match is attempted.
111
+ pub fn resolve(&self, recv_ty: &Type, method_name: &str) -> Option<&MethodInfo> {
112
+ let method_key = method_name.to_string();
113
+ Self::fallback_chain(recv_ty)
114
+ .into_iter()
115
+ .find_map(|ty| self.methods.get(&(ty, method_key.clone())))
93
116
  }
94
117
  }
95
118
 
@@ -116,7 +139,7 @@ mod tests {
116
139
  fn test_register_user_method_and_resolve() {
117
140
  let mut registry = MethodRegistry::new();
118
141
  let return_vtx = VertexId(42);
119
- registry.register_user_method(Type::instance("User"), "name", return_vtx, vec![]);
142
+ registry.register_user_method(Type::instance("User"), "name", return_vtx, vec![], None);
120
143
 
121
144
  let info = registry.resolve(&Type::instance("User"), "name").unwrap();
122
145
  assert_eq!(info.return_vertex, Some(VertexId(42)));
@@ -133,6 +156,7 @@ mod tests {
133
156
  "add",
134
157
  return_vtx,
135
158
  param_vtxs,
159
+ None,
136
160
  );
137
161
 
138
162
  let info = registry.resolve(&Type::instance("Calc"), "add").unwrap();
@@ -142,4 +166,103 @@ mod tests {
142
166
  assert_eq!(pvs[0], VertexId(20));
143
167
  assert_eq!(pvs[1], VertexId(21));
144
168
  }
169
+
170
+ // --- Object/Kernel fallback ---
171
+
172
+ #[test]
173
+ fn test_resolve_falls_back_to_object() {
174
+ let mut registry = MethodRegistry::new();
175
+ registry.register(Type::instance("Object"), "nil?", Type::instance("TrueClass"));
176
+ let info = registry.resolve(&Type::instance("CustomClass"), "nil?").unwrap();
177
+ assert_eq!(info.return_type.base_class_name(), Some("TrueClass"));
178
+ }
179
+
180
+ #[test]
181
+ fn test_resolve_falls_back_to_kernel() {
182
+ let mut registry = MethodRegistry::new();
183
+ registry.register(Type::instance("Kernel"), "puts", Type::Nil);
184
+ let info = registry.resolve(&Type::instance("MyApp"), "puts").unwrap();
185
+ assert_eq!(info.return_type, Type::Nil);
186
+ }
187
+
188
+ #[test]
189
+ fn test_resolve_object_before_kernel() {
190
+ let mut registry = MethodRegistry::new();
191
+ registry.register(Type::instance("Object"), "to_s", Type::string());
192
+ registry.register(Type::instance("Kernel"), "to_s", Type::integer());
193
+ let info = registry.resolve(&Type::instance("Anything"), "to_s").unwrap();
194
+ assert_eq!(info.return_type.base_class_name(), Some("String"));
195
+ }
196
+
197
+ #[test]
198
+ fn test_resolve_exact_match_over_fallback() {
199
+ let mut registry = MethodRegistry::new();
200
+ registry.register(Type::string(), "length", Type::integer());
201
+ registry.register(Type::instance("Object"), "length", Type::string());
202
+ let info = registry.resolve(&Type::string(), "length").unwrap();
203
+ assert_eq!(info.return_type.base_class_name(), Some("Integer"));
204
+ }
205
+
206
+ // --- Types that skip fallback ---
207
+
208
+ #[test]
209
+ fn test_singleton_type_skips_fallback() {
210
+ let mut registry = MethodRegistry::new();
211
+ registry.register(Type::instance("Kernel"), "puts", Type::Nil);
212
+ assert!(registry.resolve(&Type::singleton("User"), "puts").is_none());
213
+ }
214
+
215
+ #[test]
216
+ fn test_nil_type_skips_fallback() {
217
+ let mut registry = MethodRegistry::new();
218
+ registry.register(Type::instance("Kernel"), "puts", Type::Nil);
219
+ assert!(registry.resolve(&Type::Nil, "puts").is_none());
220
+ }
221
+
222
+ #[test]
223
+ fn test_union_type_skips_fallback() {
224
+ let mut registry = MethodRegistry::new();
225
+ registry.register(Type::instance("Kernel"), "puts", Type::Nil);
226
+ let union_ty = Type::Union(vec![Type::string(), Type::integer()]);
227
+ assert!(registry.resolve(&union_ty, "puts").is_none());
228
+ }
229
+
230
+ #[test]
231
+ fn test_bot_type_skips_fallback() {
232
+ let mut registry = MethodRegistry::new();
233
+ registry.register(Type::instance("Kernel"), "puts", Type::Nil);
234
+ assert!(registry.resolve(&Type::Bot, "puts").is_none());
235
+ }
236
+
237
+ // --- Generic type fallback chain ---
238
+
239
+ #[test]
240
+ fn test_resolve_generic_falls_back_to_kernel() {
241
+ let mut registry = MethodRegistry::new();
242
+ registry.register(Type::instance("Kernel"), "puts", Type::Nil);
243
+ let generic_type = Type::array_of(Type::integer());
244
+ let info = registry.resolve(&generic_type, "puts").unwrap();
245
+ assert_eq!(info.return_type, Type::Nil);
246
+ }
247
+
248
+ #[test]
249
+ fn test_resolve_generic_full_chain() {
250
+ // Verify the 4-step fallback: Generic[T] → Base → Object → Kernel
251
+ let mut registry = MethodRegistry::new();
252
+ registry.register(Type::instance("Kernel"), "object_id", Type::integer());
253
+ let generic_type = Type::array_of(Type::string());
254
+ // Array[String] → Array (none) → Object (none) → Kernel (exists)
255
+ let info = registry.resolve(&generic_type, "object_id").unwrap();
256
+ assert_eq!(info.return_type.base_class_name(), Some("Integer"));
257
+ }
258
+
259
+ // --- Namespaced class fallback ---
260
+
261
+ #[test]
262
+ fn test_resolve_namespaced_class_falls_back_to_object() {
263
+ let mut registry = MethodRegistry::new();
264
+ registry.register(Type::instance("Object"), "class", Type::string());
265
+ let info = registry.resolve(&Type::instance("Api::V1::User"), "class").unwrap();
266
+ assert_eq!(info.return_type.base_class_name(), Some("String"));
267
+ }
145
268
  }