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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +30 -0
- data/ext/Cargo.toml +1 -1
- data/lib/methodray/version.rb +1 -1
- data/rust/Cargo.toml +1 -1
- data/rust/src/analyzer/assignments.rs +152 -0
- data/rust/src/analyzer/attributes.rs +2 -1
- data/rust/src/analyzer/blocks.rs +4 -3
- data/rust/src/analyzer/calls.rs +7 -3
- data/rust/src/analyzer/definitions.rs +10 -5
- data/rust/src/analyzer/dispatch.rs +265 -24
- data/rust/src/analyzer/exceptions.rs +521 -0
- data/rust/src/analyzer/install.rs +22 -1
- data/rust/src/analyzer/loops.rs +176 -0
- data/rust/src/analyzer/mod.rs +32 -0
- data/rust/src/analyzer/operators.rs +119 -27
- data/rust/src/analyzer/parameters.rs +60 -9
- data/rust/src/cache/rbs_cache.rs +0 -1
- data/rust/src/cli/commands.rs +3 -3
- data/rust/src/diagnostics/diagnostic.rs +0 -3
- data/rust/src/diagnostics/formatter.rs +0 -1
- data/rust/src/env/box_manager.rs +2 -4
- data/rust/src/env/global_env.rs +43 -5
- data/rust/src/env/local_env.rs +35 -1
- data/rust/src/env/method_registry.rs +140 -17
- data/rust/src/env/scope.rs +143 -164
- data/rust/src/env/vertex_manager.rs +0 -1
- data/rust/src/graph/box.rs +217 -84
- data/rust/src/graph/change_set.rs +14 -0
- data/rust/src/lsp/server.rs +1 -1
- data/rust/src/rbs/loader.rs +1 -2
- data/rust/src/source_map.rs +0 -1
- data/rust/src/types.rs +0 -1
- metadata +4 -1
data/rust/src/env/global_env.rs
CHANGED
|
@@ -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
|
-
|
|
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
|
|
data/rust/src/env/local_env.rs
CHANGED
|
@@ -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,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
|
|
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
|
-
///
|
|
82
|
+
/// Build the method resolution order (MRO) fallback chain for a receiver type.
|
|
74
83
|
///
|
|
75
|
-
///
|
|
76
|
-
///
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
89
|
-
|
|
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
|
-
|
|
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
|
}
|