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.
@@ -1,3 +1,5 @@
1
+ use std::collections::HashMap;
2
+
1
3
  use crate::env::GlobalEnv;
2
4
  use crate::graph::change_set::ChangeSet;
3
5
  use crate::graph::vertex::VertexId;
@@ -9,21 +11,48 @@ use crate::types::Type;
9
11
  pub struct BoxId(pub usize);
10
12
 
11
13
  /// Box trait: represents constraints such as method calls
12
- #[allow(dead_code)]
13
14
  pub trait BoxTrait: Send + Sync {
14
15
  fn id(&self) -> BoxId;
15
16
  fn run(&mut self, genv: &mut GlobalEnv, changes: &mut ChangeSet);
16
17
  fn ret(&self) -> VertexId;
17
18
  }
18
19
 
20
+ /// Propagate argument types to parameter vertices by adding edges
21
+ /// from each argument vertex to the corresponding parameter vertex.
22
+ fn propagate_arguments(
23
+ arg_vtxs: &[VertexId],
24
+ param_vtxs: Option<&[VertexId]>,
25
+ changes: &mut ChangeSet,
26
+ ) {
27
+ for (arg_vtx, param_vtx) in arg_vtxs.iter().zip(param_vtxs.unwrap_or_default()) {
28
+ changes.add_edge(*arg_vtx, *param_vtx);
29
+ }
30
+ }
31
+
32
+ /// Propagate keyword argument types to keyword parameter vertices by name
33
+ fn propagate_keyword_arguments(
34
+ kwarg_vtxs: Option<&HashMap<String, VertexId>>,
35
+ kw_param_vtxs: Option<&HashMap<String, VertexId>>,
36
+ changes: &mut ChangeSet,
37
+ ) {
38
+ let (Some(args), Some(params)) = (kwarg_vtxs, kw_param_vtxs) else {
39
+ return;
40
+ };
41
+ for (name, arg_vtx) in args {
42
+ if let Some(&param_vtx) = params.get(name) {
43
+ changes.add_edge(*arg_vtx, param_vtx);
44
+ }
45
+ }
46
+ }
47
+
19
48
  /// Box representing a method call
