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/scope.rs
CHANGED
|
@@ -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,
|
|
@@ -41,9 +39,11 @@ pub struct Scope {
|
|
|
41
39
|
|
|
42
40
|
/// Class variables (class scope only)
|
|
43
41
|
pub class_vars: HashMap<String, VertexId>,
|
|
42
|
+
|
|
43
|
+
/// Constants (simple name → qualified name)
|
|
44
|
+
pub constants: HashMap<String, String>,
|
|
44
45
|
}
|
|
45
46
|
|
|
46
|
-
#[allow(dead_code)]
|
|
47
47
|
impl Scope {
|
|
48
48
|
pub fn new(id: ScopeId, kind: ScopeKind, parent: Option<ScopeId>) -> Self {
|
|
49
49
|
Self {
|
|
@@ -53,6 +53,7 @@ impl Scope {
|
|
|
53
53
|
local_vars: HashMap::new(),
|
|
54
54
|
instance_vars: HashMap::new(),
|
|
55
55
|
class_vars: HashMap::new(),
|
|
56
|
+
constants: HashMap::new(),
|
|
56
57
|
}
|
|
57
58
|
}
|
|
58
59
|
|
|
@@ -85,7 +86,12 @@ pub struct ScopeManager {
|
|
|
85
86
|
current_scope: ScopeId,
|
|
86
87
|
}
|
|
87
88
|
|
|
88
|
-
|
|
89
|
+
impl Default for ScopeManager {
|
|
90
|
+
fn default() -> Self {
|
|
91
|
+
Self::new()
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
89
95
|
impl ScopeManager {
|
|
90
96
|
pub fn new() -> Self {
|
|
91
97
|
let top_level = Scope::new(ScopeId(0), ScopeKind::TopLevel, None);
|
|
@@ -135,6 +141,18 @@ impl ScopeManager {
|
|
|
135
141
|
self.scopes.get_mut(&self.current_scope).unwrap()
|
|
136
142
|
}
|
|
137
143
|
|
|
144
|
+
/// Walk scopes from current scope up to the top-level, yielding each scope
|
|
145
|
+
fn walk_scopes(&self) -> impl Iterator<Item = &Scope> + '_ {
|
|
146
|
+
let scopes = &self.scopes;
|
|
147
|
+
let mut current = Some(self.current_scope);
|
|
148
|
+
std::iter::from_fn(move || {
|
|
149
|
+
let scope_id = current?;
|
|
150
|
+
let scope = scopes.get(&scope_id)?;
|
|
151
|
+
current = scope.parent;
|
|
152
|
+
Some(scope)
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
|
|
138
156
|
/// Get scope by ID
|
|
139
157
|
pub fn get_scope(&self, id: ScopeId) -> Option<&Scope> {
|
|
140
158
|
self.scopes.get(&id)
|
|
@@ -147,103 +165,54 @@ impl ScopeManager {
|
|
|
147
165
|
|
|
148
166
|
/// Lookup variable in current scope or parent scopes
|
|
149
167
|
pub fn lookup_var(&self, name: &str) -> Option<VertexId> {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
while let Some(scope_id) = current {
|
|
153
|
-
if let Some(scope) = self.scopes.get(&scope_id) {
|
|
154
|
-
if let Some(vtx) = scope.get_local_var(name) {
|
|
155
|
-
return Some(vtx);
|
|
156
|
-
}
|
|
157
|
-
current = scope.parent;
|
|
158
|
-
} else {
|
|
159
|
-
break;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
168
|
+
self.walk_scopes().find_map(|scope| scope.get_local_var(name))
|
|
169
|
+
}
|
|
162
170
|
|
|
163
|
-
|
|
171
|
+
/// Lookup constant in current scope or parent scopes (simple name → qualified name)
|
|
172
|
+
pub fn lookup_constant(&self, simple_name: &str) -> Option<String> {
|
|
173
|
+
self.walk_scopes()
|
|
174
|
+
.find_map(|scope| scope.constants.get(simple_name).cloned())
|
|
164
175
|
}
|
|
165
176
|
|
|
166
177
|
/// Lookup instance variable in enclosing class scope
|
|
167
178
|
pub fn lookup_instance_var(&self, name: &str) -> Option<VertexId> {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if let Some(scope) = self.scopes.get(&scope_id) {
|
|
172
|
-
// Walk up to class scope
|
|
173
|
-
match &scope.kind {
|
|
174
|
-
ScopeKind::Class { .. } => {
|
|
175
|
-
return scope.get_instance_var(name);
|
|
176
|
-
}
|
|
177
|
-
_ => {
|
|
178
|
-
current = scope.parent;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
} else {
|
|
182
|
-
break;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
None
|
|
179
|
+
self.walk_scopes()
|
|
180
|
+
.find(|scope| matches!(&scope.kind, ScopeKind::Class { .. }))
|
|
181
|
+
.and_then(|scope| scope.get_instance_var(name))
|
|
187
182
|
}
|
|
188
183
|
|
|
189
184
|
/// Set instance variable in enclosing class scope
|
|
190
185
|
pub fn set_instance_var_in_class(&mut self, name: String, vtx: VertexId) {
|
|
191
|
-
let
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
ScopeKind::Class { .. } => {
|
|
198
|
-
if let Some(class_scope) = self.scopes.get_mut(&scope_id) {
|
|
199
|
-
class_scope.set_instance_var(name, vtx);
|
|
200
|
-
}
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
_ => {
|
|
204
|
-
current = scope.parent;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
} else {
|
|
208
|
-
break;
|
|
186
|
+
let class_scope_id = self.walk_scopes()
|
|
187
|
+
.find(|scope| matches!(&scope.kind, ScopeKind::Class { .. }))
|
|
188
|
+
.map(|scope| scope.id);
|
|
189
|
+
if let Some(scope_id) = class_scope_id {
|
|
190
|
+
if let Some(scope) = self.scopes.get_mut(&scope_id) {
|
|
191
|
+
scope.set_instance_var(name, vtx);
|
|
209
192
|
}
|
|
210
193
|
}
|
|
211
194
|
}
|
|
212
195
|
|
|
213
196
|
/// Get current class name (simple name, not qualified)
|
|
214
197
|
pub fn current_class_name(&self) -> Option<String> {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
if let Some(scope) = self.scopes.get(&scope_id) {
|
|
219
|
-
if let ScopeKind::Class { name, .. } = &scope.kind {
|
|
220
|
-
return Some(name.clone());
|
|
221
|
-
}
|
|
222
|
-
current = scope.parent;
|
|
198
|
+
self.walk_scopes().find_map(|scope| {
|
|
199
|
+
if let ScopeKind::Class { name, .. } = &scope.kind {
|
|
200
|
+
Some(name.clone())
|
|
223
201
|
} else {
|
|
224
|
-
|
|
202
|
+
None
|
|
225
203
|
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
None
|
|
204
|
+
})
|
|
229
205
|
}
|
|
230
206
|
|
|
231
207
|
/// Get current module name (simple name, not qualified)
|
|
232
208
|
pub fn current_module_name(&self) -> Option<String> {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
if let Some(scope) = self.scopes.get(&scope_id) {
|
|
237
|
-
if let ScopeKind::Module { name } = &scope.kind {
|
|
238
|
-
return Some(name.clone());
|
|
239
|
-
}
|
|
240
|
-
current = scope.parent;
|
|
209
|
+
self.walk_scopes().find_map(|scope| {
|
|
210
|
+
if let ScopeKind::Module { name } = &scope.kind {
|
|
211
|
+
Some(name.clone())
|
|
241
212
|
} else {
|
|
242
|
-
|
|
213
|
+
None
|
|
243
214
|
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
None
|
|
215
|
+
})
|
|
247
216
|
}
|
|
248
217
|
|
|
249
218
|
/// Get current fully qualified name by traversing all parent class/module scopes
|
|
@@ -260,36 +229,12 @@ impl ScopeManager {
|
|
|
260
229
|
/// ```
|
|
261
230
|
/// When inside `greet`, this returns `Some("Api::V1::User")`
|
|
262
231
|
pub fn current_qualified_name(&self) -> Option<String> {
|
|
263
|
-
let mut path_segments: Vec
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
match &scope.kind {
|
|
270
|
-
ScopeKind::Class { name, .. } => {
|
|
271
|
-
// If the name already contains ::, it's a qualified name from AST
|
|
272
|
-
// (e.g., `class Api::User` defined at top level)
|
|
273
|
-
if name.contains("::") {
|
|
274
|
-
path_segments.push(name.clone());
|
|
275
|
-
} else {
|
|
276
|
-
path_segments.push(name.clone());
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
ScopeKind::Module { name } => {
|
|
280
|
-
if name.contains("::") {
|
|
281
|
-
path_segments.push(name.clone());
|
|
282
|
-
} else {
|
|
283
|
-
path_segments.push(name.clone());
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
_ => {}
|
|
287
|
-
}
|
|
288
|
-
current = scope.parent;
|
|
289
|
-
} else {
|
|
290
|
-
break;
|
|
291
|
-
}
|
|
292
|
-
}
|
|
232
|
+
let mut path_segments: Vec<&str> = self.walk_scopes()
|
|
233
|
+
.filter_map(|scope| match &scope.kind {
|
|
234
|
+
ScopeKind::Class { name, .. } | ScopeKind::Module { name } => Some(name.as_str()),
|
|
235
|
+
_ => None,
|
|
236
|
+
})
|
|
237
|
+
.collect();
|
|
293
238
|
|
|
294
239
|
if path_segments.is_empty() {
|
|
295
240
|
return None;
|
|
@@ -297,76 +242,35 @@ impl ScopeManager {
|
|
|
297
242
|
|
|
298
243
|
// Reverse to get from outermost to innermost
|
|
299
244
|
path_segments.reverse();
|
|
300
|
-
|
|
301
|
-
// Join all segments, handling cases where segments may already contain ::
|
|
302
|
-
let mut result = String::new();
|
|
303
|
-
for segment in path_segments {
|
|
304
|
-
if !result.is_empty() {
|
|
305
|
-
result.push_str("::");
|
|
306
|
-
}
|
|
307
|
-
result.push_str(&segment);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
Some(result)
|
|
245
|
+
Some(path_segments.join("::"))
|
|
311
246
|
}
|
|
312
247
|
|
|
313
248
|
/// Get return_vertex from the nearest enclosing method scope
|
|
314
249
|
pub fn current_method_return_vertex(&self) -> Option<VertexId> {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
if let ScopeKind::Method { return_vertex, .. } = &scope.kind {
|
|
319
|
-
return *return_vertex;
|
|
320
|
-
}
|
|
321
|
-
current = scope.parent;
|
|
250
|
+
self.walk_scopes().find_map(|scope| {
|
|
251
|
+
if let ScopeKind::Method { return_vertex, .. } = &scope.kind {
|
|
252
|
+
*return_vertex
|
|
322
253
|
} else {
|
|
323
|
-
|
|
254
|
+
None
|
|
324
255
|
}
|
|
325
|
-
}
|
|
326
|
-
None
|
|
256
|
+
})
|
|
327
257
|
}
|
|
328
258
|
|
|
329
259
|
/// Lookup instance variable in enclosing module scope
|
|
330
260
|
pub fn lookup_instance_var_in_module(&self, name: &str) -> Option<VertexId> {
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
if let Some(scope) = self.scopes.get(&scope_id) {
|
|
335
|
-
match &scope.kind {
|
|
336
|
-
ScopeKind::Module { .. } => {
|
|
337
|
-
return scope.get_instance_var(name);
|
|
338
|
-
}
|
|
339
|
-
_ => {
|
|
340
|
-
current = scope.parent;
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
} else {
|
|
344
|
-
break;
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
None
|
|
261
|
+
self.walk_scopes()
|
|
262
|
+
.find(|scope| matches!(&scope.kind, ScopeKind::Module { .. }))
|
|
263
|
+
.and_then(|scope| scope.get_instance_var(name))
|
|
349
264
|
}
|
|
350
265
|
|
|
351
266
|
/// Set instance variable in enclosing module scope
|
|
352
267
|
pub fn set_instance_var_in_module(&mut self, name: String, vtx: VertexId) {
|
|
353
|
-
let
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
if let Some(module_scope) = self.scopes.get_mut(&scope_id) {
|
|
360
|
-
module_scope.set_instance_var(name, vtx);
|
|
361
|
-
}
|
|
362
|
-
return;
|
|
363
|
-
}
|
|
364
|
-
_ => {
|
|
365
|
-
current = scope.parent;
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
} else {
|
|
369
|
-
break;
|
|
268
|
+
let module_scope_id = self.walk_scopes()
|
|
269
|
+
.find(|scope| matches!(&scope.kind, ScopeKind::Module { .. }))
|
|
270
|
+
.map(|scope| scope.id);
|
|
271
|
+
if let Some(scope_id) = module_scope_id {
|
|
272
|
+
if let Some(scope) = self.scopes.get_mut(&scope_id) {
|
|
273
|
+
scope.set_instance_var(name, vtx);
|
|
370
274
|
}
|
|
371
275
|
}
|
|
372
276
|
}
|
|
@@ -376,6 +280,13 @@ impl ScopeManager {
|
|
|
376
280
|
mod tests {
|
|
377
281
|
use super::*;
|
|
378
282
|
|
|
283
|
+
#[test]
|
|
284
|
+
fn test_scope_manager_default() {
|
|
285
|
+
let sm = ScopeManager::default();
|
|
286
|
+
assert_eq!(sm.current_scope().id, ScopeId(0));
|
|
287
|
+
assert!(matches!(sm.current_scope().kind, ScopeKind::TopLevel));
|
|
288
|
+
}
|
|
289
|
+
|
|
379
290
|
#[test]
|
|
380
291
|
fn test_scope_manager_creation() {
|
|
381
292
|
let sm = ScopeManager::new();
|
|
@@ -614,4 +525,72 @@ mod tests {
|
|
|
614
525
|
// At top level, no class/module
|
|
615
526
|
assert_eq!(sm.current_qualified_name(), None);
|
|
616
527
|
}
|
|
528
|
+
|
|
529
|
+
#[test]
|
|
530
|
+
fn test_constant_registration_and_lookup() {
|
|
531
|
+
let mut sm = ScopeManager::new();
|
|
532
|
+
|
|
533
|
+
// module Api
|
|
534
|
+
sm.current_scope_mut().constants.insert("Api".to_string(), "Api".to_string());
|
|
535
|
+
let api_id = sm.new_scope(ScopeKind::Module { name: "Api".to_string() });
|
|
536
|
+
sm.enter_scope(api_id);
|
|
537
|
+
|
|
538
|
+
// class User (inside Api) — register in parent scope (Api)
|
|
539
|
+
sm.current_scope_mut().constants.insert("User".to_string(), "Api::User".to_string());
|
|
540
|
+
let user_id = sm.new_scope(ScopeKind::Class {
|
|
541
|
+
name: "User".to_string(),
|
|
542
|
+
superclass: None,
|
|
543
|
+
});
|
|
544
|
+
sm.enter_scope(user_id);
|
|
545
|
+
|
|
546
|
+
assert_eq!(sm.lookup_constant("User"), Some("Api::User".to_string()));
|
|
547
|
+
assert_eq!(sm.lookup_constant("Api"), Some("Api".to_string()));
|
|
548
|
+
assert_eq!(sm.lookup_constant("Unknown"), None);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
#[test]
|
|
552
|
+
fn test_constant_lookup_from_method_scope() {
|
|
553
|
+
let mut sm = ScopeManager::new();
|
|
554
|
+
|
|
555
|
+
sm.current_scope_mut().constants.insert("Api".to_string(), "Api".to_string());
|
|
556
|
+
let api_id = sm.new_scope(ScopeKind::Module { name: "Api".to_string() });
|
|
557
|
+
sm.enter_scope(api_id);
|
|
558
|
+
|
|
559
|
+
sm.current_scope_mut().constants.insert("User".to_string(), "Api::User".to_string());
|
|
560
|
+
let user_id = sm.new_scope(ScopeKind::Class {
|
|
561
|
+
name: "User".to_string(),
|
|
562
|
+
superclass: None,
|
|
563
|
+
});
|
|
564
|
+
sm.enter_scope(user_id);
|
|
565
|
+
|
|
566
|
+
let method_id = sm.new_scope(ScopeKind::Method {
|
|
567
|
+
name: "greet".to_string(),
|
|
568
|
+
receiver_type: None,
|
|
569
|
+
return_vertex: None,
|
|
570
|
+
});
|
|
571
|
+
sm.enter_scope(method_id);
|
|
572
|
+
|
|
573
|
+
// Should find constant by traversing parent scopes from method scope
|
|
574
|
+
assert_eq!(sm.lookup_constant("User"), Some("Api::User".to_string()));
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
#[test]
|
|
578
|
+
fn test_constant_same_name_different_namespaces() {
|
|
579
|
+
let mut sm = ScopeManager::new();
|
|
580
|
+
|
|
581
|
+
// module Api
|
|
582
|
+
let api_id = sm.new_scope(ScopeKind::Module { name: "Api".to_string() });
|
|
583
|
+
sm.enter_scope(api_id);
|
|
584
|
+
sm.current_scope_mut().constants.insert("User".to_string(), "Api::User".to_string());
|
|
585
|
+
|
|
586
|
+
sm.exit_scope();
|
|
587
|
+
|
|
588
|
+
// module Admin
|
|
589
|
+
let admin_id = sm.new_scope(ScopeKind::Module { name: "Admin".to_string() });
|
|
590
|
+
sm.enter_scope(admin_id);
|
|
591
|
+
sm.current_scope_mut().constants.insert("User".to_string(), "Admin::User".to_string());
|
|
592
|
+
|
|
593
|
+
// Inside Admin scope, User should resolve to Admin::User
|
|
594
|
+
assert_eq!(sm.lookup_constant("User"), Some("Admin::User".to_string()));
|
|
595
|
+
}
|
|
617
596
|
}
|