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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +33 -0
- data/{rust → core}/Cargo.toml +1 -1
- data/core/src/analyzer/assignments.rs +499 -0
- data/{rust → core}/src/analyzer/attributes.rs +2 -1
- data/{rust → core}/src/analyzer/blocks.rs +140 -0
- data/{rust → core}/src/analyzer/calls.rs +7 -3
- data/{rust → core}/src/analyzer/definitions.rs +12 -7
- data/{rust → core}/src/analyzer/dispatch.rs +431 -13
- data/core/src/analyzer/exceptions.rs +622 -0
- data/{rust → core}/src/analyzer/install.rs +37 -1
- data/{rust → core}/src/analyzer/literals.rs +3 -17
- data/core/src/analyzer/loops.rs +301 -0
- data/{rust → core}/src/analyzer/mod.rs +4 -0
- data/{rust → core}/src/analyzer/operators.rs +119 -27
- data/{rust → core}/src/analyzer/parameters.rs +214 -5
- data/core/src/analyzer/super_calls.rs +285 -0
- data/{rust → core}/src/cache/rbs_cache.rs +0 -1
- data/{rust → core}/src/cli/commands.rs +3 -3
- data/{rust → core}/src/diagnostics/diagnostic.rs +0 -3
- data/{rust → core}/src/diagnostics/formatter.rs +0 -1
- data/{rust → core}/src/env/box_manager.rs +2 -4
- data/{rust → core}/src/env/global_env.rs +28 -7
- data/{rust → core}/src/env/local_env.rs +35 -1
- data/{rust → core}/src/env/method_registry.rs +117 -25
- data/{rust → core}/src/env/scope.rs +91 -4
- data/{rust → core}/src/env/vertex_manager.rs +0 -1
- data/{rust → core}/src/graph/box.rs +134 -8
- data/{rust → core}/src/graph/change_set.rs +14 -0
- data/{rust → core}/src/lsp/server.rs +1 -1
- data/{rust → core}/src/rbs/loader.rs +1 -2
- data/{rust → core}/src/source_map.rs +0 -1
- data/{rust → core}/src/types.rs +11 -1
- data/ext/Cargo.toml +2 -2
- data/lib/methodray/binary_locator.rb +2 -2
- data/lib/methodray/commands.rb +1 -1
- data/lib/methodray/version.rb +1 -1
- metadata +54 -50
- /data/{rust → core}/src/analyzer/conditionals.rs +0 -0
- /data/{rust → core}/src/analyzer/parentheses.rs +0 -0
- /data/{rust → core}/src/analyzer/returns.rs +0 -0
- /data/{rust → core}/src/analyzer/variables.rs +0 -0
- /data/{rust → core}/src/cache/mod.rs +0 -0
- /data/{rust → core}/src/checker.rs +0 -0
- /data/{rust → core}/src/cli/args.rs +0 -0
- /data/{rust → core}/src/cli/mod.rs +0 -0
- /data/{rust → core}/src/diagnostics/mod.rs +0 -0
- /data/{rust → core}/src/env/mod.rs +0 -0
- /data/{rust → core}/src/env/type_error.rs +0 -0
- /data/{rust → core}/src/graph/mod.rs +0 -0
- /data/{rust → core}/src/graph/vertex.rs +0 -0
- /data/{rust → core}/src/lib.rs +0 -0
- /data/{rust → core}/src/lsp/diagnostics.rs +0 -0
- /data/{rust → core}/src/lsp/main.rs +0 -0
- /data/{rust → core}/src/lsp/mod.rs +0 -0
- /data/{rust → core}/src/main.rs +0 -0
- /data/{rust → core}/src/parser.rs +0 -0
- /data/{rust → core}/src/rbs/converter.rs +0 -0
- /data/{rust → core}/src/rbs/error.rs +0 -0
- /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
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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.
|
|
83
|
-
/// 4.
|
|
84
|
-
|
|
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
|
-
//
|
|
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)
|
|
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(
|
|
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
|
-
|
|
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
|
}
|