20
- #[allow(dead_code)]
21
49
  pub struct MethodCallBox {
22
50
  id: BoxId,
23
51
  recv: VertexId,
24
52
  method_name: String,
25
53
  ret: VertexId,
26
54
  arg_vtxs: Vec<VertexId>,
55
+ kwarg_vtxs: Option<HashMap<String, VertexId>>,
27
56
  location: Option<SourceLocation>, // Source code location
28
57
  /// Number of times this box has been rescheduled
29
58
  reschedule_count: u8,
@@ -39,6 +68,7 @@ impl MethodCallBox {
39
68
  method_name: String,
40
69
  ret: VertexId,
41
70
  arg_vtxs: Vec<VertexId>,
71
+ kwarg_vtxs: Option<HashMap<String, VertexId>>,
42
72
  location: Option<SourceLocation>,
43
73
  ) -> Self {
44
74
  Self {
@@ -47,10 +77,94 @@ impl MethodCallBox {
47
77
  method_name,
48
78
  ret,
49
79
  arg_vtxs,
80
+ kwarg_vtxs,
50
81
  location,
51
82
  reschedule_count: 0,
52
83
  }
53
84
  }
85
+
86
+ /// Reschedule this box for re-execution if the limit hasn't been reached.
87
+ /// Handles cases where the receiver has no types yet (e.g., block parameters
88
+ /// that get typed by a later box). If max reschedules are reached, the box
89
+ /// is silently dropped (receiver type remains unknown).
90
+ fn try_reschedule(&mut self, changes: &mut ChangeSet) {
91
+ if self.reschedule_count < MAX_RESCHEDULE_COUNT {
92
+ self.reschedule_count += 1;
93
+ changes.reschedule(self.id);
94
+ }
95
+ }
96
+
97
+ fn process_recv_type(
98
+ &self,
99
+ recv_ty: &Type,
100
+ genv: &mut GlobalEnv,
101
+ changes: &mut ChangeSet,
102
+ ) {
103
+ if let Some(method_info) = genv.resolve_method(recv_ty, &self.method_name) {
104
+ if let Some(return_vtx) = method_info.return_vertex {
105
+ // User-defined method: connect body's return vertex to call site
106
+ changes.add_edge(return_vtx, self.ret);
107
+ propagate_arguments(
108
+ &self.arg_vtxs,
109
+ method_info.param_vertices.as_deref(),
110
+ changes,
111
+ );
112
+ propagate_keyword_arguments(
113
+ self.kwarg_vtxs.as_ref(),
114
+ method_info.keyword_param_vertices.as_ref(),
115
+ changes,
116
+ );
117
+ } else {
118
+ // Builtin/RBS method: create source with fixed return type
119
+ let ret_src_id = genv.new_source(method_info.return_type.clone());
120
+ changes.add_edge(ret_src_id, self.ret);
121
+ }
122
+ } else if self.method_name == "new" {
123
+ self.handle_new_call(recv_ty, genv, changes);
124
+ } else if !matches!(recv_ty, Type::Singleton { .. }) {
125
+ // Singleton types with unresolved methods are silently skipped;
126
+ // these are typically RBS class methods not yet supported.
127
+ self.report_type_error(recv_ty, genv);
128
+ }
129
+ }
130
+
131
+ /// Handle `.new` calls: singleton(Foo)#new produces instance(Foo),
132
+ /// and propagates arguments to the `initialize` method's parameters.
133
+ fn handle_new_call(
134
+ &self,
135
+ recv_ty: &Type,
136
+ genv: &mut GlobalEnv,
137
+ changes: &mut ChangeSet,
138
+ ) {
139
+ if let Type::Singleton { name } = recv_ty {
140
+ let instance_type = Type::instance(name.full_name());
141
+
142
+ let ret_src = genv.new_source(instance_type.clone());
143
+ changes.add_edge(ret_src, self.ret);
144
+
145
+ let init_info = genv.resolve_method(&instance_type, "initialize");
146
+ propagate_arguments(
147
+ &self.arg_vtxs,
148
+ init_info.and_then(|info| info.param_vertices.as_deref()),
149
+ changes,
150
+ );
151
+ propagate_keyword_arguments(
152
+ self.kwarg_vtxs.as_ref(),
153
+ init_info.and_then(|info| info.keyword_param_vertices.as_ref()),
154
+ changes,
155
+ );
156
+ } else {
157
+ self.report_type_error(recv_ty, genv);
158
+ }
159
+ }
160
+
161
+ fn report_type_error(&self, recv_ty: &Type, genv: &mut GlobalEnv) {
162
+ genv.record_type_error(
163
+ recv_ty.clone(),
164
+ self.method_name.clone(),
165
+ self.location.clone(),
166
+ );
167
+ }
54
168
  }
55
169
 
56
170
  impl BoxTrait for MethodCallBox {
@@ -63,88 +177,17 @@ impl BoxTrait for MethodCallBox {
63
177
  }
64
178
 
65
179
  fn run(&mut self, genv: &mut GlobalEnv, changes: &mut ChangeSet) {
66
- // Get receiver type (handles both Vertex and Source)
67
- let recv_types: Vec<Type> = if let Some(recv_vertex) = genv.get_vertex(self.recv) {
68
- // Vertex case: may have multiple types
69
- recv_vertex.types.keys().cloned().collect()
70
- } else if let Some(recv_source) = genv.get_source(self.recv) {
71
- // Source case: has one fixed type (e.g., literals)
72
- vec![recv_source.ty.clone()]
73
- } else {
74
- // Receiver not found
180
+ let Some(recv_types) = genv.get_receiver_types(self.recv) else {
75
181
  return;
76
182
  };
77
183
 
78
- // If receiver has no types yet, reschedule this box for later
79
- // This handles cases like block parameters that are typed later
80
184
  if recv_types.is_empty() {
81
- if self.reschedule_count < MAX_RESCHEDULE_COUNT {
82
- self.reschedule_count += 1;
83
- changes.reschedule(self.id);
84
- }
85
- // If max reschedules reached, just skip (receiver type is unknown)
185
+ self.try_reschedule(changes);
86
186
  return;
87
187
  }
88
188
 
89
189
  for recv_ty in recv_types {
90
- // Resolve method
91
- if let Some(method_info) = genv.resolve_method(&recv_ty, &self.method_name) {
92
- if let Some(return_vtx) = method_info.return_vertex {
93
- // User-defined: edge from body's last expr → call site return
94
- changes.add_edge(return_vtx, self.ret);
95
-
96
- // Propagate argument types to parameter vertices
97
- if let Some(param_vtxs) = &method_info.param_vertices {
98
- for (i, param_vtx) in param_vtxs.iter().enumerate() {
99
- if let Some(arg_vtx) = self.arg_vtxs.get(i) {
100
- changes.add_edge(*arg_vtx, *param_vtx);
101
- }
102
- }
103
- }
104
- } else {
105
- // RBS/builtin: create Source with fixed return type
106
- let ret_src_id = genv.new_source(method_info.return_type.clone());
107
- changes.add_edge(ret_src_id, self.ret);
108
- }
109
- } else if self.method_name == "new" {
110
- if let Type::Singleton { name } = &recv_ty {
111
- // singleton(User)#new → instance(User)
112
- let instance_type = Type::instance(name.full_name());
113
- let ret_src = genv.new_source(instance_type.clone());
114
- changes.add_edge(ret_src, self.ret);
115
-
116
- // Propagate arguments to initialize parameters
117
- if let Some(init_info) = genv.resolve_method(&instance_type, "initialize") {
118
- if let Some(param_vtxs) = &init_info.param_vertices {
119
- for (i, param_vtx) in param_vtxs.iter().enumerate() {
120
- if let Some(arg_vtx) = self.arg_vtxs.get(i) {
121
- changes.add_edge(*arg_vtx, *param_vtx);
122
- }
123
- }
124
- }
125
- }
126
- continue;
127
- }
128
- // Non-singleton .new: record error
129
- genv.record_type_error(
130
- recv_ty.clone(),
131
- self.method_name.clone(),
132
- self.location.clone(),
133
- );
134
- } else if matches!(&recv_ty, Type::Singleton { .. }) {
135
- // Skip error for unknown class methods on Singleton types.
136
- // User-defined class methods (def self.foo) are resolved by
137
- // resolve_method above. Only unresolved methods reach here
138
- // (e.g., RBS class methods not yet supported).
139
- continue;
140
- } else {
141
- // Record type error for diagnostic reporting
142
- genv.record_type_error(
143
- recv_ty.clone(),
144
- self.method_name.clone(),
145
- self.location.clone(),
146
- );
147
- }
190
+ self.process_recv_type(&recv_ty, genv, changes);
148
191
  }
149
192
  }
150
193
  }
@@ -154,7 +197,6 @@ impl BoxTrait for MethodCallBox {
154
197
  /// When a method with a block is called (e.g., `str.each_char { |c| ... }`),
155
198
  /// this box resolves the block parameter types from the method's RBS definition
156
199
  /// and propagates them to the block parameter vertices.
157
- #[allow(dead_code)]
158
200
  pub struct BlockParameterTypeBox {
159
201
  id: BoxId,
160
202
  /// Receiver vertex of the method call
@@ -241,12 +283,7 @@ impl BoxTrait for BlockParameterTypeBox {
241
283
  }
242
284
 
243
285
  fn run(&mut self, genv: &mut GlobalEnv, changes: &mut ChangeSet) {
244
- // Get receiver types
245
- let recv_types: Vec<Type> = if let Some(recv_vertex) = genv.get_vertex(self.recv_vtx) {
246
- recv_vertex.types.keys().cloned().collect()
247
- } else if let Some(recv_source) = genv.get_source(self.recv_vtx) {
248
- vec![recv_source.ty.clone()]
249
- } else {
286
+ let Some(recv_types) = genv.get_receiver_types(self.recv_vtx) else {
250
287
  return;
251
288
  };
252
289
 
@@ -318,6 +355,7 @@ mod tests {
318
355
  "upcase".to_string(),
319
356
  ret_vtx,
320
357
  vec![],
358
+ None,
321
359
  None, // No location in test
322
360
  );
323
361
 
@@ -350,6 +388,7 @@ mod tests {
350
388
  "unknown_method".to_string(),
351
389
  ret_vtx,
352
390
  vec![],
391
+ None,
353
392
  None, // No location in test
354
393
  );
355
394
 
@@ -545,7 +584,7 @@ mod tests {
545
584
  let body_src = genv.new_source(Type::string());
546
585
 
547
586
  // Register user-defined method User#name with return_vertex
548
- genv.register_user_method(Type::instance("User"), "name", body_src, vec![]);
587
+ genv.register_user_method(Type::instance("User"), "name", body_src, vec![], None);
549
588
 
550
589
  // Simulate: user.name (receiver has type User)
551
590
  let recv_vtx = genv.new_vertex();
@@ -561,6 +600,7 @@ mod tests {
561
600
  ret_vtx,
562
601
  vec![],
563
602
  None,
603
+ None,
564
604
  );
565
605
  genv.register_box(box_id, Box::new(call_box));
566
606
 
@@ -591,6 +631,7 @@ mod tests {
591
631
  inner_ret_vtx,
592
632
  vec![],
593
633
  None,
634
+ None,
594
635
  );
595
636
  genv.register_box(inner_box_id, Box::new(inner_call));
596
637
 
@@ -600,6 +641,7 @@ mod tests {
600
641
  "format",
601
642
  inner_ret_vtx,
602
643
  vec![param_vtx],
644
+ None,
603
645
  );
604
646
 
605
647
  // 5. Simulate call: Formatter.new.format(42)
@@ -618,6 +660,7 @@ mod tests {
618
660
  call_ret_vtx,
619
661
  vec![arg_vtx],
620
662
  None,
663
+ None,
621
664
  );
622
665
  genv.register_box(call_box_id, Box::new(call_box));
623
666
 
@@ -630,4 +673,94 @@ mod tests {
630
673
  // Return type should be String (Integer#to_s -> String)
631
674
  assert_eq!(genv.get_vertex(call_ret_vtx).unwrap().show(), "String");
632
675
  }
676
+
677
+ #[test]
678
+ fn test_keyword_arg_propagation() {
679
+ let mut genv = GlobalEnv::new();
680
+
681
+ // Simulate: def greet(name:); name; end
682
+ let param_vtx = genv.new_vertex();
683
+ let mut kw_params = HashMap::new();
684
+ kw_params.insert("name".to_string(), param_vtx);
685
+
686
+ genv.register_user_method(
687
+ Type::instance("Greeter"),
688
+ "greet",
689
+ param_vtx, // return vertex = param (returns name)
690
+ vec![],
691
+ Some(kw_params),
692
+ );
693
+
694
+ // Simulate call: Greeter.new.greet(name: "Alice")
695
+ let recv_vtx = genv.new_vertex();
696
+ let recv_src = genv.new_source(Type::instance("Greeter"));
697
+ genv.add_edge(recv_src, recv_vtx);
698
+
699
+ let arg_vtx = genv.new_source(Type::string());
700
+ let mut kwarg_vtxs = HashMap::new();
701
+ kwarg_vtxs.insert("name".to_string(), arg_vtx);
702
+
703
+ let ret_vtx = genv.new_vertex();
704
+ let box_id = genv.alloc_box_id();
705
+ let call_box = MethodCallBox::new(
706
+ box_id,
707
+ recv_vtx,
708
+ "greet".to_string(),
709
+ ret_vtx,
710
+ vec![],
711
+ Some(kwarg_vtxs),
712
+ None,
713
+ );
714
+ genv.register_box(box_id, Box::new(call_box));
715
+
716
+ genv.run_all();
717
+
718
+ // param_vtx should have String type (propagated from keyword argument)
719
+ assert_eq!(genv.get_vertex(param_vtx).unwrap().show(), "String");
720
+ assert_eq!(genv.get_vertex(ret_vtx).unwrap().show(), "String");
721
+ }
722
+
723
+ #[test]
724
+ fn test_keyword_arg_name_mismatch_skipped() {
725
+ let mut genv = GlobalEnv::new();
726
+
727
+ let param_vtx = genv.new_vertex();
728
+ let mut kw_params = HashMap::new();
729
+ kw_params.insert("name".to_string(), param_vtx);
730
+
731
+ genv.register_user_method(
732
+ Type::instance("Greeter"),
733
+ "greet",
734
+ param_vtx,
735
+ vec![],
736
+ Some(kw_params),
737
+ );
738
+
739
+ let recv_vtx = genv.new_vertex();
740
+ let recv_src = genv.new_source(Type::instance("Greeter"));
741
+ genv.add_edge(recv_src, recv_vtx);
742
+
743
+ // Wrong keyword name: "title" instead of "name"
744
+ let arg_vtx = genv.new_source(Type::string());
745
+ let mut kwarg_vtxs = HashMap::new();
746
+ kwarg_vtxs.insert("title".to_string(), arg_vtx);
747
+
748
+ let ret_vtx = genv.new_vertex();
749
+ let box_id = genv.alloc_box_id();
750
+ let call_box = MethodCallBox::new(
751
+ box_id,
752
+ recv_vtx,
753
+ "greet".to_string(),
754
+ ret_vtx,
755
+ vec![],
756
+ Some(kwarg_vtxs),
757
+ None,
758
+ );
759
+ genv.register_box(box_id, Box::new(call_box));
760
+
761
+ genv.run_all();
762
+
763
+ // param_vtx should remain untyped (name mismatch → no propagation)
764
+ assert_eq!(genv.get_vertex(param_vtx).unwrap().show(), "untyped");
765
+ }
633
766
  }
@@ -10,6 +10,12 @@ pub struct ChangeSet {
10
10
  reschedule_boxes: Vec<BoxId>,
11
11
  }
12
12
 
13
+ impl Default for ChangeSet {
14
+ fn default() -> Self {
15
+ Self::new()
16
+ }
17
+ }
18
+
13
19
  impl ChangeSet {
14
20
  pub fn new() -> Self {
15
21
  Self {
@@ -75,6 +81,14 @@ pub enum EdgeUpdate {
75
81
  mod tests {
76
82
  use super::*;
77
83
 
84
+ #[test]
85
+ fn test_change_set_default() {
86
+ let mut cs = ChangeSet::default();
87
+ cs.add_edge(VertexId(1), VertexId(2));
88
+ let updates = cs.reinstall();
89
+ assert_eq!(updates.len(), 1);
90
+ }
91
+
78
92
  #[test]
79
93
  fn test_change_set_add() {
80
94
  let mut cs = ChangeSet::new();
@@ -132,7 +132,7 @@ pub async fn run_server() {
132
132
  let stdin = tokio::io::stdin();
133
133
  let stdout = tokio::io::stdout();
134
134
 
135
- let (service, socket) = LspService::new(|client| MethodRayServer::new(client));
135
+ let (service, socket) = LspService::new(MethodRayServer::new);
136
136
 
137
137
  Server::new(stdin, stdout, socket).serve(service).await;
138
138
  }
@@ -155,8 +155,7 @@ pub fn register_rbs_methods(genv: &mut GlobalEnv, ruby: &Ruby) -> Result<usize,
155
155
  cache.to_method_infos()
156
156
  } else {
157
157
  eprintln!("Cache invalid, reloading from RBS...");
158
- let methods = load_and_cache_rbs_methods(ruby, methodray_version, &rbs_version)?;
159
- methods
158
+ load_and_cache_rbs_methods(ruby, methodray_version, &rbs_version)?
160
159
  }
161
160
  } else {
162
161
  eprintln!("No cache found, loading from RBS...");
@@ -30,7 +30,6 @@ pub struct SourceLocation {
30
30
  pub length: usize,
31
31
  }
32
32
 
33
- #[allow(dead_code)]
34
33
  impl SourceLocation {
35
34
  pub fn new(line: usize, column: usize, length: usize) -> Self {
36
35
  Self {
data/rust/src/types.rs CHANGED
@@ -125,7 +125,6 @@ impl From<String> for QualifiedName {
125
125
 
126
126
  /// Type system for graph-based type inference
127
127
  #[derive(Clone, Debug, PartialEq, Eq, Hash)]
128
- #[allow(dead_code)]
129
128
  pub enum Type {
130
129
  /// Instance type: String, Integer, Api::User, etc.
131
130
  Instance { name: QualifiedName },
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: method-ray
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.1.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - dak2
@@ -48,14 +48,17 @@ files:
48
48
  - lib/methodray/commands.rb
49
49
  - lib/methodray/version.rb
50
50
  - rust/Cargo.toml
51
+ - rust/src/analyzer/assignments.rs
51
52
  - rust/src/analyzer/attributes.rs
52
53
  - rust/src/analyzer/blocks.rs
53
54
  - rust/src/analyzer/calls.rs
54
55
  - rust/src/analyzer/conditionals.rs
55
56
  - rust/src/analyzer/definitions.rs
56
57
  - rust/src/analyzer/dispatch.rs
58
+ - rust/src/analyzer/exceptions.rs
57
59
  - rust/src/analyzer/install.rs
58
60
  - rust/src/analyzer/literals.rs
61
+ - rust/src/analyzer/loops.rs
59
62
  - rust/src/analyzer/mod.rs
60
63
  - rust/src/analyzer/operators.rs
61
64
  - rust/src/analyzer/parameters.rs