method-ray 0.1.1
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 +7 -0
- data/CHANGELOG.md +23 -0
- data/LICENSE +21 -0
- data/README.md +39 -0
- data/exe/methodray +7 -0
- data/ext/Cargo.toml +24 -0
- data/ext/extconf.rb +40 -0
- data/ext/src/cli.rs +33 -0
- data/ext/src/lib.rs +79 -0
- data/lib/methodray/cli.rb +28 -0
- data/lib/methodray/commands.rb +78 -0
- data/lib/methodray/version.rb +5 -0
- data/lib/methodray.rb +9 -0
- data/rust/Cargo.toml +39 -0
- data/rust/src/analyzer/calls.rs +56 -0
- data/rust/src/analyzer/definitions.rs +70 -0
- data/rust/src/analyzer/dispatch.rs +134 -0
- data/rust/src/analyzer/install.rs +226 -0
- data/rust/src/analyzer/literals.rs +85 -0
- data/rust/src/analyzer/mod.rs +11 -0
- data/rust/src/analyzer/tests/integration_test.rs +136 -0
- data/rust/src/analyzer/tests/mod.rs +1 -0
- data/rust/src/analyzer/variables.rs +76 -0
- data/rust/src/cache/mod.rs +3 -0
- data/rust/src/cache/rbs_cache.rs +158 -0
- data/rust/src/checker.rs +139 -0
- data/rust/src/cli/args.rs +40 -0
- data/rust/src/cli/commands.rs +139 -0
- data/rust/src/cli/mod.rs +6 -0
- data/rust/src/diagnostics/diagnostic.rs +125 -0
- data/rust/src/diagnostics/formatter.rs +119 -0
- data/rust/src/diagnostics/mod.rs +5 -0
- data/rust/src/env/box_manager.rs +121 -0
- data/rust/src/env/global_env.rs +279 -0
- data/rust/src/env/local_env.rs +58 -0
- data/rust/src/env/method_registry.rs +63 -0
- data/rust/src/env/mod.rs +15 -0
- data/rust/src/env/scope.rs +330 -0
- data/rust/src/env/type_error.rs +23 -0
- data/rust/src/env/vertex_manager.rs +195 -0
- data/rust/src/graph/box.rs +157 -0
- data/rust/src/graph/change_set.rs +115 -0
- data/rust/src/graph/mod.rs +7 -0
- data/rust/src/graph/vertex.rs +167 -0
- data/rust/src/lib.rs +24 -0
- data/rust/src/lsp/diagnostics.rs +133 -0
- data/rust/src/lsp/main.rs +8 -0
- data/rust/src/lsp/mod.rs +4 -0
- data/rust/src/lsp/server.rs +138 -0
- data/rust/src/main.rs +46 -0
- data/rust/src/parser.rs +96 -0
- data/rust/src/rbs/converter.rs +82 -0
- data/rust/src/rbs/error.rs +37 -0
- data/rust/src/rbs/loader.rs +183 -0
- data/rust/src/rbs/mod.rs +15 -0
- data/rust/src/source_map.rs +102 -0
- data/rust/src/types.rs +75 -0
- metadata +119 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
use super::diagnostic::Diagnostic;
|
|
2
|
+
use std::fs;
|
|
3
|
+
use std::path::Path;
|
|
4
|
+
|
|
5
|
+
/// Format diagnostics in LSP-compatible format
|
|
6
|
+
///
|
|
7
|
+
/// Example output:
|
|
8
|
+
/// ```text
|
|
9
|
+
/// app/models/user.rb:10:5: error: undefined method `upcase` for Integer
|
|
10
|
+
/// ```
|
|
11
|
+
#[allow(dead_code)]
|
|
12
|
+
pub fn format_diagnostics(diagnostics: &[Diagnostic]) -> String {
|
|
13
|
+
diagnostics
|
|
14
|
+
.iter()
|
|
15
|
+
.map(|diag| {
|
|
16
|
+
format!(
|
|
17
|
+
"{}:{}:{}: {}: {}",
|
|
18
|
+
diag.location.file.display(),
|
|
19
|
+
diag.location.line,
|
|
20
|
+
diag.location.column,
|
|
21
|
+
diag.level.as_str(),
|
|
22
|
+
diag.message
|
|
23
|
+
)
|
|
24
|
+
})
|
|
25
|
+
.collect::<Vec<_>>()
|
|
26
|
+
.join("\n")
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/// Format diagnostics with code snippet
|
|
30
|
+
///
|
|
31
|
+
/// Example output:
|
|
32
|
+
/// ```text
|
|
33
|
+
/// app/models/user.rb:10:5: error: undefined method `upcase` for Integer
|
|
34
|
+
/// x.upcase
|
|
35
|
+
/// ^^^^^^
|
|
36
|
+
/// ```
|
|
37
|
+
pub fn format_diagnostics_with_source(diagnostics: &[Diagnostic], source_code: &str) -> String {
|
|
38
|
+
let lines: Vec<&str> = source_code.lines().collect();
|
|
39
|
+
|
|
40
|
+
diagnostics
|
|
41
|
+
.iter()
|
|
42
|
+
.map(|diag| {
|
|
43
|
+
let mut output = format!(
|
|
44
|
+
"{}:{}:{}: {}: {}",
|
|
45
|
+
diag.location.file.display(),
|
|
46
|
+
diag.location.line,
|
|
47
|
+
diag.location.column,
|
|
48
|
+
diag.level.as_str(),
|
|
49
|
+
diag.message
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
// Add code snippet
|
|
53
|
+
if diag.location.line > 0 && diag.location.line <= lines.len() {
|
|
54
|
+
let line_index = diag.location.line - 1; // Convert to 0-indexed
|
|
55
|
+
let source_line = lines[line_index];
|
|
56
|
+
|
|
57
|
+
output.push('\n');
|
|
58
|
+
output.push_str(" ");
|
|
59
|
+
output.push_str(source_line);
|
|
60
|
+
output.push('\n');
|
|
61
|
+
|
|
62
|
+
// Add caret indicator
|
|
63
|
+
let column = diag.location.column.saturating_sub(1); // Convert to 0-indexed
|
|
64
|
+
output.push_str(" ");
|
|
65
|
+
output.push_str(&" ".repeat(column));
|
|
66
|
+
output.push('^');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
output
|
|
70
|
+
})
|
|
71
|
+
.collect::<Vec<_>>()
|
|
72
|
+
.join("\n\n")
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/// Read source file and format diagnostics with code snippet
|
|
76
|
+
pub fn format_diagnostics_with_file(diagnostics: &[Diagnostic], file_path: &Path) -> String {
|
|
77
|
+
match fs::read_to_string(file_path) {
|
|
78
|
+
Ok(source) => format_diagnostics_with_source(diagnostics, &source),
|
|
79
|
+
Err(_) => format_diagnostics(diagnostics), // Fallback to simple format
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
#[cfg(test)]
|
|
84
|
+
mod tests {
|
|
85
|
+
use super::*;
|
|
86
|
+
use crate::diagnostics::diagnostic::{Diagnostic, Location};
|
|
87
|
+
use std::path::PathBuf;
|
|
88
|
+
|
|
89
|
+
#[test]
|
|
90
|
+
fn test_format_diagnostics() {
|
|
91
|
+
let diagnostics = vec![
|
|
92
|
+
Diagnostic::undefined_method(
|
|
93
|
+
Location {
|
|
94
|
+
file: PathBuf::from("test.rb"),
|
|
95
|
+
line: 10,
|
|
96
|
+
column: 5,
|
|
97
|
+
length: None,
|
|
98
|
+
},
|
|
99
|
+
"Integer",
|
|
100
|
+
"upcase",
|
|
101
|
+
),
|
|
102
|
+
Diagnostic::union_partial_error(
|
|
103
|
+
Location {
|
|
104
|
+
file: PathBuf::from("test.rb"),
|
|
105
|
+
line: 15,
|
|
106
|
+
column: 3,
|
|
107
|
+
length: None,
|
|
108
|
+
},
|
|
109
|
+
vec!["String".to_string()],
|
|
110
|
+
vec!["Integer".to_string()],
|
|
111
|
+
"upcase",
|
|
112
|
+
),
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
let output = format_diagnostics(&diagnostics);
|
|
116
|
+
assert!(output.contains("test.rb:10:5: error:"));
|
|
117
|
+
assert!(output.contains("test.rb:15:3: warning:"));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
//! Box management and execution queue
|
|
2
|
+
//!
|
|
3
|
+
//! Handles registration and execution of Box instances (reactive computations).
|
|
4
|
+
|
|
5
|
+
use crate::graph::{BoxId, BoxTrait};
|
|
6
|
+
use std::collections::{HashMap, HashSet, VecDeque};
|
|
7
|
+
|
|
8
|
+
/// Manages boxes and their execution queue
|
|
9
|
+
#[allow(dead_code)]
|
|
10
|
+
pub struct BoxManager {
|
|
11
|
+
/// All registered boxes
|
|
12
|
+
pub boxes: HashMap<BoxId, Box<dyn BoxTrait>>,
|
|
13
|
+
/// Queue of boxes to be executed
|
|
14
|
+
pub run_queue: VecDeque<BoxId>,
|
|
15
|
+
/// Set to prevent duplicate queue entries
|
|
16
|
+
run_queue_set: HashSet<BoxId>,
|
|
17
|
+
/// Next box ID to allocate
|
|
18
|
+
pub next_box_id: usize,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
impl Default for BoxManager {
|
|
22
|
+
fn default() -> Self {
|
|
23
|
+
Self::new()
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
#[allow(dead_code)]
|
|
28
|
+
impl BoxManager {
|
|
29
|
+
/// Create a new empty box manager
|
|
30
|
+
pub fn new() -> Self {
|
|
31
|
+
Self {
|
|
32
|
+
boxes: HashMap::new(),
|
|
33
|
+
run_queue: VecDeque::new(),
|
|
34
|
+
run_queue_set: HashSet::new(),
|
|
35
|
+
next_box_id: 0,
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/// Get a box by ID
|
|
40
|
+
pub fn get(&self, id: BoxId) -> Option<&Box<dyn BoxTrait>> {
|
|
41
|
+
self.boxes.get(&id)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/// Remove a box and return it (for temporary mutation)
|
|
45
|
+
pub fn remove(&mut self, id: BoxId) -> Option<Box<dyn BoxTrait>> {
|
|
46
|
+
self.boxes.remove(&id)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/// Insert a box back after temporary removal
|
|
50
|
+
pub fn insert(&mut self, id: BoxId, box_instance: Box<dyn BoxTrait>) {
|
|
51
|
+
self.boxes.insert(id, box_instance);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/// Check if a box exists
|
|
55
|
+
pub fn contains(&self, id: BoxId) -> bool {
|
|
56
|
+
self.boxes.contains_key(&id)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/// Add a box to the execution queue
|
|
60
|
+
pub fn add_run(&mut self, box_id: BoxId) {
|
|
61
|
+
if !self.run_queue_set.contains(&box_id) {
|
|
62
|
+
self.run_queue.push_back(box_id);
|
|
63
|
+
self.run_queue_set.insert(box_id);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/// Pop the next box from the queue
|
|
68
|
+
pub fn pop_run(&mut self) -> Option<BoxId> {
|
|
69
|
+
if let Some(box_id) = self.run_queue.pop_front() {
|
|
70
|
+
self.run_queue_set.remove(&box_id);
|
|
71
|
+
Some(box_id)
|
|
72
|
+
} else {
|
|
73
|
+
None
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/// Check if the queue is empty
|
|
78
|
+
pub fn queue_is_empty(&self) -> bool {
|
|
79
|
+
self.run_queue.is_empty()
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/// Get the number of registered boxes
|
|
83
|
+
pub fn len(&self) -> usize {
|
|
84
|
+
self.boxes.len()
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/// Check if there are no registered boxes
|
|
88
|
+
pub fn is_empty(&self) -> bool {
|
|
89
|
+
self.boxes.is_empty()
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
#[cfg(test)]
|
|
94
|
+
mod tests {
|
|
95
|
+
use super::*;
|
|
96
|
+
|
|
97
|
+
#[test]
|
|
98
|
+
fn test_add_run_prevents_duplicates() {
|
|
99
|
+
let mut manager = BoxManager::new();
|
|
100
|
+
|
|
101
|
+
let id = BoxId(0);
|
|
102
|
+
manager.add_run(id);
|
|
103
|
+
manager.add_run(id); // Should be ignored
|
|
104
|
+
|
|
105
|
+
assert_eq!(manager.run_queue.len(), 1);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
#[test]
|
|
109
|
+
fn test_pop_run() {
|
|
110
|
+
let mut manager = BoxManager::new();
|
|
111
|
+
|
|
112
|
+
let id1 = BoxId(0);
|
|
113
|
+
let id2 = BoxId(1);
|
|
114
|
+
manager.add_run(id1);
|
|
115
|
+
manager.add_run(id2);
|
|
116
|
+
|
|
117
|
+
assert_eq!(manager.pop_run(), Some(id1));
|
|
118
|
+
assert_eq!(manager.pop_run(), Some(id2));
|
|
119
|
+
assert_eq!(manager.pop_run(), None);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
//! Global environment: facade for the type inference engine
|
|
2
|
+
//!
|
|
3
|
+
//! This module provides a unified interface for managing vertices, boxes,
|
|
4
|
+
//! methods, type errors, and scopes during type inference.
|
|
5
|
+
|
|
6
|
+
use crate::env::box_manager::BoxManager;
|
|
7
|
+
use crate::env::method_registry::{MethodInfo, MethodRegistry};
|
|
8
|
+
use crate::env::scope::{Scope, ScopeId, ScopeKind, ScopeManager};
|
|
9
|
+
use crate::env::type_error::TypeError;
|
|
10
|
+
use crate::env::vertex_manager::VertexManager;
|
|
11
|
+
use crate::graph::{BoxId, BoxTrait, ChangeSet, EdgeUpdate, Source, Vertex, VertexId};
|
|
12
|
+
use crate::source_map::SourceLocation;
|
|
13
|
+
use crate::types::Type;
|
|
14
|
+
|
|
15
|
+
/// Global environment: core of the type inference engine
|
|
16
|
+
///
|
|
17
|
+
/// This is a facade that coordinates the various subsystems:
|
|
18
|
+
/// - Vertex management (type graph nodes)
|
|
19
|
+
/// - Box management (reactive computations)
|
|
20
|
+
/// - Method registry (method definitions)
|
|
21
|
+
/// - Type errors (diagnostic collection)
|
|
22
|
+
/// - Scope management (lexical scopes)
|
|
23
|
+
pub struct GlobalEnv {
|
|
24
|
+
/// Vertex and source management
|
|
25
|
+
vertex_manager: VertexManager,
|
|
26
|
+
|
|
27
|
+
/// Box management and execution queue
|
|
28
|
+
box_manager: BoxManager,
|
|
29
|
+
|
|
30
|
+
/// Method definitions
|
|
31
|
+
method_registry: MethodRegistry,
|
|
32
|
+
|
|
33
|
+
/// Type errors collected during analysis
|
|
34
|
+
pub type_errors: Vec<TypeError>,
|
|
35
|
+
|
|
36
|
+
/// Scope management
|
|
37
|
+
pub scope_manager: ScopeManager,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
#[allow(dead_code)]
|
|
41
|
+
impl GlobalEnv {
|
|
42
|
+
pub fn new() -> Self {
|
|
43
|
+
Self {
|
|
44
|
+
vertex_manager: VertexManager::new(),
|
|
45
|
+
box_manager: BoxManager::new(),
|
|
46
|
+
method_registry: MethodRegistry::new(),
|
|
47
|
+
type_errors: Vec::new(),
|
|
48
|
+
scope_manager: ScopeManager::new(),
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ===== Vertex Management =====
|
|
53
|
+
|
|
54
|
+
/// Create new Vertex
|
|
55
|
+
pub fn new_vertex(&mut self) -> VertexId {
|
|
56
|
+
self.vertex_manager.new_vertex()
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/// Create new Source (fixed type)
|
|
60
|
+
pub fn new_source(&mut self, ty: Type) -> VertexId {
|
|
61
|
+
self.vertex_manager.new_source(ty)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/// Get Vertex
|
|
65
|
+
pub fn get_vertex(&self, id: VertexId) -> Option<&Vertex> {
|
|
66
|
+
self.vertex_manager.get_vertex(id)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// Get Source
|
|
70
|
+
pub fn get_source(&self, id: VertexId) -> Option<&Source> {
|
|
71
|
+
self.vertex_manager.get_source(id)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/// Add edge (immediate type propagation)
|
|
75
|
+
pub fn add_edge(&mut self, src: VertexId, dst: VertexId) {
|
|
76
|
+
self.vertex_manager.add_edge(src, dst);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/// For debugging: display types of all Vertices
|
|
80
|
+
pub fn show_all(&self) -> String {
|
|
81
|
+
self.vertex_manager.show_all()
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ===== Box Management =====
|
|
85
|
+
|
|
86
|
+
/// Allocate a new BoxId
|
|
87
|
+
pub fn alloc_box_id(&mut self) -> BoxId {
|
|
88
|
+
let id = BoxId(self.box_manager.next_box_id);
|
|
89
|
+
self.box_manager.next_box_id += 1;
|
|
90
|
+
id
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/// Register a Box with a pre-allocated ID and add it to the run queue
|
|
94
|
+
pub fn register_box(&mut self, box_id: BoxId, box_instance: Box<dyn BoxTrait>) {
|
|
95
|
+
self.box_manager.insert(box_id, box_instance);
|
|
96
|
+
self.box_manager.add_run(box_id);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/// Get the number of registered boxes
|
|
100
|
+
pub fn box_count(&self) -> usize {
|
|
101
|
+
self.box_manager.len()
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/// Apply changes
|
|
105
|
+
pub fn apply_changes(&mut self, mut changes: ChangeSet) {
|
|
106
|
+
let updates = changes.reinstall();
|
|
107
|
+
|
|
108
|
+
for update in updates {
|
|
109
|
+
match update {
|
|
110
|
+
EdgeUpdate::Add { src, dst } => {
|
|
111
|
+
self.add_edge(src, dst);
|
|
112
|
+
}
|
|
113
|
+
EdgeUpdate::Remove { .. } => {
|
|
114
|
+
// TODO: Implement edge removal (in Phase 2)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/// Execute all Boxes
|
|
121
|
+
pub fn run_all(&mut self) {
|
|
122
|
+
while let Some(box_id) = self.box_manager.pop_run() {
|
|
123
|
+
if self.box_manager.contains(box_id) {
|
|
124
|
+
let mut changes = ChangeSet::new();
|
|
125
|
+
|
|
126
|
+
// Execute Box (temporarily remove to avoid &mut self borrow issue)
|
|
127
|
+
let mut temp_box = self.box_manager.remove(box_id).unwrap();
|
|
128
|
+
temp_box.run(self, &mut changes);
|
|
129
|
+
self.box_manager.insert(box_id, temp_box);
|
|
130
|
+
|
|
131
|
+
self.apply_changes(changes);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ===== Method Registry =====
|
|
137
|
+
|
|
138
|
+
/// Resolve method
|
|
139
|
+
pub fn resolve_method(&self, recv_ty: &Type, method_name: &str) -> Option<&MethodInfo> {
|
|
140
|
+
self.method_registry.resolve(recv_ty, method_name)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/// Register built-in method
|
|
144
|
+
pub fn register_builtin_method(&mut self, recv_ty: Type, method_name: &str, ret_ty: Type) {
|
|
145
|
+
self.method_registry.register(recv_ty, method_name, ret_ty);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ===== Type Errors =====
|
|
149
|
+
|
|
150
|
+
/// Record a type error (undefined method)
|
|
151
|
+
pub fn record_type_error(
|
|
152
|
+
&mut self,
|
|
153
|
+
receiver_type: Type,
|
|
154
|
+
method_name: String,
|
|
155
|
+
location: Option<SourceLocation>,
|
|
156
|
+
) {
|
|
157
|
+
self.type_errors
|
|
158
|
+
.push(TypeError::new(receiver_type, method_name, location));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ===== Scope Management =====
|
|
162
|
+
|
|
163
|
+
/// Enter a class scope
|
|
164
|
+
pub fn enter_class(&mut self, name: String) -> ScopeId {
|
|
165
|
+
let scope_id = self.scope_manager.new_scope(ScopeKind::Class {
|
|
166
|
+
name,
|
|
167
|
+
superclass: None,
|
|
168
|
+
});
|
|
169
|
+
self.scope_manager.enter_scope(scope_id);
|
|
170
|
+
scope_id
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/// Enter a method scope
|
|
174
|
+
pub fn enter_method(&mut self, name: String) -> ScopeId {
|
|
175
|
+
let class_name = self.scope_manager.current_class_name();
|
|
176
|
+
let scope_id = self.scope_manager.new_scope(ScopeKind::Method {
|
|
177
|
+
name,
|
|
178
|
+
receiver_type: class_name,
|
|
179
|
+
});
|
|
180
|
+
self.scope_manager.enter_scope(scope_id);
|
|
181
|
+
scope_id
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/// Exit current scope
|
|
185
|
+
pub fn exit_scope(&mut self) {
|
|
186
|
+
self.scope_manager.exit_scope();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/// Get current scope
|
|
190
|
+
pub fn current_scope(&self) -> &Scope {
|
|
191
|
+
self.scope_manager.current_scope()
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/// Get current scope mutably
|
|
195
|
+
pub fn current_scope_mut(&mut self) -> &mut Scope {
|
|
196
|
+
self.scope_manager.current_scope_mut()
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
impl Default for GlobalEnv {
|
|
201
|
+
fn default() -> Self {
|
|
202
|
+
Self::new()
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
#[cfg(test)]
|
|
207
|
+
mod tests {
|
|
208
|
+
use super::*;
|
|
209
|
+
|
|
210
|
+
#[test]
|
|
211
|
+
fn test_global_env_new_vertex() {
|
|
212
|
+
let mut genv = GlobalEnv::new();
|
|
213
|
+
|
|
214
|
+
let v1 = genv.new_vertex();
|
|
215
|
+
let v2 = genv.new_vertex();
|
|
216
|
+
|
|
217
|
+
assert_eq!(v1.0, 0);
|
|
218
|
+
assert_eq!(v2.0, 1);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
#[test]
|
|
222
|
+
fn test_global_env_new_source() {
|
|
223
|
+
let mut genv = GlobalEnv::new();
|
|
224
|
+
|
|
225
|
+
let s1 = genv.new_source(Type::string());
|
|
226
|
+
let s2 = genv.new_source(Type::integer());
|
|
227
|
+
|
|
228
|
+
assert_eq!(genv.get_source(s1).unwrap().ty.show(), "String");
|
|
229
|
+
assert_eq!(genv.get_source(s2).unwrap().ty.show(), "Integer");
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
#[test]
|
|
233
|
+
fn test_global_env_edge_propagation() {
|
|
234
|
+
let mut genv = GlobalEnv::new();
|
|
235
|
+
|
|
236
|
+
// Source<String> -> Vertex
|
|
237
|
+
let src = genv.new_source(Type::string());
|
|
238
|
+
let vtx = genv.new_vertex();
|
|
239
|
+
|
|
240
|
+
genv.add_edge(src, vtx);
|
|
241
|
+
|
|
242
|
+
// Verify type propagated to Vertex
|
|
243
|
+
assert_eq!(genv.get_vertex(vtx).unwrap().show(), "String");
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
#[test]
|
|
247
|
+
fn test_global_env_chain_propagation() {
|
|
248
|
+
let mut genv = GlobalEnv::new();
|
|
249
|
+
|
|
250
|
+
// Source<String> -> Vertex1 -> Vertex2
|
|
251
|
+
let src = genv.new_source(Type::string());
|
|
252
|
+
let v1 = genv.new_vertex();
|
|
253
|
+
let v2 = genv.new_vertex();
|
|
254
|
+
|
|
255
|
+
genv.add_edge(src, v1);
|
|
256
|
+
genv.add_edge(v1, v2);
|
|
257
|
+
|
|
258
|
+
// Verify type propagated to v2
|
|
259
|
+
assert_eq!(genv.get_vertex(v1).unwrap().show(), "String");
|
|
260
|
+
assert_eq!(genv.get_vertex(v2).unwrap().show(), "String");
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
#[test]
|
|
264
|
+
fn test_global_env_union_propagation() {
|
|
265
|
+
let mut genv = GlobalEnv::new();
|
|
266
|
+
|
|
267
|
+
// Source<String> -> Vertex
|
|
268
|
+
// Source<Integer> -> Vertex
|
|
269
|
+
let src1 = genv.new_source(Type::string());
|
|
270
|
+
let src2 = genv.new_source(Type::integer());
|
|
271
|
+
let vtx = genv.new_vertex();
|
|
272
|
+
|
|
273
|
+
genv.add_edge(src1, vtx);
|
|
274
|
+
genv.add_edge(src2, vtx);
|
|
275
|
+
|
|
276
|
+
// Verify it became Union type
|
|
277
|
+
assert_eq!(genv.get_vertex(vtx).unwrap().show(), "(Integer | String)");
|
|
278
|
+
}
|
|
279
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
use crate::graph::VertexId;
|
|
2
|
+
use std::collections::HashMap;
|
|
3
|
+
|
|
4
|
+
/// Local environment: mapping of local variable names to VertexIDs
|
|
5
|
+
pub struct LocalEnv {
|
|
6
|
+
locals: HashMap<String, VertexId>,
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
#[allow(dead_code)]
|
|
10
|
+
impl LocalEnv {
|
|
11
|
+
pub fn new() -> Self {
|
|
12
|
+
Self {
|
|
13
|
+
locals: HashMap::new(),
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/// Register variable
|
|
18
|
+
pub fn new_var(&mut self, name: String, vtx_id: VertexId) {
|
|
19
|
+
self.locals.insert(name, vtx_id);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/// Get variable
|
|
23
|
+
pub fn get_var(&self, name: &str) -> Option<VertexId> {
|
|
24
|
+
self.locals.get(name).copied()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/// Get all variables
|
|
28
|
+
pub fn all_vars(&self) -> impl Iterator<Item = (&String, &VertexId)> {
|
|
29
|
+
self.locals.iter()
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
#[cfg(test)]
|
|
34
|
+
mod tests {
|
|
35
|
+
use super::*;
|
|
36
|
+
|
|
37
|
+
#[test]
|
|
38
|
+
fn test_local_env() {
|
|
39
|
+
let mut lenv = LocalEnv::new();
|
|
40
|
+
|
|
41
|
+
lenv.new_var("x".to_string(), VertexId(1));
|
|
42
|
+
lenv.new_var("y".to_string(), VertexId(2));
|
|
43
|
+
|
|
44
|
+
assert_eq!(lenv.get_var("x"), Some(VertexId(1)));
|
|
45
|
+
assert_eq!(lenv.get_var("y"), Some(VertexId(2)));
|
|
46
|
+
assert_eq!(lenv.get_var("z"), None);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
#[test]
|
|
50
|
+
fn test_local_env_override() {
|
|
51
|
+
let mut lenv = LocalEnv::new();
|
|
52
|
+
|
|
53
|
+
lenv.new_var("x".to_string(), VertexId(1));
|
|
54
|
+
lenv.new_var("x".to_string(), VertexId(2)); // Override
|
|
55
|
+
|
|
56
|
+
assert_eq!(lenv.get_var("x"), Some(VertexId(2)));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
//! Method registration and resolution
|
|
2
|
+
|
|
3
|
+
use crate::types::Type;
|
|
4
|
+
use std::collections::HashMap;
|
|
5
|
+
|
|
6
|
+
/// Method information
|
|
7
|
+
#[derive(Debug, Clone)]
|
|
8
|
+
pub struct MethodInfo {
|
|
9
|
+
pub return_type: Type,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/// Registry for method definitions
|
|
13
|
+
#[derive(Debug, Default)]
|
|
14
|
+
pub struct MethodRegistry {
|
|
15
|
+
methods: HashMap<(Type, String), MethodInfo>,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
impl MethodRegistry {
|
|
19
|
+
/// Create a new empty registry
|
|
20
|
+
pub fn new() -> Self {
|
|
21
|
+
Self {
|
|
22
|
+
methods: HashMap::new(),
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/// Register a method for a receiver type
|
|
27
|
+
pub fn register(&mut self, recv_ty: Type, method_name: &str, ret_ty: Type) {
|
|
28
|
+
self.methods.insert(
|
|
29
|
+
(recv_ty, method_name.to_string()),
|
|
30
|
+
MethodInfo {
|
|
31
|
+
return_type: ret_ty,
|
|
32
|
+
},
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/// Resolve a method for a receiver type
|
|
37
|
+
pub fn resolve(&self, recv_ty: &Type, method_name: &str) -> Option<&MethodInfo> {
|
|
38
|
+
self.methods
|
|
39
|
+
.get(&(recv_ty.clone(), method_name.to_string()))
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
#[cfg(test)]
|
|
44
|
+
mod tests {
|
|
45
|
+
use super::*;
|
|
46
|
+
|
|
47
|
+
#[test]
|
|
48
|
+
fn test_register_and_resolve() {
|
|
49
|
+
let mut registry = MethodRegistry::new();
|
|
50
|
+
registry.register(Type::string(), "length", Type::integer());
|
|
51
|
+
|
|
52
|
+
let info = registry.resolve(&Type::string(), "length").unwrap();
|
|
53
|
+
assert!(
|
|
54
|
+
matches!(info.return_type, Type::Instance { ref class_name, .. } if class_name == "Integer")
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
#[test]
|
|
59
|
+
fn test_resolve_not_found() {
|
|
60
|
+
let registry = MethodRegistry::new();
|
|
61
|
+
assert!(registry.resolve(&Type::string(), "unknown").is_none());
|
|
62
|
+
}
|
|
63
|
+
}
|
data/rust/src/env/mod.rs
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
//! Environment management for type inference
|
|
2
|
+
//!
|
|
3
|
+
//! This module provides the core data structures for managing
|
|
4
|
+
//! type inference state including global and local environments.
|
|
5
|
+
|
|
6
|
+
pub mod box_manager;
|
|
7
|
+
pub mod global_env;
|
|
8
|
+
pub mod local_env;
|
|
9
|
+
pub mod method_registry;
|
|
10
|
+
pub mod scope;
|
|
11
|
+
pub mod type_error;
|
|
12
|
+
pub mod vertex_manager;
|
|
13
|
+
|
|
14
|
+
pub use global_env::GlobalEnv;
|
|
15
|
+
pub use local_env::LocalEnv;
|