method-ray 0.1.7 → 0.1.9

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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +33 -0
  3. data/{rust → core}/Cargo.toml +1 -1
  4. data/core/src/analyzer/assignments.rs +499 -0
  5. data/{rust → core}/src/analyzer/attributes.rs +2 -1
  6. data/{rust → core}/src/analyzer/blocks.rs +140 -0
  7. data/{rust → core}/src/analyzer/calls.rs +7 -3
  8. data/{rust → core}/src/analyzer/definitions.rs +12 -7
  9. data/{rust → core}/src/analyzer/dispatch.rs +431 -13
  10. data/core/src/analyzer/exceptions.rs +622 -0
  11. data/{rust → core}/src/analyzer/install.rs +37 -1
  12. data/{rust → core}/src/analyzer/literals.rs +3 -17
  13. data/core/src/analyzer/loops.rs +301 -0
  14. data/{rust → core}/src/analyzer/mod.rs +4 -0
  15. data/{rust → core}/src/analyzer/operators.rs +119 -27
  16. data/{rust → core}/src/analyzer/parameters.rs +214 -5
  17. data/core/src/analyzer/super_calls.rs +285 -0
  18. data/{rust → core}/src/cache/rbs_cache.rs +0 -1
  19. data/{rust → core}/src/cli/commands.rs +3 -3
  20. data/{rust → core}/src/diagnostics/diagnostic.rs +0 -3
  21. data/{rust → core}/src/diagnostics/formatter.rs +0 -1
  22. data/{rust → core}/src/env/box_manager.rs +2 -4
  23. data/{rust → core}/src/env/global_env.rs +28 -7
  24. data/{rust → core}/src/env/local_env.rs +35 -1
  25. data/{rust → core}/src/env/method_registry.rs +117 -25
  26. data/{rust → core}/src/env/scope.rs +91 -4
  27. data/{rust → core}/src/env/vertex_manager.rs +0 -1
  28. data/{rust → core}/src/graph/box.rs +134 -8
  29. data/{rust → core}/src/graph/change_set.rs +14 -0
  30. data/{rust → core}/src/lsp/server.rs +1 -1
  31. data/{rust → core}/src/rbs/loader.rs +1 -2
  32. data/{rust → core}/src/source_map.rs +0 -1
  33. data/{rust → core}/src/types.rs +11 -1
  34. data/ext/Cargo.toml +2 -2
  35. data/lib/methodray/binary_locator.rb +2 -2
  36. data/lib/methodray/commands.rb +1 -1
  37. data/lib/methodray/version.rb +1 -1
  38. metadata +54 -50
  39. /data/{rust → core}/src/analyzer/conditionals.rs +0 -0
  40. /data/{rust → core}/src/analyzer/parentheses.rs +0 -0
  41. /data/{rust → core}/src/analyzer/returns.rs +0 -0
  42. /data/{rust → core}/src/analyzer/variables.rs +0 -0
  43. /data/{rust → core}/src/cache/mod.rs +0 -0
  44. /data/{rust → core}/src/checker.rs +0 -0
  45. /data/{rust → core}/src/cli/args.rs +0 -0
  46. /data/{rust → core}/src/cli/mod.rs +0 -0
  47. /data/{rust → core}/src/diagnostics/mod.rs +0 -0
  48. /data/{rust → core}/src/env/mod.rs +0 -0
  49. /data/{rust → core}/src/env/type_error.rs +0 -0
  50. /data/{rust → core}/src/graph/mod.rs +0 -0
  51. /data/{rust → core}/src/graph/vertex.rs +0 -0
  52. /data/{rust → core}/src/lib.rs +0 -0
  53. /data/{rust → core}/src/lsp/diagnostics.rs +0 -0
  54. /data/{rust → core}/src/lsp/main.rs +0 -0
  55. /data/{rust → core}/src/lsp/mod.rs +0 -0
  56. /data/{rust → core}/src/main.rs +0 -0
  57. /data/{rust → core}/src/parser.rs +0 -0
  58. /data/{rust → core}/src/rbs/converter.rs +0 -0
  59. /data/{rust → core}/src/rbs/error.rs +0 -0
  60. /data/{rust → core}/src/rbs/mod.rs +0 -0
