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.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +23 -0
  3. data/LICENSE +21 -0
  4. data/README.md +39 -0
  5. data/exe/methodray +7 -0
  6. data/ext/Cargo.toml +24 -0
  7. data/ext/extconf.rb +40 -0
  8. data/ext/src/cli.rs +33 -0
  9. data/ext/src/lib.rs +79 -0
  10. data/lib/methodray/cli.rb +28 -0
  11. data/lib/methodray/commands.rb +78 -0
  12. data/lib/methodray/version.rb +5 -0
  13. data/lib/methodray.rb +9 -0
  14. data/rust/Cargo.toml +39 -0
  15. data/rust/src/analyzer/calls.rs +56 -0
  16. data/rust/src/analyzer/definitions.rs +70 -0
  17. data/rust/src/analyzer/dispatch.rs +134 -0
  18. data/rust/src/analyzer/install.rs +226 -0
  19. data/rust/src/analyzer/literals.rs +85 -0
  20. data/rust/src/analyzer/mod.rs +11 -0
  21. data/rust/src/analyzer/tests/integration_test.rs +136 -0
  22. data/rust/src/analyzer/tests/mod.rs +1 -0
  23. data/rust/src/analyzer/variables.rs +76 -0
  24. data/rust/src/cache/mod.rs +3 -0
  25. data/rust/src/cache/rbs_cache.rs +158 -0
  26. data/rust/src/checker.rs +139 -0
  27. data/rust/src/cli/args.rs +40 -0
  28. data/rust/src/cli/commands.rs +139 -0
  29. data/rust/src/cli/mod.rs +6 -0
  30. data/rust/src/diagnostics/diagnostic.rs +125 -0
  31. data/rust/src/diagnostics/formatter.rs +119 -0
  32. data/rust/src/diagnostics/mod.rs +5 -0
  33. data/rust/src/env/box_manager.rs +121 -0
  34. data/rust/src/env/global_env.rs +279 -0
  35. data/rust/src/env/local_env.rs +58 -0
  36. data/rust/src/env/method_registry.rs +63 -0
  37. data/rust/src/env/mod.rs +15 -0
  38. data/rust/src/env/scope.rs +330 -0
  39. data/rust/src/env/type_error.rs +23 -0
  40. data/rust/src/env/vertex_manager.rs +195 -0
  41. data/rust/src/graph/box.rs +157 -0
  42. data/rust/src/graph/change_set.rs +115 -0
  43. data/rust/src/graph/mod.rs +7 -0
  44. data/rust/src/graph/vertex.rs +167 -0
  45. data/rust/src/lib.rs +24 -0
  46. data/rust/src/lsp/diagnostics.rs +133 -0
  47. data/rust/src/lsp/main.rs +8 -0
  48. data/rust/src/lsp/mod.rs +4 -0
  49. data/rust/src/lsp/server.rs +138 -0
  50. data/rust/src/main.rs +46 -0
  51. data/rust/src/parser.rs +96 -0
  52. data/rust/src/rbs/converter.rs +82 -0
  53. data/rust/src/rbs/error.rs +37 -0
  54. data/rust/src/rbs/loader.rs +183 -0
  55. data/rust/src/rbs/mod.rs +15 -0
  56. data/rust/src/source_map.rs +102 -0
  57. data/rust/src/types.rs +75 -0
  58. metadata +119 -0
