method-ray 0.1.9 → 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 +20 -0
- data/README.md +9 -11
- data/core/Cargo.toml +1 -1
- data/core/src/analyzer/assignments.rs +0 -280
- data/core/src/analyzer/blocks.rs +0 -190
- data/core/src/analyzer/calls.rs +3 -32
- data/core/src/analyzer/conditionals.rs +0 -348
- data/core/src/analyzer/definitions.rs +11 -526
- data/core/src/analyzer/dispatch.rs +54 -1000
- data/core/src/analyzer/exceptions.rs +0 -454
- data/core/src/analyzer/literals.rs +0 -54
- data/core/src/analyzer/loops.rs +0 -207
- data/core/src/analyzer/mod.rs +0 -15
- data/core/src/analyzer/operators.rs +0 -205
- data/core/src/analyzer/parameters.rs +4 -227
- data/core/src/analyzer/parentheses.rs +0 -88
- data/core/src/analyzer/returns.rs +0 -152
- data/core/src/analyzer/super_calls.rs +1 -212
- data/core/src/analyzer/variables.rs +5 -25
- data/core/src/checker.rs +0 -13
- data/core/src/diagnostics/diagnostic.rs +0 -41
- data/core/src/diagnostics/formatter.rs +0 -38
- data/core/src/env/box_manager.rs +0 -30
- data/core/src/env/global_env.rs +52 -79
- data/core/src/env/local_env.rs +0 -50
- data/core/src/env/method_registry.rs +52 -233
- data/core/src/env/scope.rs +0 -375
- data/core/src/env/vertex_manager.rs +0 -73
- data/core/src/graph/box.rs +20 -439
- data/core/src/graph/change_set.rs +0 -65
- data/core/src/graph/vertex.rs +0 -69
- data/core/src/parser.rs +0 -77
- data/ext/Cargo.toml +1 -1
- data/lib/methodray/version.rb +1 -1
- metadata +5 -4
data/core/src/env/global_env.rs
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
use std::collections::HashMap;
|
|
7
7
|
|
|
8
8
|
use crate::env::box_manager::BoxManager;
|
|
9
|
-
use crate::env::method_registry::{MethodInfo, MethodRegistry};
|
|
9
|
+
use crate::env::method_registry::{MethodInfo, MethodRegistry, ResolutionContext};
|
|
10
10
|
use crate::env::scope::{Scope, ScopeId, ScopeKind, ScopeManager};
|
|
11
11
|
use crate::env::type_error::TypeError;
|
|
12
12
|
use crate::env::vertex_manager::VertexManager;
|
|
@@ -41,6 +41,11 @@ pub struct GlobalEnv {
|
|
|
41
41
|
/// Module inclusions: class_name → Vec<module_name> (in include order)
|
|
42
42
|
module_inclusions: HashMap<String, Vec<String>>,
|
|
43
43
|
|
|
44
|
+
/// Superclass map: child_class → parent_class
|
|
45
|
+
superclass_map: HashMap<String, String>,
|
|
46
|
+
|
|
47
|
+
/// Module extensions: class_name → Vec<module_name> (in extend order)
|
|
48
|
+
module_extensions: HashMap<String, Vec<String>>,
|
|
44
49
|
}
|
|
45
50
|
|
|
46
51
|
impl GlobalEnv {
|
|
@@ -52,6 +57,8 @@ impl GlobalEnv {
|
|
|
52
57
|
type_errors: Vec::new(),
|
|
53
58
|
scope_manager: ScopeManager::new(),
|
|
54
59
|
module_inclusions: HashMap::new(),
|
|
60
|
+
superclass_map: HashMap::new(),
|
|
61
|
+
module_extensions: HashMap::new(),
|
|
55
62
|
}
|
|
56
63
|
}
|
|
57
64
|
|
|
@@ -163,14 +170,31 @@ impl GlobalEnv {
|
|
|
163
170
|
|
|
164
171
|
/// Resolve method
|
|
165
172
|
pub fn resolve_method(&self, recv_ty: &Type, method_name: &str) -> Option<&MethodInfo> {
|
|
173
|
+
let ctx = ResolutionContext {
|
|
174
|
+
inclusions: &self.module_inclusions,
|
|
175
|
+
superclass_map: &self.superclass_map,
|
|
176
|
+
extensions: &self.module_extensions,
|
|
177
|
+
};
|
|
166
178
|
self.method_registry
|
|
167
|
-
.resolve(recv_ty, method_name, &
|
|
179
|
+
.resolve(recv_ty, method_name, &ctx)
|
|
168
180
|
}
|
|
169
181
|
|
|
170
182
|
/// Record that a class includes a module
|
|
171
183
|
pub fn record_include(&mut self, class_name: &str, module_name: &str) {
|
|
172
|
-
self.module_inclusions
|
|
173
|
-
|
|
184
|
+
Self::record_mixin(&mut self.module_inclusions, class_name, module_name);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/// Record that a class extends a module
|
|
188
|
+
pub fn record_extend(&mut self, class_name: &str, module_name: &str) {
|
|
189
|
+
Self::record_mixin(&mut self.module_extensions, class_name, module_name);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
fn record_mixin(
|
|
193
|
+
map: &mut HashMap<String, Vec<String>>,
|
|
194
|
+
class_name: &str,
|
|
195
|
+
module_name: &str,
|
|
196
|
+
) {
|
|
197
|
+
map.entry(class_name.to_string())
|
|
174
198
|
.or_default()
|
|
175
199
|
.push(module_name.to_string());
|
|
176
200
|
}
|
|
@@ -247,6 +271,30 @@ impl GlobalEnv {
|
|
|
247
271
|
});
|
|
248
272
|
self.scope_manager.enter_scope(scope_id);
|
|
249
273
|
self.register_constant_in_parent(scope_id, &name);
|
|
274
|
+
|
|
275
|
+
// Record superclass relationship in superclass_map
|
|
276
|
+
if let Some(parent) = superclass {
|
|
277
|
+
let child_name = self.scope_manager.current_qualified_name()
|
|
278
|
+
.unwrap_or_else(|| name.clone());
|
|
279
|
+
// NOTE: lookup_constant may fail for cross-namespace inheritance
|
|
280
|
+
// (e.g., `class Dog < Animal` inside `module Service` where Animal is `Api::Animal`).
|
|
281
|
+
// In that case, the raw name is used. This is a known limitation (see design doc Q2).
|
|
282
|
+
let parent_name = self.scope_manager.lookup_constant(parent)
|
|
283
|
+
.unwrap_or_else(|| parent.to_string());
|
|
284
|
+
|
|
285
|
+
// Detect superclass mismatch (Ruby raises TypeError for this at runtime)
|
|
286
|
+
if let Some(existing) = self.superclass_map.get(&child_name) {
|
|
287
|
+
if *existing != parent_name {
|
|
288
|
+
eprintln!(
|
|
289
|
+
"[methodray] warning: superclass mismatch for {}: previously {}, now {}",
|
|
290
|
+
child_name, existing, parent_name
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
self.superclass_map.insert(child_name, parent_name);
|
|
296
|
+
}
|
|
297
|
+
|
|
250
298
|
scope_id
|
|
251
299
|
}
|
|
252
300
|
|
|
@@ -293,78 +341,3 @@ impl Default for GlobalEnv {
|
|
|
293
341
|
Self::new()
|
|
294
342
|
}
|
|
295
343
|
}
|
|
296
|
-
|
|
297
|
-
#[cfg(test)]
|
|
298
|
-
mod tests {
|
|
299
|
-
use super::*;
|
|
300
|
-
|
|
301
|
-
#[test]
|
|
302
|
-
fn test_global_env_new_vertex() {
|
|
303
|
-
let mut genv = GlobalEnv::new();
|
|
304
|
-
|
|
305
|
-
let v1 = genv.new_vertex();
|
|
306
|
-
let v2 = genv.new_vertex();
|
|
307
|
-
|
|
308
|
-
assert_eq!(v1.0, 0);
|
|
309
|
-
assert_eq!(v2.0, 1);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
#[test]
|
|
313
|
-
fn test_global_env_new_source() {
|
|
314
|
-
let mut genv = GlobalEnv::new();
|
|
315
|
-
|
|
316
|
-
let s1 = genv.new_source(Type::string());
|
|
317
|
-
let s2 = genv.new_source(Type::integer());
|
|
318
|
-
|
|
319
|
-
assert_eq!(genv.get_source(s1).unwrap().ty.show(), "String");
|
|
320
|
-
assert_eq!(genv.get_source(s2).unwrap().ty.show(), "Integer");
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
#[test]
|
|
324
|
-
fn test_global_env_edge_propagation() {
|
|
325
|
-
let mut genv = GlobalEnv::new();
|
|
326
|
-
|
|
327
|
-
// Source<String> -> Vertex
|
|
328
|
-
let src = genv.new_source(Type::string());
|
|
329
|
-
let vtx = genv.new_vertex();
|
|
330
|
-
|
|
331
|
-
genv.add_edge(src, vtx);
|
|
332
|
-
|
|
333
|
-
// Verify type propagated to Vertex
|
|
334
|
-
assert_eq!(genv.get_vertex(vtx).unwrap().show(), "String");
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
#[test]
|
|
338
|
-
fn test_global_env_chain_propagation() {
|
|
339
|
-
let mut genv = GlobalEnv::new();
|
|
340
|
-
|
|
341
|
-
// Source<String> -> Vertex1 -> Vertex2
|
|
342
|
-
let src = genv.new_source(Type::string());
|
|
343
|
-
let v1 = genv.new_vertex();
|
|
344
|
-
let v2 = genv.new_vertex();
|
|
345
|
-
|
|
346
|
-
genv.add_edge(src, v1);
|
|
347
|
-
genv.add_edge(v1, v2);
|
|
348
|
-
|
|
349
|
-
// Verify type propagated to v2
|
|
350
|
-
assert_eq!(genv.get_vertex(v1).unwrap().show(), "String");
|
|
351
|
-
assert_eq!(genv.get_vertex(v2).unwrap().show(), "String");
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
#[test]
|
|
355
|
-
fn test_global_env_union_propagation() {
|
|
356
|
-
let mut genv = GlobalEnv::new();
|
|
357
|
-
|
|
358
|
-
// Source<String> -> Vertex
|
|
359
|
-
// Source<Integer> -> Vertex
|
|
360
|
-
let src1 = genv.new_source(Type::string());
|
|
361
|
-
let src2 = genv.new_source(Type::integer());
|
|
362
|
-
let vtx = genv.new_vertex();
|
|
363
|
-
|
|
364
|
-
genv.add_edge(src1, vtx);
|
|
365
|
-
genv.add_edge(src2, vtx);
|
|
366
|
-
|
|
367
|
-
// Verify it became Union type
|
|
368
|
-
assert_eq!(genv.get_vertex(vtx).unwrap().show(), "(Integer | String)");
|
|
369
|
-
}
|
|
370
|
-
}
|
data/core/src/env/local_env.rs
CHANGED
|
@@ -40,53 +40,3 @@ impl LocalEnv {
|
|
|
40
40
|
self.locals.iter()
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
|
-
|
|
44
|
-
#[cfg(test)]
|
|
45
|
-
mod tests {
|
|
46
|
-
use super::*;
|
|
47
|
-
|
|
48
|
-
#[test]
|
|
49
|
-
fn test_local_env_default() {
|
|
50
|
-
let lenv = LocalEnv::default();
|
|
51
|
-
assert_eq!(lenv.get_var("x"), None);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
#[test]
|
|
55
|
-
fn test_local_env() {
|
|
56
|
-
let mut lenv = LocalEnv::new();
|
|
57
|
-
|
|
58
|
-
lenv.new_var("x".to_string(), VertexId(1));
|
|
59
|
-
lenv.new_var("y".to_string(), VertexId(2));
|
|
60
|
-
|
|
61
|
-
assert_eq!(lenv.get_var("x"), Some(VertexId(1)));
|
|
62
|
-
assert_eq!(lenv.get_var("y"), Some(VertexId(2)));
|
|
63
|
-
assert_eq!(lenv.get_var("z"), None);
|
|
64
|
-
}
|
|
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
|
-
|
|
83
|
-
#[test]
|
|
84
|
-
fn test_local_env_override() {
|
|
85
|
-
let mut lenv = LocalEnv::new();
|
|
86
|
-
|
|
87
|
-
lenv.new_var("x".to_string(), VertexId(1));
|
|
88
|
-
lenv.new_var("x".to_string(), VertexId(2)); // Override
|
|
89
|
-
|
|
90
|
-
assert_eq!(lenv.get_var("x"), Some(VertexId(2)));
|
|
91
|
-
}
|
|
92
|
-
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
//! Method registration and resolution
|
|
2
2
|
|
|
3
|
-
use std::collections::HashMap;
|
|
3
|
+
use std::collections::{HashMap, HashSet};
|
|
4
4
|
|
|
5
5
|
use crate::graph::VertexId;
|
|
6
6
|
use crate::types::Type;
|
|
@@ -9,6 +9,13 @@ use smallvec::SmallVec;
|
|
|
9
9
|
const OBJECT_CLASS: &str = "Object";
|
|
10
10
|
const KERNEL_MODULE: &str = "Kernel";
|
|
11
11
|
|
|
12
|
+
/// Aggregated context for method resolution (inclusions, superclass chain, extensions)
|
|
13
|
+
pub struct ResolutionContext<'a> {
|
|
14
|
+
pub inclusions: &'a HashMap<String, Vec<String>>,
|
|
15
|
+
pub superclass_map: &'a HashMap<String, String>,
|
|
16
|
+
pub extensions: &'a HashMap<String, Vec<String>>,
|
|
17
|
+
}
|
|
18
|
+
|
|
12
19
|
/// Method information
|
|
13
20
|
#[derive(Debug, Clone)]
|
|
14
21
|
pub struct MethodInfo {
|
|
@@ -79,17 +86,32 @@ impl MethodRegistry {
|
|
|
79
86
|
);
|
|
80
87
|
}
|
|
81
88
|
|
|
89
|
+
/// Add included modules for a given class name to the chain.
|
|
90
|
+
fn add_included_modules(
|
|
91
|
+
chain: &mut SmallVec<[Type; 8]>,
|
|
92
|
+
class_name: &str,
|
|
93
|
+
inclusions: &HashMap<String, Vec<String>>,
|
|
94
|
+
) {
|
|
95
|
+
if let Some(modules) = inclusions.get(class_name) {
|
|
96
|
+
for module_name in modules.iter().rev() {
|
|
97
|
+
chain.push(Type::instance(module_name));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
82
102
|
/// Build the method resolution order (MRO) fallback chain for a receiver type.
|
|
83
103
|
///
|
|
84
104
|
/// Returns a list of types to search in order:
|
|
85
105
|
/// 1. Exact receiver type
|
|
86
106
|
/// 2. Generic → base class (e.g., Array[Integer] → Array)
|
|
87
|
-
/// 3. Included modules (last included first, matching Ruby MRO)
|
|
88
|
-
/// 4.
|
|
89
|
-
/// 5.
|
|
107
|
+
/// 3. Included modules of self (last included first, matching Ruby MRO)
|
|
108
|
+
/// 4. Superclass chain: for each parent, add parent type + its included modules
|
|
109
|
+
/// 5. Object (for Instance/Generic types only)
|
|
110
|
+
/// 6. Kernel (for Instance/Generic types only)
|
|
111
|
+
/// 7. Extended modules (for Singleton types only, last extended has highest priority)
|
|
90
112
|
fn fallback_chain(
|
|
91
113
|
recv_ty: &Type,
|
|
92
|
-
|
|
114
|
+
ctx: &ResolutionContext,
|
|
93
115
|
) -> SmallVec<[Type; 8]> {
|
|
94
116
|
let mut chain = SmallVec::new();
|
|
95
117
|
chain.push(recv_ty.clone());
|
|
@@ -98,14 +120,24 @@ impl MethodRegistry {
|
|
|
98
120
|
chain.push(Type::Instance { name: name.clone() });
|
|
99
121
|
}
|
|
100
122
|
|
|
101
|
-
// MRO for Instance/Generic: included modules → Object → Kernel
|
|
123
|
+
// MRO for Instance/Generic: included modules → superclass chain → Object → Kernel
|
|
102
124
|
if matches!(recv_ty, Type::Instance { .. } | Type::Generic { .. }) {
|
|
103
|
-
// Included modules (reverse order = last included has highest priority)
|
|
125
|
+
// Included modules of self (reverse order = last included has highest priority)
|
|
104
126
|
if let Some(class_name) = recv_ty.base_class_name() {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
127
|
+
Self::add_included_modules(&mut chain, class_name, ctx.inclusions);
|
|
128
|
+
|
|
129
|
+
// Walk superclass chain
|
|
130
|
+
let mut visited = HashSet::new();
|
|
131
|
+
visited.insert(class_name.to_string());
|
|
132
|
+
let mut current = class_name.to_string();
|
|
133
|
+
while let Some(parent) = ctx.superclass_map.get(¤t) {
|
|
134
|
+
if !visited.insert(parent.clone()) {
|
|
135
|
+
// Cycle detected, stop walking
|
|
136
|
+
break;
|
|
108
137
|
}
|
|
138
|
+
chain.push(Type::instance(parent));
|
|
139
|
+
Self::add_included_modules(&mut chain, parent, ctx.inclusions);
|
|
140
|
+
current = parent.clone();
|
|
109
141
|
}
|
|
110
142
|
}
|
|
111
143
|
|
|
@@ -113,242 +145,29 @@ impl MethodRegistry {
|
|
|
113
145
|
chain.push(Type::instance(KERNEL_MODULE));
|
|
114
146
|
}
|
|
115
147
|
|
|
148
|
+
// Singleton type: search extended modules (extend makes module methods available as class methods)
|
|
149
|
+
if let Type::Singleton { name } = recv_ty {
|
|
150
|
+
Self::add_included_modules(&mut chain, name.full_name(), ctx.extensions);
|
|
151
|
+
}
|
|
152
|
+
|
|
116
153
|
chain
|
|
117
154
|
}
|
|
118
155
|
|
|
119
156
|
/// Resolve a method for a receiver type
|
|
120
157
|
///
|
|
121
158
|
/// Searches the MRO fallback chain: exact type → base class (for generics)
|
|
122
|
-
/// → included modules → Object → Kernel.
|
|
123
|
-
/// For
|
|
159
|
+
/// → included modules → superclass chain → Object → Kernel.
|
|
160
|
+
/// For Singleton types, also searches extended modules after exact match.
|
|
161
|
+
/// For other non-instance types (Nil, Union, Bot), only exact match is attempted.
|
|
124
162
|
pub fn resolve(
|
|
125
163
|
&self,
|
|
126
164
|
recv_ty: &Type,
|
|
127
165
|
method_name: &str,
|
|
128
|
-
|
|
166
|
+
ctx: &ResolutionContext,
|
|
129
167
|
) -> Option<&MethodInfo> {
|
|
130
168
|
let method_key = method_name.to_string();
|
|
131
|
-
Self::fallback_chain(recv_ty,
|
|
169
|
+
Self::fallback_chain(recv_ty, ctx)
|
|
132
170
|
.into_iter()
|
|
133
171
|
.find_map(|ty| self.methods.get(&(ty, method_key.clone())))
|
|
134
172
|
}
|
|
135
173
|
}
|
|
136
|
-
|
|
137
|
-
#[cfg(test)]
|
|
138
|
-
mod tests {
|
|
139
|
-
use super::*;
|
|
140
|
-
|
|
141
|
-
#[test]
|
|
142
|
-
fn test_register_and_resolve() {
|
|
143
|
-
let mut registry = MethodRegistry::new();
|
|
144
|
-
registry.register(Type::string(), "length", Type::integer());
|
|
145
|
-
|
|
146
|
-
let info = registry.resolve(&Type::string(), "length", &HashMap::new()).unwrap();
|
|
147
|
-
assert_eq!(info.return_type.base_class_name(), Some("Integer"));
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
#[test]
|
|
151
|
-
fn test_resolve_not_found() {
|
|
152
|
-
let registry = MethodRegistry::new();
|
|
153
|
-
assert!(registry.resolve(&Type::string(), "unknown", &HashMap::new()).is_none());
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
#[test]
|
|
157
|
-
fn test_register_user_method_and_resolve() {
|
|
158
|
-
let mut registry = MethodRegistry::new();
|
|
159
|
-
let return_vtx = VertexId(42);
|
|
160
|
-
registry.register_user_method(Type::instance("User"), "name", return_vtx, vec![], None);
|
|
161
|
-
|
|
162
|
-
let info = registry.resolve(&Type::instance("User"), "name", &HashMap::new()).unwrap();
|
|
163
|
-
assert_eq!(info.return_vertex, Some(VertexId(42)));
|
|
164
|
-
assert_eq!(info.return_type, Type::Bot);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
#[test]
|
|
168
|
-
fn test_register_user_method_with_param_vertices() {
|
|
169
|
-
let mut registry = MethodRegistry::new();
|
|
170
|
-
let return_vtx = VertexId(10);
|
|
171
|
-
let param_vtxs = vec![VertexId(20), VertexId(21)];
|
|
172
|
-
registry.register_user_method(
|
|
173
|
-
Type::instance("Calc"),
|
|
174
|
-
"add",
|
|
175
|
-
return_vtx,
|
|
176
|
-
param_vtxs,
|
|
177
|
-
None,
|
|
178
|
-
);
|
|
179
|
-
|
|
180
|
-
let info = registry.resolve(&Type::instance("Calc"), "add", &HashMap::new()).unwrap();
|
|
181
|
-
assert_eq!(info.return_vertex, Some(VertexId(10)));
|
|
182
|
-
let pvs = info.param_vertices.as_ref().unwrap();
|
|
183
|
-
assert_eq!(pvs.len(), 2);
|
|
184
|
-
assert_eq!(pvs[0], VertexId(20));
|
|
185
|
-
assert_eq!(pvs[1], VertexId(21));
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// --- Object/Kernel fallback ---
|
|
189
|
-
|
|
190
|
-
#[test]
|
|
191
|
-
fn test_resolve_falls_back_to_object() {
|
|
192
|
-
let mut registry = MethodRegistry::new();
|
|
193
|
-
registry.register(Type::instance("Object"), "nil?", Type::instance("TrueClass"));
|
|
194
|
-
let info = registry.resolve(&Type::instance("CustomClass"), "nil?", &HashMap::new()).unwrap();
|
|
195
|
-
assert_eq!(info.return_type.base_class_name(), Some("TrueClass"));
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
#[test]
|
|
199
|
-
fn test_resolve_falls_back_to_kernel() {
|
|
200
|
-
let mut registry = MethodRegistry::new();
|
|
201
|
-
registry.register(Type::instance("Kernel"), "puts", Type::Nil);
|
|
202
|
-
let info = registry.resolve(&Type::instance("MyApp"), "puts", &HashMap::new()).unwrap();
|
|
203
|
-
assert_eq!(info.return_type, Type::Nil);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
#[test]
|
|
207
|
-
fn test_resolve_object_before_kernel() {
|
|
208
|
-
let mut registry = MethodRegistry::new();
|
|
209
|
-
registry.register(Type::instance("Object"), "to_s", Type::string());
|
|
210
|
-
registry.register(Type::instance("Kernel"), "to_s", Type::integer());
|
|
211
|
-
let info = registry.resolve(&Type::instance("Anything"), "to_s", &HashMap::new()).unwrap();
|
|
212
|
-
assert_eq!(info.return_type.base_class_name(), Some("String"));
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
#[test]
|
|
216
|
-
fn test_resolve_exact_match_over_fallback() {
|
|
217
|
-
let mut registry = MethodRegistry::new();
|
|
218
|
-
registry.register(Type::string(), "length", Type::integer());
|
|
219
|
-
registry.register(Type::instance("Object"), "length", Type::string());
|
|
220
|
-
let info = registry.resolve(&Type::string(), "length", &HashMap::new()).unwrap();
|
|
221
|
-
assert_eq!(info.return_type.base_class_name(), Some("Integer"));
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// --- Types that skip fallback ---
|
|
225
|
-
|
|
226
|
-
#[test]
|
|
227
|
-
fn test_singleton_type_skips_fallback() {
|
|
228
|
-
let mut registry = MethodRegistry::new();
|
|
229
|
-
registry.register(Type::instance("Kernel"), "puts", Type::Nil);
|
|
230
|
-
assert!(registry.resolve(&Type::singleton("User"), "puts", &HashMap::new()).is_none());
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
#[test]
|
|
234
|
-
fn test_nil_type_skips_fallback() {
|
|
235
|
-
let mut registry = MethodRegistry::new();
|
|
236
|
-
registry.register(Type::instance("Kernel"), "puts", Type::Nil);
|
|
237
|
-
assert!(registry.resolve(&Type::Nil, "puts", &HashMap::new()).is_none());
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
#[test]
|
|
241
|
-
fn test_union_type_skips_fallback() {
|
|
242
|
-
let mut registry = MethodRegistry::new();
|
|
243
|
-
registry.register(Type::instance("Kernel"), "puts", Type::Nil);
|
|
244
|
-
let union_ty = Type::Union(vec![Type::string(), Type::integer()]);
|
|
245
|
-
assert!(registry.resolve(&union_ty, "puts", &HashMap::new()).is_none());
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
#[test]
|
|
249
|
-
fn test_bot_type_skips_fallback() {
|
|
250
|
-
let mut registry = MethodRegistry::new();
|
|
251
|
-
registry.register(Type::instance("Kernel"), "puts", Type::Nil);
|
|
252
|
-
assert!(registry.resolve(&Type::Bot, "puts", &HashMap::new()).is_none());
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// --- Generic type fallback chain ---
|
|
256
|
-
|
|
257
|
-
#[test]
|
|
258
|
-
fn test_resolve_generic_falls_back_to_kernel() {
|
|
259
|
-
let mut registry = MethodRegistry::new();
|
|
260
|
-
registry.register(Type::instance("Kernel"), "puts", Type::Nil);
|
|
261
|
-
let generic_type = Type::array_of(Type::integer());
|
|
262
|
-
let info = registry.resolve(&generic_type, "puts", &HashMap::new()).unwrap();
|
|
263
|
-
assert_eq!(info.return_type, Type::Nil);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
#[test]
|
|
267
|
-
fn test_resolve_generic_full_chain() {
|
|
268
|
-
// Verify the 4-step fallback: Generic[T] → Base → Object → Kernel
|
|
269
|
-
let mut registry = MethodRegistry::new();
|
|
270
|
-
registry.register(Type::instance("Kernel"), "object_id", Type::integer());
|
|
271
|
-
let generic_type = Type::array_of(Type::string());
|
|
272
|
-
// Array[String] → Array (none) → Object (none) → Kernel (exists)
|
|
273
|
-
let info = registry.resolve(&generic_type, "object_id", &HashMap::new()).unwrap();
|
|
274
|
-
assert_eq!(info.return_type.base_class_name(), Some("Integer"));
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// --- Namespaced class fallback ---
|
|
278
|
-
|
|
279
|
-
#[test]
|
|
280
|
-
fn test_resolve_namespaced_class_falls_back_to_object() {
|
|
281
|
-
let mut registry = MethodRegistry::new();
|
|
282
|
-
registry.register(Type::instance("Object"), "class", Type::string());
|
|
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();
|
|
298
|
-
assert_eq!(info.return_type.base_class_name(), Some("String"));
|
|
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
|
-
}
|
|
354
|
-
}
|