@@ -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};
@@ -35,9 +37,12 @@ pub struct GlobalEnv {
35
37
 
36
38
  /// Scope management
37
39
  pub scope_manager: ScopeManager,
40
+
41
+ /// Module inclusions: class_name → Vec<module_name> (in include order)
42
+ module_inclusions: HashMap<String, Vec<String>>,
43
+
38
44
  }
39
45
 
40
- #[allow(dead_code)]
41
46
  impl GlobalEnv {
42
47
  pub fn new() -> Self {
43
48
  Self {
@@ -46,6 +51,7 @@ impl GlobalEnv {
46
51
  method_registry: MethodRegistry::new(),
47
52
  type_errors: Vec::new(),
48
53
  scope_manager: ScopeManager::new(),
54
+ module_inclusions: HashMap::new(),
49
55
  }
50
56
  }
51
57
 
@@ -157,7 +163,16 @@ impl GlobalEnv {
157
163
 
158
164
  /// Resolve method
159
165
  pub fn resolve_method(&self, recv_ty: &Type, method_name: &str) -> Option<&MethodInfo> {
160
- self.method_registry.resolve(recv_ty, method_name)
166
+ self.method_registry
167
+ .resolve(recv_ty, method_name, &self.module_inclusions)
168
+ }
169
+
170
+ /// Record that a class includes a module
171
+ pub fn record_include(&mut self, class_name: &str, module_name: &str) {
172
+ self.module_inclusions
173
+ .entry(class_name.to_string())
174
+ .or_default()
175
+ .push(module_name.to_string());
161
176
  }
162
177
 
163
178
  /// Register built-in method
@@ -184,9 +199,15 @@ impl GlobalEnv {
184
199
  method_name: &str,
185
200
  return_vertex: VertexId,
186
201
  param_vertices: Vec<VertexId>,
202
+ keyword_param_vertices: Option<HashMap<String, VertexId>>,
187
203
  ) {
188
- self.method_registry
189
- .register_user_method(recv_ty, method_name, return_vertex, param_vertices);
204
+ self.method_registry.register_user_method(
205
+ recv_ty,
206
+ method_name,
207
+ return_vertex,
208
+ param_vertices,
209
+ keyword_param_vertices,
210
+ );
190
211
  }
191
212
 
192
213
  // ===== Type Errors =====
@@ -218,11 +239,11 @@ impl GlobalEnv {
218
239
  }
219
240
  }
220
241
 
221
- /// Enter a class scope
222
- pub fn enter_class(&mut self, name: String) -> ScopeId {
242
+ /// Enter a class scope with optional superclass
243
+ pub fn enter_class(&mut self, name: String, superclass: Option<&str>) -> ScopeId {
223
244
  let scope_id = self.scope_manager.new_scope(ScopeKind::Class {
224
245
  name: name.clone(),
225
- superclass: None,
246
+ superclass: superclass.map(|s| s.to_string()),
226
247
  });
227
248
  self.scope_manager.enter_scope(scope_id);
228
249
  self.register_constant_in_parent(scope_id, &name);
@@ -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,9 +1,10 @@
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
7
  use smallvec::SmallVec;
6
- use std::collections::HashMap;
7
8
 
8
9
  const OBJECT_CLASS: &str = "Object";
9
10
  const KERNEL_MODULE: &str = "Kernel";
@@ -15,6 +16,7 @@ pub struct MethodInfo {
15
16
  pub block_param_types: Option<Vec<Type>>,
16
17
  pub return_vertex: Option<VertexId>,
17
18
  pub param_vertices: Option<Vec<VertexId>>,
19
+ pub keyword_param_vertices: Option<HashMap<String, VertexId>>,
18
20
  }
19
21
 
20
22
  /// Registry for method definitions
@@ -51,6 +53,7 @@ impl MethodRegistry {
51
53
  block_param_types,
52
54
  return_vertex: None,
53
55
  param_vertices: None,
56
+ keyword_param_vertices: None,
54
57
  },
55
58
  );
56
59
  }
@@ -62,6 +65,7 @@ impl MethodRegistry {
62
65
  method_name: &str,
63
66
  return_vertex: VertexId,
64
67
  param_vertices: Vec<VertexId>,
68
+ keyword_param_vertices: Option<HashMap<String, VertexId>>,
65
69
  ) {
66
70
  self.methods.insert(
67
71
  (recv_ty, method_name.to_string()),
@@ -70,6 +74,7 @@ impl MethodRegistry {
70
74
  block_param_types: None,
71
75
  return_vertex: Some(return_vertex),
72
76
  param_vertices: Some(param_vertices),
77
+ keyword_param_vertices,
73
78
  },
74
79
  );
75
80
  }
@@ -79,9 +84,13 @@ impl MethodRegistry {
79
84
  /// Returns a list of types to search in order:
80
85
  /// 1. Exact receiver type
81
86
  /// 2. Generic → base class (e.g., Array[Integer] → Array)
82
- /// 3. Object (for Instance/Generic types only)
83
- /// 4. Kernel (for Instance/Generic types only)
84
- fn fallback_chain(recv_ty: &Type) -> SmallVec<[Type; 4]> {
87
+ /// 3. Included modules (last included first, matching Ruby MRO)
88
+ /// 4. Object (for Instance/Generic types only)
89
+ /// 5. Kernel (for Instance/Generic types only)
90
+ fn fallback_chain(
91
+ recv_ty: &Type,
92
+ inclusions: &HashMap<String, Vec<String>>,
93
+ ) -> SmallVec<[Type; 8]> {
85
94
  let mut chain = SmallVec::new();
86
95
  chain.push(recv_ty.clone());
87
96
 
@@ -89,9 +98,17 @@ impl MethodRegistry {
89
98
  chain.push(Type::Instance { name: name.clone() });
90
99
  }
91
100
 
92
- // NOTE: Kernel is a module, not a class. Represented as Type::Instance
93
- // due to lack of Type::Module variant.
101
+ // MRO for Instance/Generic: included modules Object Kernel
94
102
  if matches!(recv_ty, Type::Instance { .. } | Type::Generic { .. }) {
103
+ // Included modules (reverse order = last included has highest priority)
104
+ if let Some(class_name) = recv_ty.base_class_name() {
105
+ if let Some(modules) = inclusions.get(class_name) {
106
+ for module_name in modules.iter().rev() {
107
+ chain.push(Type::instance(module_name));
108
+ }
109
+ }
110
+ }
111
+
95
112
  chain.push(Type::instance(OBJECT_CLASS));
96
113
  chain.push(Type::instance(KERNEL_MODULE));
97
114
  }
@@ -101,11 +118,17 @@ impl MethodRegistry {
101
118
 
102
119
  /// Resolve a method for a receiver type
103
120
  ///
104
- /// Searches the MRO fallback chain: exact type → base class (for generics) → Object → Kernel.
121
+ /// Searches the MRO fallback chain: exact type → base class (for generics)
122
+ /// → included modules → Object → Kernel.
105
123
  /// For non-instance types (Singleton, Nil, Union, Bot), only exact match is attempted.
106
- pub fn resolve(&self, recv_ty: &Type, method_name: &str) -> Option<&MethodInfo> {
124
+ pub fn resolve(
125
+ &self,
126
+ recv_ty: &Type,
127
+ method_name: &str,
128
+ inclusions: &HashMap<String, Vec<String>>,
129
+ ) -> Option<&MethodInfo> {
107
130
  let method_key = method_name.to_string();
108
- Self::fallback_chain(recv_ty)
131
+ Self::fallback_chain(recv_ty, inclusions)
109
132
  .into_iter()
110
133
  .find_map(|ty| self.methods.get(&(ty, method_key.clone())))
111
134
  }
@@ -120,23 +143,23 @@ mod tests {
120
143
  let mut registry = MethodRegistry::new();
121
144
  registry.register(Type::string(), "length", Type::integer());
122
145
 
123
- let info = registry.resolve(&Type::string(), "length").unwrap();
146
+ let info = registry.resolve(&Type::string(), "length", &HashMap::new()).unwrap();
124
147
  assert_eq!(info.return_type.base_class_name(), Some("Integer"));
125
148
  }
126
149
 
127
150
  #[test]
128
151
  fn test_resolve_not_found() {
129
152
  let registry = MethodRegistry::new();
130
- assert!(registry.resolve(&Type::string(), "unknown").is_none());
153
+ assert!(registry.resolve(&Type::string(), "unknown", &HashMap::new()).is_none());
131
154
  }
132
155
 
133
156
  #[test]
134
157
  fn test_register_user_method_and_resolve() {
135
158
  let mut registry = MethodRegistry::new();
136
159
  let return_vtx = VertexId(42);
137
- registry.register_user_method(Type::instance("User"), "name", return_vtx, vec![]);
160
+ registry.register_user_method(Type::instance("User"), "name", return_vtx, vec![], None);
138
161
 
139
- let info = registry.resolve(&Type::instance("User"), "name").unwrap();
162
+ let info = registry.resolve(&Type::instance("User"), "name", &HashMap::new()).unwrap();
140
163
  assert_eq!(info.return_vertex, Some(VertexId(42)));
141
164
  assert_eq!(info.return_type, Type::Bot);
142
165
  }
@@ -151,9 +174,10 @@ mod tests {
151
174
  "add",
152
175
  return_vtx,
153
176
  param_vtxs,
177
+ None,
154
178
  );
155
179
 
156
- let info = registry.resolve(&Type::instance("Calc"), "add").unwrap();
180
+ let info = registry.resolve(&Type::instance("Calc"), "add", &HashMap::new()).unwrap();
157
181
  assert_eq!(info.return_vertex, Some(VertexId(10)));
158
182
  let pvs = info.param_vertices.as_ref().unwrap();
159
183
  assert_eq!(pvs.len(), 2);
@@ -167,7 +191,7 @@ mod tests {
167
191
  fn test_resolve_falls_back_to_object() {
168
192
  let mut registry = MethodRegistry::new();
169
193
  registry.register(Type::instance("Object"), "nil?", Type::instance("TrueClass"));
170
- let info = registry.resolve(&Type::instance("CustomClass"), "nil?").unwrap();
194
+ let info = registry.resolve(&Type::instance("CustomClass"), "nil?", &HashMap::new()).unwrap();
171
195
  assert_eq!(info.return_type.base_class_name(), Some("TrueClass"));
172
196
  }
173
197
 
@@ -175,7 +199,7 @@ mod tests {
175
199
  fn test_resolve_falls_back_to_kernel() {
176
200
  let mut registry = MethodRegistry::new();
177
201
  registry.register(Type::instance("Kernel"), "puts", Type::Nil);
178
- let info = registry.resolve(&Type::instance("MyApp"), "puts").unwrap();
202
+ let info = registry.resolve(&Type::instance("MyApp"), "puts", &HashMap::new()).unwrap();
179
203
  assert_eq!(info.return_type, Type::Nil);
180
204
  }
181
205
 
@@ -184,7 +208,7 @@ mod tests {
184
208
  let mut registry = MethodRegistry::new();
185
209
  registry.register(Type::instance("Object"), "to_s", Type::string());
186
210
  registry.register(Type::instance("Kernel"), "to_s", Type::integer());
187
- let info = registry.resolve(&Type::instance("Anything"), "to_s").unwrap();
211
+ let info = registry.resolve(&Type::instance("Anything"), "to_s", &HashMap::new()).unwrap();
188
212
  assert_eq!(info.return_type.base_class_name(), Some("String"));
189
213
  }
190
214
 
@@ -193,7 +217,7 @@ mod tests {
193
217
  let mut registry = MethodRegistry::new();
194
218
  registry.register(Type::string(), "length", Type::integer());
195
219
  registry.register(Type::instance("Object"), "length", Type::string());
196
- let info = registry.resolve(&Type::string(), "length").unwrap();
220
+ let info = registry.resolve(&Type::string(), "length", &HashMap::new()).unwrap();
197
221
  assert_eq!(info.return_type.base_class_name(), Some("Integer"));
198
222
  }
199
223
 
@@ -203,14 +227,14 @@ mod tests {
203
227
  fn test_singleton_type_skips_fallback() {
204
228
  let mut registry = MethodRegistry::new();
205
229
  registry.register(Type::instance("Kernel"), "puts", Type::Nil);
206
- assert!(registry.resolve(&Type::singleton("User"), "puts").is_none());
230
+ assert!(registry.resolve(&Type::singleton("User"), "puts", &HashMap::new()).is_none());
207
231
  }
208
232
 
209
233
  #[test]
210
234
  fn test_nil_type_skips_fallback() {
211
235
  let mut registry = MethodRegistry::new();
212
236
  registry.register(Type::instance("Kernel"), "puts", Type::Nil);
213
- assert!(registry.resolve(&Type::Nil, "puts").is_none());
237
+ assert!(registry.resolve(&Type::Nil, "puts", &HashMap::new()).is_none());
214
238
  }
215
239
 
216
240
  #[test]
@@ -218,14 +242,14 @@ mod tests {
218
242
  let mut registry = MethodRegistry::new();
219
243
  registry.register(Type::instance("Kernel"), "puts", Type::Nil);
220
244
  let union_ty = Type::Union(vec![Type::string(), Type::integer()]);
221
- assert!(registry.resolve(&union_ty, "puts").is_none());
245
+ assert!(registry.resolve(&union_ty, "puts", &HashMap::new()).is_none());
222
246
  }
223
247
 
224
248
  #[test]
225
249
  fn test_bot_type_skips_fallback() {
226
250
  let mut registry = MethodRegistry::new();
227
251
  registry.register(Type::instance("Kernel"), "puts", Type::Nil);
228
- assert!(registry.resolve(&Type::Bot, "puts").is_none());
252
+ assert!(registry.resolve(&Type::Bot, "puts", &HashMap::new()).is_none());
229
253
  }
230
254
 
231
255
  // --- Generic type fallback chain ---
@@ -235,7 +259,7 @@ mod tests {
235
259
  let mut registry = MethodRegistry::new();
236
260
  registry.register(Type::instance("Kernel"), "puts", Type::Nil);
237
261
  let generic_type = Type::array_of(Type::integer());
238
- let info = registry.resolve(&generic_type, "puts").unwrap();
262
+ let info = registry.resolve(&generic_type, "puts", &HashMap::new()).unwrap();
239
263
  assert_eq!(info.return_type, Type::Nil);
240
264
  }
241
265
 
@@ -246,7 +270,7 @@ mod tests {
246
270
  registry.register(Type::instance("Kernel"), "object_id", Type::integer());
247
271
  let generic_type = Type::array_of(Type::string());
248
272
  // Array[String] → Array (none) → Object (none) → Kernel (exists)
249
- let info = registry.resolve(&generic_type, "object_id").unwrap();
273
+ let info = registry.resolve(&generic_type, "object_id", &HashMap::new()).unwrap();
250
274
  assert_eq!(info.return_type.base_class_name(), Some("Integer"));
251
275
  }
252
276
 
@@ -256,7 +280,75 @@ mod tests {
256
280
  fn test_resolve_namespaced_class_falls_back_to_object() {
257
281
  let mut registry = MethodRegistry::new();
258
282
  registry.register(Type::instance("Object"), "class", Type::string());
259
- let info = registry.resolve(&Type::instance("Api::V1::User"), "class").unwrap();
283
+ let info = registry.resolve(&Type::instance("Api::V1::User"), "class", &HashMap::new()).unwrap();
284
+ assert_eq!(info.return_type.base_class_name(), Some("String"));
285
+ }
286
+
287
+ // --- Include (mixin) fallback ---
288
+
289
+ #[test]
290
+ fn test_resolve_with_include() {
291
+ let mut registry = MethodRegistry::new();
292
+ registry.register(Type::instance("Greetable"), "greet", Type::string());
293
+
294
+ let mut inclusions = HashMap::new();
295
+ inclusions.insert("User".to_string(), vec!["Greetable".to_string()]);
296
+
297
+ let info = registry.resolve(&Type::instance("User"), "greet", &inclusions).unwrap();
260
298
  assert_eq!(info.return_type.base_class_name(), Some("String"));
261
299
  }
300
+
301
+ #[test]
302
+ fn test_resolve_include_order() {
303
+ // include A; include B → B's method found first (MRO: last included wins)
304
+ let mut registry = MethodRegistry::new();
305
+ registry.register(Type::instance("A"), "foo", Type::string());
306
+ registry.register(Type::instance("B"), "foo", Type::integer());
307
+
308
+ let mut inclusions = HashMap::new();
309
+ inclusions.insert("User".to_string(), vec!["A".to_string(), "B".to_string()]);
310
+
311
+ let info = registry.resolve(&Type::instance("User"), "foo", &inclusions).unwrap();
312
+ assert_eq!(info.return_type.base_class_name(), Some("Integer"));
313
+ }
314
+
315
+ #[test]
316
+ fn test_resolve_class_method_over_include() {
317
+ // Class's own method takes priority over included module
318
+ let mut registry = MethodRegistry::new();
319
+ registry.register(Type::instance("Greetable"), "greet", Type::string());
320
+ registry.register(Type::instance("User"), "greet", Type::integer());
321
+
322
+ let mut inclusions = HashMap::new();
323
+ inclusions.insert("User".to_string(), vec!["Greetable".to_string()]);
324
+
325
+ let info = registry.resolve(&Type::instance("User"), "greet", &inclusions).unwrap();
326
+ assert_eq!(info.return_type.base_class_name(), Some("Integer"));
327
+ }
328
+
329
+ #[test]
330
+ fn test_resolve_include_before_object() {
331
+ // Included module is searched before Object in MRO
332
+ let mut registry = MethodRegistry::new();
333
+ registry.register(Type::instance("Object"), "foo", Type::string());
334
+ registry.register(Type::instance("MyModule"), "foo", Type::integer());
335
+
336
+ let mut inclusions = HashMap::new();
337
+ inclusions.insert("User".to_string(), vec!["MyModule".to_string()]);
338
+
339
+ let info = registry.resolve(&Type::instance("User"), "foo", &inclusions).unwrap();
340
+ assert_eq!(info.return_type.base_class_name(), Some("Integer"));
341
+ }
342
+
343
+ #[test]
344
+ fn test_singleton_type_skips_include_fallback() {
345
+ // include adds instance methods only — Singleton (class-level) should NOT resolve
346
+ let mut registry = MethodRegistry::new();
347
+ registry.register(Type::instance("Greetable"), "greet", Type::string());
348
+
349
+ let mut inclusions = HashMap::new();
350
+ inclusions.insert("User".to_string(), vec!["Greetable".to_string()]);
351
+
352
+ assert!(registry.resolve(&Type::singleton("User"), "greet", &inclusions).is_none());
353
+ }
262
354
  }
@@ -7,7 +7,6 @@ pub struct ScopeId(pub usize);
7
7
 
8
8
  /// Scope kind
9
9
  #[derive(Debug, Clone)]
10
- #[allow(dead_code)]
11
10
  pub enum ScopeKind {
12
11
  TopLevel,
13
12
  Class {
@@ -27,7 +26,6 @@ pub enum ScopeKind {
27
26
 
28
27
  /// Scope information
29
28
  #[derive(Debug, Clone)]
30
- #[allow(dead_code)]
31
29
  pub struct Scope {
32
30
  pub id: ScopeId,
33
31
  pub kind: ScopeKind,
@@ -46,7 +44,6 @@ pub struct Scope {
46
44
  pub constants: HashMap<String, String>,
47
45
  }
48
46
 
49
- #[allow(dead_code)]
50
47
  impl Scope {
51
48
  pub fn new(id: ScopeId, kind: ScopeKind, parent: Option<ScopeId>) -> Self {
52
49
  Self {
@@ -89,7 +86,12 @@ pub struct ScopeManager {
89
86
  current_scope: ScopeId,
90
87
  }
91
88
 
92
- #[allow(dead_code)]
89
+ impl Default for ScopeManager {
90
+ fn default() -> Self {
91
+ Self::new()
92
+ }
93
+ }
94
+
93
95
  impl ScopeManager {
94
96
  pub fn new() -> Self {
95
97
  let top_level = Scope::new(ScopeId(0), ScopeKind::TopLevel, None);
@@ -243,6 +245,28 @@ impl ScopeManager {
243
245
  Some(path_segments.join("::"))
244
246
  }
245
247
 
248
+ /// Get current method name from nearest enclosing method scope
249
+ pub fn current_method_name(&self) -> Option<String> {
250
+ self.walk_scopes().find_map(|scope| {
251
+ if let ScopeKind::Method { name, .. } = &scope.kind {
252
+ Some(name.clone())
253
+ } else {
254
+ None
255
+ }
256
+ })
257
+ }
258
+
259
+ /// Get superclass name from nearest enclosing class scope
260
+ pub fn current_superclass(&self) -> Option<String> {
261
+ self.walk_scopes().find_map(|scope| {
262
+ if let ScopeKind::Class { superclass, .. } = &scope.kind {
263
+ superclass.clone()
264
+ } else {
265
+ None
266
+ }
267
+ })
268
+ }
269
+
246
270
  /// Get return_vertex from the nearest enclosing method scope
247
271
  pub fn current_method_return_vertex(&self) -> Option<VertexId> {
248
272
  self.walk_scopes().find_map(|scope| {
@@ -278,6 +302,13 @@ impl ScopeManager {
278
302
  mod tests {
279
303
  use super::*;
280
304
 
305
+ #[test]
306
+ fn test_scope_manager_default() {
307
+ let sm = ScopeManager::default();
308
+ assert_eq!(sm.current_scope().id, ScopeId(0));
309
+ assert!(matches!(sm.current_scope().kind, ScopeKind::TopLevel));
310
+ }
311
+
281
312
  #[test]
282
313
  fn test_scope_manager_creation() {
283
314
  let sm = ScopeManager::new();
@@ -584,4 +615,60 @@ mod tests {
584
615
  // Inside Admin scope, User should resolve to Admin::User
585
616
  assert_eq!(sm.lookup_constant("User"), Some("Admin::User".to_string()));
586
617
  }
618
+
619
+ #[test]
620
+ fn test_current_method_name() {
621
+ let mut sm = ScopeManager::new();
622
+ let class_id = sm.new_scope(ScopeKind::Class {
623
+ name: "User".to_string(),
624
+ superclass: None,
625
+ });
626
+ sm.enter_scope(class_id);
627
+ let method_id = sm.new_scope(ScopeKind::Method {
628
+ name: "greet".to_string(),
629
+ receiver_type: None,
630
+ return_vertex: None,
631
+ });
632
+ sm.enter_scope(method_id);
633
+ assert_eq!(sm.current_method_name(), Some("greet".to_string()));
634
+
635
+ sm.exit_scope();
636
+ assert_eq!(sm.current_method_name(), None);
637
+ }
638
+
639
+ #[test]
640
+ fn test_current_superclass() {
641
+ let mut sm = ScopeManager::new();
642
+ let class_id = sm.new_scope(ScopeKind::Class {
643
+ name: "Dog".to_string(),
644
+ superclass: Some("Animal".to_string()),
645
+ });
646
+ sm.enter_scope(class_id);
647
+ assert_eq!(sm.current_superclass(), Some("Animal".to_string()));
648
+
649
+ sm.exit_scope();
650
+ assert_eq!(sm.current_superclass(), None);
651
+ }
652
+
653
+ #[test]
654
+ fn test_current_method_name_from_nested_block() {
655
+ let mut sm = ScopeManager::new();
656
+ let class_id = sm.new_scope(ScopeKind::Class {
657
+ name: "User".to_string(),
658
+ superclass: Some("Base".to_string()),
659
+ });
660
+ sm.enter_scope(class_id);
661
+ let method_id = sm.new_scope(ScopeKind::Method {
662
+ name: "process".to_string(),
663
+ receiver_type: None,
664
+ return_vertex: None,
665
+ });
666
+ sm.enter_scope(method_id);
667
+ let block_id = sm.new_scope(ScopeKind::Block);
668
+ sm.enter_scope(block_id);
669
+
670
+ // Inside a block, should still find enclosing method/superclass
671
+ assert_eq!(sm.current_method_name(), Some("process".to_string()));
672
+ assert_eq!(sm.current_superclass(), Some("Base".to_string()));
673
+ }
587
674
  }
@@ -17,7 +17,6 @@ pub struct VertexManager {
17
17
  next_vertex_id: usize,
18
18
  }
19
19
 
20
- #[allow(dead_code)]
21
20
  impl VertexManager {
22
21
  /// Create a new empty vertex manager
23
22
  pub fn new() -> Self {