@@ -0,0 +1,330 @@
1
+ use crate::graph::VertexId;
2
+ use std::collections::HashMap;
3
+
4
+ /// スコープID
5
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
6
+ pub struct ScopeId(pub usize);
7
+
8
+ /// スコープの種類
9
+ #[derive(Debug, Clone)]
10
+ #[allow(dead_code)]
11
+ pub enum ScopeKind {
12
+ TopLevel,
13
+ Class {
14
+ name: String,
15
+ superclass: Option<String>,
16
+ },
17
+ Module {
18
+ name: String,
19
+ },
20
+ Method {
21
+ name: String,
22
+ receiver_type: Option<String>, // レシーバーのクラス名
23
+ },
24
+ Block,
25
+ }
26
+
27
+ /// スコープ情報
28
+ #[derive(Debug, Clone)]
29
+ #[allow(dead_code)]
30
+ pub struct Scope {
31
+ pub id: ScopeId,
32
+ pub kind: ScopeKind,
33
+ pub parent: Option<ScopeId>,
34
+
35
+ /// ローカル変数
36
+ pub local_vars: HashMap<String, VertexId>,
37
+
38
+ /// インスタンス変数(クラス/メソッドスコープのみ)
39
+ pub instance_vars: HashMap<String, VertexId>,
40
+
41
+ /// クラス変数(クラススコープのみ)
42
+ pub class_vars: HashMap<String, VertexId>,
43
+ }
44
+
45
+ #[allow(dead_code)]
46
+ impl Scope {
47
+ pub fn new(id: ScopeId, kind: ScopeKind, parent: Option<ScopeId>) -> Self {
48
+ Self {
49
+ id,
50
+ kind,
51
+ parent,
52
+ local_vars: HashMap::new(),
53
+ instance_vars: HashMap::new(),
54
+ class_vars: HashMap::new(),
55
+ }
56
+ }
57
+
58
+ /// ローカル変数を追加
59
+ pub fn set_local_var(&mut self, name: String, vtx: VertexId) {
60
+ self.local_vars.insert(name, vtx);
61
+ }
62
+
63
+ /// ローカル変数を取得
64
+ pub fn get_local_var(&self, name: &str) -> Option<VertexId> {
65
+ self.local_vars.get(name).copied()
66
+ }
67
+
68
+ /// インスタンス変数を追加
69
+ pub fn set_instance_var(&mut self, name: String, vtx: VertexId) {
70
+ self.instance_vars.insert(name, vtx);
71
+ }
72
+
73
+ /// インスタンス変数を取得
74
+ pub fn get_instance_var(&self, name: &str) -> Option<VertexId> {
75
+ self.instance_vars.get(name).copied()
76
+ }
77
+ }
78
+
79
+ /// スコープマネージャー
80
+ #[derive(Debug)]
81
+ pub struct ScopeManager {
82
+ scopes: HashMap<ScopeId, Scope>,
83
+ next_id: usize,
84
+ current_scope: ScopeId,
85
+ }
86
+
87
+ #[allow(dead_code)]
88
+ impl ScopeManager {
89
+ pub fn new() -> Self {
90
+ let top_level = Scope::new(ScopeId(0), ScopeKind::TopLevel, None);
91
+
92
+ let mut scopes = HashMap::new();
93
+ scopes.insert(ScopeId(0), top_level);
94
+
95
+ Self {
96
+ scopes,
97
+ next_id: 1,
98
+ current_scope: ScopeId(0),
99
+ }
100
+ }
101
+
102
+ /// 新しいスコープを作成
103
+ pub fn new_scope(&mut self, kind: ScopeKind) -> ScopeId {
104
+ let id = ScopeId(self.next_id);
105
+ self.next_id += 1;
106
+
107
+ let scope = Scope::new(id, kind, Some(self.current_scope));
108
+ self.scopes.insert(id, scope);
109
+
110
+ id
111
+ }
112
+
113
+ /// スコープに入る
114
+ pub fn enter_scope(&mut self, scope_id: ScopeId) {
115
+ self.current_scope = scope_id;
116
+ }
117
+
118
+ /// スコープから出る
119
+ pub fn exit_scope(&mut self) {
120
+ if let Some(scope) = self.scopes.get(&self.current_scope) {
121
+ if let Some(parent) = scope.parent {
122
+ self.current_scope = parent;
123
+ }
124
+ }
125
+ }
126
+
127
+ /// 現在のスコープを取得
128
+ pub fn current_scope(&self) -> &Scope {
129
+ self.scopes.get(&self.current_scope).unwrap()
130
+ }
131
+
132
+ /// 現在のスコープを可変で取得
133
+ pub fn current_scope_mut(&mut self) -> &mut Scope {
134
+ self.scopes.get_mut(&self.current_scope).unwrap()
135
+ }
136
+
137
+ /// スコープを取得
138
+ pub fn get_scope(&self, id: ScopeId) -> Option<&Scope> {
139
+ self.scopes.get(&id)
140
+ }
141
+
142
+ /// スコープを可変で取得
143
+ pub fn get_scope_mut(&mut self, id: ScopeId) -> Option<&mut Scope> {
144
+ self.scopes.get_mut(&id)
145
+ }
146
+
147
+ /// 変数を現在のスコープまたは親スコープから検索
148
+ pub fn lookup_var(&self, name: &str) -> Option<VertexId> {
149
+ let mut current = Some(self.current_scope);
150
+
151
+ while let Some(scope_id) = current {
152
+ if let Some(scope) = self.scopes.get(&scope_id) {
153
+ if let Some(vtx) = scope.get_local_var(name) {
154
+ return Some(vtx);
155
+ }
156
+ current = scope.parent;
157
+ } else {
158
+ break;
159
+ }
160
+ }
161
+
162
+ None
163
+ }
164
+
165
+ /// インスタンス変数を現在のクラススコープから検索
166
+ pub fn lookup_instance_var(&self, name: &str) -> Option<VertexId> {
167
+ let mut current = Some(self.current_scope);
168
+
169
+ while let Some(scope_id) = current {
170
+ if let Some(scope) = self.scopes.get(&scope_id) {
171
+ // クラススコープまで遡る
172
+ match &scope.kind {
173
+ ScopeKind::Class { .. } => {
174
+ return scope.get_instance_var(name);
175
+ }
176
+ _ => {
177
+ current = scope.parent;
178
+ }
179
+ }
180
+ } else {
181
+ break;
182
+ }
183
+ }
184
+
185
+ None
186
+ }
187
+
188
+ /// インスタンス変数を現在のクラススコープに設定
189
+ pub fn set_instance_var_in_class(&mut self, name: String, vtx: VertexId) {
190
+ let mut current = Some(self.current_scope);
191
+
192
+ while let Some(scope_id) = current {
193
+ if let Some(scope) = self.scopes.get(&scope_id) {
194
+ // クラススコープを見つけたら設定
195
+ match &scope.kind {
196
+ ScopeKind::Class { .. } => {
197
+ if let Some(class_scope) = self.scopes.get_mut(&scope_id) {
198
+ class_scope.set_instance_var(name, vtx);
199
+ }
200
+ return;
201
+ }
202
+ _ => {
203
+ current = scope.parent;
204
+ }
205
+ }
206
+ } else {
207
+ break;
208
+ }
209
+ }
210
+ }
211
+
212
+ /// 現在のクラス名を取得
213
+ pub fn current_class_name(&self) -> Option<String> {
214
+ let mut current = Some(self.current_scope);
215
+
216
+ while let Some(scope_id) = current {
217
+ if let Some(scope) = self.scopes.get(&scope_id) {
218
+ if let ScopeKind::Class { name, .. } = &scope.kind {
219
+ return Some(name.clone());
220
+ }
221
+ current = scope.parent;
222
+ } else {
223
+ break;
224
+ }
225
+ }
226
+
227
+ None
228
+ }
229
+ }
230
+
231
+ #[cfg(test)]
232
+ mod tests {
233
+ use super::*;
234
+
235
+ #[test]
236
+ fn test_scope_manager_creation() {
237
+ let sm = ScopeManager::new();
238
+ assert_eq!(sm.current_scope().id, ScopeId(0));
239
+ assert!(matches!(sm.current_scope().kind, ScopeKind::TopLevel));
240
+ }
241
+
242
+ #[test]
243
+ fn test_scope_manager_new_scope() {
244
+ let mut sm = ScopeManager::new();
245
+
246
+ let class_id = sm.new_scope(ScopeKind::Class {
247
+ name: "User".to_string(),
248
+ superclass: None,
249
+ });
250
+
251
+ assert_eq!(class_id, ScopeId(1));
252
+ assert_eq!(sm.current_scope().id, ScopeId(0)); // Still in top-level
253
+ }
254
+
255
+ #[test]
256
+ fn test_scope_manager_enter_exit() {
257
+ let mut sm = ScopeManager::new();
258
+
259
+ let class_id = sm.new_scope(ScopeKind::Class {
260
+ name: "User".to_string(),
261
+ superclass: None,
262
+ });
263
+
264
+ sm.enter_scope(class_id);
265
+ assert_eq!(sm.current_scope().id, ScopeId(1));
266
+
267
+ sm.exit_scope();
268
+ assert_eq!(sm.current_scope().id, ScopeId(0));
269
+ }
270
+
271
+ #[test]
272
+ fn test_scope_manager_local_var() {
273
+ let mut sm = ScopeManager::new();
274
+
275
+ sm.current_scope_mut()
276
+ .set_local_var("x".to_string(), VertexId(10));
277
+
278
+ assert_eq!(sm.lookup_var("x"), Some(VertexId(10)));
279
+ assert_eq!(sm.lookup_var("y"), None);
280
+ }
281
+
282
+ #[test]
283
+ fn test_scope_manager_nested_lookup() {
284
+ let mut sm = ScopeManager::new();
285
+
286
+ // Top level: x = 10
287
+ sm.current_scope_mut()
288
+ .set_local_var("x".to_string(), VertexId(10));
289
+
290
+ // Enter class
291
+ let class_id = sm.new_scope(ScopeKind::Class {
292
+ name: "User".to_string(),
293
+ superclass: None,
294
+ });
295
+ sm.enter_scope(class_id);
296
+
297
+ // Class level: y = 20
298
+ sm.current_scope_mut()
299
+ .set_local_var("y".to_string(), VertexId(20));
300
+
301
+ // Can lookup both x (from parent) and y (from current)
302
+ assert_eq!(sm.lookup_var("x"), Some(VertexId(10)));
303
+ assert_eq!(sm.lookup_var("y"), Some(VertexId(20)));
304
+ }
305
+
306
+ #[test]
307
+ fn test_scope_manager_current_class_name() {
308
+ let mut sm = ScopeManager::new();
309
+
310
+ assert_eq!(sm.current_class_name(), None);
311
+
312
+ let class_id = sm.new_scope(ScopeKind::Class {
313
+ name: "User".to_string(),
314
+ superclass: None,
315
+ });
316
+ sm.enter_scope(class_id);
317
+
318
+ assert_eq!(sm.current_class_name(), Some("User".to_string()));
319
+
320
+ // Enter method within class
321
+ let method_id = sm.new_scope(ScopeKind::Method {
322
+ name: "test".to_string(),
323
+ receiver_type: None,
324
+ });
325
+ sm.enter_scope(method_id);
326
+
327
+ // Should still find parent class name
328
+ assert_eq!(sm.current_class_name(), Some("User".to_string()));
329
+ }
330
+ }
@@ -0,0 +1,23 @@
1
+ //! Type error definitions for diagnostic reporting
2
+
3
+ use crate::source_map::SourceLocation;
4
+ use crate::types::Type;
5
+
6
+ /// Type error information for diagnostic reporting
7
+ #[derive(Debug, Clone)]
8
+ pub struct TypeError {
9
+ pub receiver_type: Type,
10
+ pub method_name: String,
11
+ pub location: Option<SourceLocation>,
12
+ }
13
+
14
+ impl TypeError {
15
+ /// Create a new type error
16
+ pub fn new(receiver_type: Type, method_name: String, location: Option<SourceLocation>) -> Self {
17
+ Self {
18
+ receiver_type,
19
+ method_name,
20
+ location,
21
+ }
22
+ }
23
+ }
@@ -0,0 +1,195 @@
1
+ //! Vertex and Source management
2
+ //!
3
+ //! Handles creation, storage, and type propagation for vertices and sources.
4
+
5
+ use crate::graph::{Source, Vertex, VertexId};
6
+ use crate::types::Type;
7
+ use std::collections::HashMap;
8
+
9
+ /// Manages vertices and sources in the type graph
10
+ #[derive(Debug, Default)]
11
+ pub struct VertexManager {
12
+ /// All vertices in the graph
13
+ pub vertices: HashMap<VertexId, Vertex>,
14
+ /// All sources (fixed-type nodes) in the graph
15
+ pub sources: HashMap<VertexId, Source>,
16
+ /// Next vertex ID to allocate
17
+ next_vertex_id: usize,
18
+ }
19
+
20
+ #[allow(dead_code)]
21
+ impl VertexManager {
22
+ /// Create a new empty vertex manager
23
+ pub fn new() -> Self {
24
+ Self {
25
+ vertices: HashMap::new(),
26
+ sources: HashMap::new(),
27
+ next_vertex_id: 0,
28
+ }
29
+ }
30
+
31
+ /// Create a new vertex and return its ID
32
+ pub fn new_vertex(&mut self) -> VertexId {
33
+ let id = VertexId(self.next_vertex_id);
34
+ self.next_vertex_id += 1;
35
+ self.vertices.insert(id, Vertex::new());
36
+ id
37
+ }
38
+
39
+ /// Create a new source with a fixed type
40
+ pub fn new_source(&mut self, ty: Type) -> VertexId {
41
+ let id = VertexId(self.next_vertex_id);
42
+ self.next_vertex_id += 1;
43
+ self.sources.insert(id, Source::new(ty));
44
+ id
45
+ }
46
+
47
+ /// Get a vertex by ID
48
+ pub fn get_vertex(&self, id: VertexId) -> Option<&Vertex> {
49
+ self.vertices.get(&id)
50
+ }
51
+
52
+ /// Get a vertex mutably by ID
53
+ pub fn get_vertex_mut(&mut self, id: VertexId) -> Option<&mut Vertex> {
54
+ self.vertices.get_mut(&id)
55
+ }
56
+
57
+ /// Get a source by ID
58
+ pub fn get_source(&self, id: VertexId) -> Option<&Source> {
59
+ self.sources.get(&id)
60
+ }
61
+
62
+ /// Add an edge between two vertices and propagate types
63
+ pub fn add_edge(&mut self, src: VertexId, dst: VertexId) {
64
+ // Add edge from src to dst
65
+ if let Some(src_vtx) = self.vertices.get_mut(&src) {
66
+ src_vtx.add_next(dst);
67
+ }
68
+
69
+ // Propagate type
70
+ self.propagate_from(src, dst);
71
+ }
72
+
73
+ /// Get types from a vertex or source
74
+ fn get_types(&self, id: VertexId) -> Vec<Type> {
75
+ if let Some(vtx) = self.vertices.get(&id) {
76
+ vtx.types.keys().cloned().collect()
77
+ } else if let Some(src) = self.sources.get(&id) {
78
+ vec![src.ty.clone()]
79
+ } else {
80
+ vec![]
81
+ }
82
+ }
83
+
84
+ /// Propagate types from src to dst
85
+ fn propagate_from(&mut self, src: VertexId, dst: VertexId) {
86
+ let types = self.get_types(src);
87
+ if !types.is_empty() {
88
+ self.propagate_types(src, dst, types);
89
+ }
90
+ }
91
+
92
+ /// Recursively propagate types through the graph
93
+ fn propagate_types(&mut self, src_id: VertexId, dst_id: VertexId, types: Vec<Type>) {
94
+ // Add type only if dst is a Vertex (not a Source)
95
+ let next_propagations = if let Some(dst_vtx) = self.vertices.get_mut(&dst_id) {
96
+ dst_vtx.on_type_added(src_id, types)
97
+ } else {
98
+ // If dst is a Source, do nothing (fixed type)
99
+ return;
100
+ };
101
+
102
+ // Recursively propagate to next vertices
103
+ for (next_id, next_types) in next_propagations {
104
+ self.propagate_types(dst_id, next_id, next_types);
105
+ }
106
+ }
107
+
108
+ /// Display all vertices and sources for debugging
109
+ pub fn show_all(&self) -> String {
110
+ let mut lines = Vec::new();
111
+
112
+ for (id, vtx) in &self.vertices {
113
+ lines.push(format!("Vertex {}: {}", id.0, vtx.show()));
114
+ }
115
+
116
+ for (id, src) in &self.sources {
117
+ lines.push(format!("Source {}: {}", id.0, src.ty.show()));
118
+ }
119
+
120
+ lines.join("\n")
121
+ }
122
+ }
123
+
124
+ #[cfg(test)]
125
+ mod tests {
126
+ use super::*;
127
+
128
+ #[test]
129
+ fn test_new_vertex() {
130
+ let mut manager = VertexManager::new();
131
+
132
+ let v1 = manager.new_vertex();
133
+ let v2 = manager.new_vertex();
134
+
135
+ assert_eq!(v1.0, 0);
136
+ assert_eq!(v2.0, 1);
137
+ assert!(manager.get_vertex(v1).is_some());
138
+ assert!(manager.get_vertex(v2).is_some());
139
+ }
140
+
141
+ #[test]
142
+ fn test_new_source() {
143
+ let mut manager = VertexManager::new();
144
+
145
+ let s1 = manager.new_source(Type::string());
146
+ let s2 = manager.new_source(Type::integer());
147
+
148
+ assert_eq!(manager.get_source(s1).unwrap().ty.show(), "String");
149
+ assert_eq!(manager.get_source(s2).unwrap().ty.show(), "Integer");
150
+ }
151
+
152
+ #[test]
153
+ fn test_edge_propagation() {
154
+ let mut manager = VertexManager::new();
155
+
156
+ let src = manager.new_source(Type::string());
157
+ let vtx = manager.new_vertex();
158
+
159
+ manager.add_edge(src, vtx);
160
+
161
+ assert_eq!(manager.get_vertex(vtx).unwrap().show(), "String");
162
+ }
163
+
164
+ #[test]
165
+ fn test_chain_propagation() {
166
+ let mut manager = VertexManager::new();
167
+
168
+ let src = manager.new_source(Type::string());
169
+ let v1 = manager.new_vertex();
170
+ let v2 = manager.new_vertex();
171
+
172
+ manager.add_edge(src, v1);
173
+ manager.add_edge(v1, v2);
174
+
175
+ assert_eq!(manager.get_vertex(v1).unwrap().show(), "String");
176
+ assert_eq!(manager.get_vertex(v2).unwrap().show(), "String");
177
+ }
178
+
179
+ #[test]
180
+ fn test_union_propagation() {
181
+ let mut manager = VertexManager::new();
182
+
183
+ let src1 = manager.new_source(Type::string());
184
+ let src2 = manager.new_source(Type::integer());
185
+ let vtx = manager.new_vertex();
186
+
187
+ manager.add_edge(src1, vtx);
188
+ manager.add_edge(src2, vtx);
189
+
190
+ assert_eq!(
191
+ manager.get_vertex(vtx).unwrap().show(),
192
+ "(Integer | String)"
193
+ );
194
+ }
195
+ }
@@ -0,0 +1,157 @@
1
+ use crate::env::GlobalEnv;
2
+ use crate::graph::change_set::ChangeSet;
3
+ use crate::graph::vertex::VertexId;
4
+ use crate::source_map::SourceLocation;
5
+ use crate::types::Type;
6
+
7
+ /// Unique ID for Box
8
+ #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
9
+ pub struct BoxId(pub usize);
10
+
11
+ /// Box trait: represents constraints such as method calls
12
+ #[allow(dead_code)]
13
+ pub trait BoxTrait: Send + Sync {
14
+ fn id(&self) -> BoxId;
15
+ fn run(&mut self, genv: &mut GlobalEnv, changes: &mut ChangeSet);
16
+ fn ret(&self) -> VertexId;
17
+ }
18
+
19
+ /// Box representing a method call
20
+ #[allow(dead_code)]
21
+ pub struct MethodCallBox {
22
+ id: BoxId,
23
+ recv: VertexId,
24
+ method_name: String,
25
+ ret: VertexId,
26
+ location: Option<SourceLocation>, // Source code location
27
+ }
28
+
29
+ impl MethodCallBox {
30
+ pub fn new(
31
+ id: BoxId,
32
+ recv: VertexId,
33
+ method_name: String,
34
+ ret: VertexId,
35
+ location: Option<SourceLocation>,
36
+ ) -> Self {
37
+ Self {
38
+ id,
39
+ recv,
40
+ method_name,
41
+ ret,
42
+ location,
43
+ }
44
+ }
45
+ }
46
+
47
+ impl BoxTrait for MethodCallBox {
48
+ fn id(&self) -> BoxId {
49
+ self.id
50
+ }
51
+
52
+ fn ret(&self) -> VertexId {
53
+ self.ret
54
+ }
55
+
56
+ fn run(&mut self, genv: &mut GlobalEnv, changes: &mut ChangeSet) {
57
+ // Get receiver type (handles both Vertex and Source)
58
+ let recv_types: Vec<Type> = if let Some(recv_vertex) = genv.get_vertex(self.recv) {
59
+ // Vertex case: may have multiple types
60
+ recv_vertex.types.keys().cloned().collect()
61
+ } else if let Some(recv_source) = genv.get_source(self.recv) {
62
+ // Source case: has one fixed type (e.g., literals)
63
+ vec![recv_source.ty.clone()]
64
+ } else {
65
+ // Receiver not found
66
+ return;
67
+ };
68
+
69
+ for recv_ty in recv_types {
70
+ // Resolve method
71
+ if let Some(method_info) = genv.resolve_method(&recv_ty, &self.method_name) {
72
+ // Create return type as Source
73
+ let ret_src_id = genv.new_source(method_info.return_type.clone());
74
+
75
+ // Add edge to return value
76
+ changes.add_edge(ret_src_id, self.ret);
77
+ } else {
78
+ // Record type error for diagnostic reporting
79
+ genv.record_type_error(
80
+ recv_ty.clone(),
81
+ self.method_name.clone(),
82
+ self.location.clone(),
83
+ );
84
+ }
85
+ }
86
+ }
87
+ }
88
+
89
+ #[cfg(test)]
90
+ mod tests {
91
+ use super::*;
92
+ use crate::env::GlobalEnv;
93
+ use crate::types::Type;
94
+
95
+ #[test]
96
+ fn test_method_call_box() {
97
+ let mut genv = GlobalEnv::new();
98
+
99
+ // Register String#upcase
100
+ genv.register_builtin_method(Type::string(), "upcase", Type::string());
101
+
102
+ // x = "hello" (Source<String> -> Vertex)
103
+ let x_vtx = genv.new_vertex();
104
+ let str_src = genv.new_source(Type::string());
105
+ genv.add_edge(str_src, x_vtx);
106
+
107
+ // x.upcase
108
+ let ret_vtx = genv.new_vertex();
109
+ let box_id = BoxId(0);
110
+ let mut call_box = MethodCallBox::new(
111
+ box_id,
112
+ x_vtx,
113
+ "upcase".to_string(),
114
+ ret_vtx,
115
+ None, // No location in test
116
+ );
117
+
118
+ // Execute Box
119
+ let mut changes = ChangeSet::new();
120
+ call_box.run(&mut genv, &mut changes);
121
+ genv.apply_changes(changes);
122
+
123
+ // Check return type
124
+ let ret_vertex = genv.get_vertex(ret_vtx).unwrap();
125
+ assert_eq!(ret_vertex.show(), "String");
126
+ }
127
+
128
+ #[test]
129
+ fn test_method_call_box_undefined() {
130
+ let mut genv = GlobalEnv::new();
131
+
132
+ // Don't register method
133
+
134
+ let x_vtx = genv.new_vertex();
135
+ let str_src = genv.new_source(Type::string());
136
+ genv.add_edge(str_src, x_vtx);
137
+
138
+ // x.unknown_method (undefined)
139
+ let ret_vtx = genv.new_vertex();
140
+ let box_id = BoxId(0);
141
+ let mut call_box = MethodCallBox::new(
142
+ box_id,
143
+ x_vtx,
144
+ "unknown_method".to_string(),
145
+ ret_vtx,
146
+ None, // No location in test
147
+ );
148
+
149
+ let mut changes = ChangeSet::new();
150
+ call_box.run(&mut genv, &mut changes);
151
+ genv.apply_changes(changes);
152
+
153
+ // Return value is untyped
154
+ let ret_vertex = genv.get_vertex(ret_vtx).unwrap();
155
+ assert_eq!(ret_vertex.show(), "untyped");
156
+ }
157
+ }