method-ray 0.1.3 → 0.1.4

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.
@@ -5,26 +5,16 @@
5
5
  //! - Coordinating the graph construction process
6
6
 
7
7
  use crate::env::{GlobalEnv, LocalEnv};
8
- use crate::graph::{BlockParameterTypeBox, ChangeSet, VertexId};
9
- use crate::types::Type;
8
+ use crate::graph::{ChangeSet, VertexId};
10
9
  use ruby_prism::Node;
11
10
 
12
- use super::blocks::{enter_block_scope, exit_block_scope, install_block_parameter};
13
- use super::definitions::{
14
- exit_scope, extract_class_name, extract_module_name, install_class, install_method,
15
- install_module,
16
- };
17
- use super::dispatch::{
18
- dispatch_needs_child, dispatch_simple, finish_ivar_write, finish_local_var_write,
19
- finish_method_call, DispatchResult, NeedsChildKind,
20
- };
21
- use super::literals::install_literal;
22
- use super::parameters::{
23
- install_keyword_rest_parameter, install_optional_parameter, install_required_parameter,
24
- install_rest_parameter,
25
- };
11
+ use super::blocks::process_block_node;
12
+ use super::conditionals::{process_case_node, process_if_node, process_unless_node};
13
+ use super::definitions::{process_class_node, process_def_node, process_module_node};
14
+ use super::dispatch::{dispatch_needs_child, dispatch_simple, process_needs_child, DispatchResult};
15
+ use super::literals::install_literal_node;
26
16
 
27
- /// Build graph from AST
17
+ /// Build graph from AST (public API wrapper)
28
18
  pub struct AstInstaller<'a> {
29
19
  genv: &'a mut GlobalEnv,
30
20
  lenv: &'a mut LocalEnv,
@@ -42,885 +32,77 @@ impl<'a> AstInstaller<'a> {
42
32
  }
43
33
  }
44
34
 
45
- /// Install node (returns Vertex ID)
46
35
  pub fn install_node(&mut self, node: &Node) -> Option<VertexId> {
47
- // Class definition
48
- if let Some(class_node) = node.as_class_node() {
49
- return self.install_class_node(&class_node);
50
- }
51
-
52
- // Module definition
53
- if let Some(module_node) = node.as_module_node() {
54
- return self.install_module_node(&module_node);
55
- }
56
-
57
- // Method definition
58
- if let Some(def_node) = node.as_def_node() {
59
- return self.install_def_node(&def_node);
60
- }
61
-
62
- // Block node (standalone block, e.g., lambda { |x| x })
63
- if let Some(block_node) = node.as_block_node() {
64
- return self.install_block_node(&block_node);
65
- }
66
-
67
- // Try simple dispatch first (no child processing needed)
68
- match dispatch_simple(self.genv, self.lenv, node) {
69
- DispatchResult::Vertex(vtx) => return Some(vtx),
70
- DispatchResult::NotHandled => {}
71
- }
72
-
73
- // Literals (String, Integer, Array, Hash, nil, true, false, Symbol)
74
- if let Some(vtx) = self.install_literal_node(node) {
75
- return Some(vtx);
76
- }
77
-
78
- // Check if node needs child processing
79
- if let Some(kind) = dispatch_needs_child(node, self.source) {
80
- return self.process_needs_child(kind);
81
- }
82
-
83
- None
84
- }
85
-
86
- /// Install literal node
87
- ///
88
- /// Handles all literals including Array and Hash with element type inference
89
- fn install_literal_node(&mut self, node: &Node) -> Option<VertexId> {
90
- // Array literals need special handling for element type inference
91
- if node.as_array_node().is_some() {
92
- let elements: Vec<Node> = node.as_array_node().unwrap().elements().iter().collect();
93
- return self.install_array_literal_elements(elements);
94
- }
95
-
96
- // Hash literals need special handling for key/value type inference
97
- if node.as_hash_node().is_some() {
98
- let elements: Vec<Node> = node.as_hash_node().unwrap().elements().iter().collect();
99
- return self.install_hash_literal_elements(elements);
100
- }
101
-
102
- // Range literals need special handling for element type inference
103
- if let Some(range_node) = node.as_range_node() {
104
- return self.install_range_literal(&range_node);
105
- }
106
-
107
- // Other literals (String, Integer, nil, true, false, Symbol)
108
- install_literal(self.genv, node)
109
- }
110
-
111
- /// Install array literal with pre-collected elements
112
- fn install_array_literal_elements(&mut self, elements: Vec<Node>) -> Option<VertexId> {
113
- use crate::types::Type;
114
- use std::collections::HashSet;
115
-
116
- if elements.is_empty() {
117
- return Some(self.genv.new_source(Type::array()));
118
- }
119
-
120
- let mut element_types: HashSet<Type> = HashSet::new();
121
-
122
- for element in &elements {
123
- if let Some(vtx) = self.install_node(element) {
124
- if let Some(source) = self.genv.get_source(vtx) {
125
- element_types.insert(source.ty.clone());
126
- } else if let Some(vertex) = self.genv.get_vertex(vtx) {
127
- for ty in vertex.types.keys() {
128
- element_types.insert(ty.clone());
129
- }
130
- }
131
- }
132
- }
133
-
134
- let array_type = if element_types.is_empty() {
135
- Type::array()
136
- } else if element_types.len() == 1 {
137
- let elem_type = element_types.into_iter().next().unwrap();
138
- Type::array_of(elem_type)
139
- } else {
140
- let types_vec: Vec<Type> = element_types.into_iter().collect();
141
- let union_type = Type::Union(types_vec);
142
- Type::array_of(union_type)
143
- };
144
-
145
- Some(self.genv.new_source(array_type))
146
- }
147
-
148
- /// Install hash literal with element type inference
149
- fn install_hash_literal_elements(&mut self, elements: Vec<Node>) -> Option<VertexId> {
150
- use crate::types::Type;
151
- use std::collections::HashSet;
152
-
153
- if elements.is_empty() {
154
- return Some(self.genv.new_source(Type::hash()));
155
- }
156
-
157
- let mut key_types: HashSet<Type> = HashSet::new();
158
- let mut value_types: HashSet<Type> = HashSet::new();
159
-
160
- for element in &elements {
161
- if let Some(assoc_node) = element.as_assoc_node() {
162
- // Infer key type
163
- if let Some(key_vtx) = self.install_node(&assoc_node.key()) {
164
- if let Some(source) = self.genv.get_source(key_vtx) {
165
- key_types.insert(source.ty.clone());
166
- } else if let Some(vertex) = self.genv.get_vertex(key_vtx) {
167
- for ty in vertex.types.keys() {
168
- key_types.insert(ty.clone());
169
- }
170
- }
171
- }
172
-
173
- // Infer value type
174
- if let Some(value_vtx) = self.install_node(&assoc_node.value()) {
175
- if let Some(source) = self.genv.get_source(value_vtx) {
176
- value_types.insert(source.ty.clone());
177
- } else if let Some(vertex) = self.genv.get_vertex(value_vtx) {
178
- for ty in vertex.types.keys() {
179
- value_types.insert(ty.clone());
180
- }
181
- }
182
- }
183
- }
184
- }
185
-
186
- let hash_type = if key_types.is_empty() || value_types.is_empty() {
187
- Type::hash()
188
- } else {
189
- // Build key type (single type or union)
190
- let key_type = if key_types.len() == 1 {
191
- key_types.into_iter().next().unwrap()
192
- } else {
193
- let types_vec: Vec<Type> = key_types.into_iter().collect();
194
- Type::Union(types_vec)
195
- };
196
-
197
- // Build value type (single type or union)
198
- let value_type = if value_types.len() == 1 {
199
- value_types.into_iter().next().unwrap()
200
- } else {
201
- let types_vec: Vec<Type> = value_types.into_iter().collect();
202
- Type::Union(types_vec)
203
- };
204
-
205
- Type::hash_of(key_type, value_type)
206
- };
207
-
208
- Some(self.genv.new_source(hash_type))
209
- }
210
-
211
- /// Install range literal with element type inference
212
- fn install_range_literal(&mut self, range_node: &ruby_prism::RangeNode) -> Option<VertexId> {
213
- // Try to infer element type from left or right endpoint
214
- let element_type = if let Some(left) = range_node.left() {
215
- self.infer_range_element_type(&left)
216
- } else if let Some(right) = range_node.right() {
217
- self.infer_range_element_type(&right)
218
- } else {
219
- None
220
- };
221
-
222
- let range_type = match element_type {
223
- Some(ty) => Type::range_of(ty),
224
- None => Type::range(),
225
- };
226
-
227
- Some(self.genv.new_source(range_type))
228
- }
229
-
230
- /// Infer element type from a range endpoint node
231
- fn infer_range_element_type(&mut self, node: &Node) -> Option<Type> {
232
- // Install the node and get its type
233
- if let Some(vtx) = self.install_node(node) {
234
- if let Some(source) = self.genv.get_source(vtx) {
235
- return Some(source.ty.clone());
236
- }
237
- if let Some(vertex) = self.genv.get_vertex(vtx) {
238
- // Get first type from vertex (simplified)
239
- if let Some(ty) = vertex.types.keys().next() {
240
- return Some(ty.clone());
241
- }
242
- }
243
- }
244
- None
245
- }
246
-
247
- /// Process nodes that need child evaluation first
248
- fn process_needs_child(&mut self, kind: NeedsChildKind) -> Option<VertexId> {
249
- match kind {
250
- NeedsChildKind::IvarWrite { ivar_name, value } => {
251
- let value_vtx = self.install_node(&value)?;
252
- Some(finish_ivar_write(self.genv, ivar_name, value_vtx))
253
- }
254
- NeedsChildKind::LocalVarWrite { var_name, value } => {
255
- let value_vtx = self.install_node(&value)?;
256
- Some(finish_local_var_write(
257
- self.genv,
258
- self.lenv,
259
- &mut self.changes,
260
- var_name,
261
- value_vtx,
262
- ))
263
- }
264
- NeedsChildKind::MethodCall {
265
- receiver,
266
- method_name,
267
- location,
268
- block,
269
- } => {
270
- let recv_vtx = self.install_node(&receiver)?;
271
-
272
- // Process block if present (e.g., `x.each { |i| ... }`)
273
- // Collect block parameter vertex IDs for type inference
274
- let mut block_param_vtxs: Vec<VertexId> = Vec::new();
275
- if let Some(block_node) = block {
276
- // Block may be a BlockNode or BlockArgumentNode
277
- if let Some(bn) = block_node.as_block_node() {
278
- block_param_vtxs = self.install_block_node_with_params(&bn);
279
- }
280
- }
281
-
282
- // Create BlockParameterTypeBox if block has parameters
283
- if !block_param_vtxs.is_empty() {
284
- let box_id = self.genv.alloc_box_id();
285
- let block_box = BlockParameterTypeBox::new(
286
- box_id,
287
- recv_vtx,
288
- method_name.clone(),
289
- block_param_vtxs,
290
- );
291
- self.genv.register_box(box_id, Box::new(block_box));
292
- }
293
-
294
- Some(finish_method_call(
295
- self.genv,
296
- recv_vtx,
297
- method_name,
298
- location,
299
- ))
300
- }
301
- }
302
- }
303
-
304
- /// Install class definition
305
- fn install_class_node(&mut self, class_node: &ruby_prism::ClassNode) -> Option<VertexId> {
306
- let class_name = extract_class_name(class_node);
307
- install_class(self.genv, class_name);
308
-
309
- if let Some(body) = class_node.body() {
310
- if let Some(statements) = body.as_statements_node() {
311
- self.install_statements(&statements);
312
- }
313
- }
314
-
315
- exit_scope(self.genv);
316
- None
317
- }
318
-
319
- /// Install module definition
320
- fn install_module_node(&mut self, module_node: &ruby_prism::ModuleNode) -> Option<VertexId> {
321
- let module_name = extract_module_name(module_node);
322
- install_module(self.genv, module_name);
323
-
324
- if let Some(body) = module_node.body() {
325
- if let Some(statements) = body.as_statements_node() {
326
- self.install_statements(&statements);
327
- }
328
- }
329
-
330
- exit_scope(self.genv);
331
- None
332
- }
333
-
334
- /// Install method definition
335
- fn install_def_node(&mut self, def_node: &ruby_prism::DefNode) -> Option<VertexId> {
336
- let method_name = String::from_utf8_lossy(def_node.name().as_slice()).to_string();
337
- install_method(self.genv, method_name);
338
-
339
- // Process parameters BEFORE processing body
340
- // This ensures parameters are available as local variables in the method body
341
- if let Some(params_node) = def_node.parameters() {
342
- self.install_parameters(&params_node);
343
- }
344
-
345
- if let Some(body) = def_node.body() {
346
- if let Some(statements) = body.as_statements_node() {
347
- self.install_statements(&statements);
348
- }
349
- }
350
-
351
- exit_scope(self.genv);
352
- None
353
- }
354
-
355
- /// Install block node
356
- ///
357
- /// Processes blocks like `{ |x| x.to_s }` or `do |item| item.upcase end`
358
- fn install_block_node(&mut self, block_node: &ruby_prism::BlockNode) -> Option<VertexId> {
359
- // Use the version that collects param vtxs, but discard them
360
- self.install_block_node_with_params(block_node);
361
- None
362
- }
363
-
364
- /// Install block node and return block parameter vertex IDs
365
- ///
366
- /// This is used when processing method calls with blocks to collect
367
- /// the block parameter vertices for type inference via BlockParameterTypeBox.
368
- fn install_block_node_with_params(
369
- &mut self,
370
- block_node: &ruby_prism::BlockNode,
371
- ) -> Vec<VertexId> {
372
- // Enter block scope
373
- enter_block_scope(self.genv);
374
-
375
- let mut param_vtxs = Vec::new();
376
-
377
- // Process block parameters BEFORE processing body
378
- // block_node.parameters() returns Option<Node>, need to convert to BlockParametersNode
379
- if let Some(params_node) = block_node.parameters() {
380
- if let Some(block_params) = params_node.as_block_parameters_node() {
381
- param_vtxs = self.install_block_parameters_with_vtxs(&block_params);
382
- }
383
- }
384
-
385
- // Process block body
386
- if let Some(body) = block_node.body() {
387
- if let Some(statements) = body.as_statements_node() {
388
- self.install_statements(&statements);
389
- } else {
390
- // Single expression body
391
- self.install_node(&body);
392
- }
393
- }
394
-
395
- // Exit block scope
396
- exit_block_scope(self.genv);
397
-
398
- param_vtxs
399
- }
400
-
401
- /// Install block parameters as local variables
402
- ///
403
- /// Block parameters like `|x, y|` are registered as local variables
404
- /// with Bot (untyped) type.
405
- #[allow(dead_code)]
406
- fn install_block_parameters(&mut self, block_params: &ruby_prism::BlockParametersNode) {
407
- // Just call the version that returns vtxs and discard the result
408
- self.install_block_parameters_with_vtxs(block_params);
409
- }
410
-
411
- /// Install block parameters and return their vertex IDs
412
- ///
413
- /// This version is used when we need to track the block parameter vertices
414
- /// for type inference from the method's RBS block signature.
415
- fn install_block_parameters_with_vtxs(
416
- &mut self,
417
- block_params: &ruby_prism::BlockParametersNode,
418
- ) -> Vec<VertexId> {
419
- let mut vtxs = Vec::new();
420
-
421
- // BlockParametersNode contains a ParametersNode
422
- if let Some(params) = block_params.parameters() {
423
- // Process required parameters (most common in blocks)
424
- for node in params.requireds().iter() {
425
- if let Some(req_param) = node.as_required_parameter_node() {
426
- let name = String::from_utf8_lossy(req_param.name().as_slice()).to_string();
427
- let vtx = install_block_parameter(self.genv, self.lenv, name);
428
- vtxs.push(vtx);
429
- }
430
- }
431
-
432
- // Optional parameters in blocks: { |x = 1| ... }
433
- for node in params.optionals().iter() {
434
- if let Some(opt_param) = node.as_optional_parameter_node() {
435
- let name = String::from_utf8_lossy(opt_param.name().as_slice()).to_string();
436
- let default_value = opt_param.value();
437
-
438
- if let Some(default_vtx) = self.install_node(&default_value) {
439
- let vtx = install_optional_parameter(
440
- self.genv,
441
- self.lenv,
442
- &mut self.changes,
443
- name,
444
- default_vtx,
445
- );
446
- vtxs.push(vtx);
447
- } else {
448
- let vtx = install_block_parameter(self.genv, self.lenv, name);
449
- vtxs.push(vtx);
450
- }
451
- }
452
- }
453
-
454
- // Rest parameter in blocks: { |*args| ... }
455
- if let Some(rest_node) = params.rest() {
456
- if let Some(rest_param) = rest_node.as_rest_parameter_node() {
457
- if let Some(name_id) = rest_param.name() {
458
- let name = String::from_utf8_lossy(name_id.as_slice()).to_string();
459
- let vtx = install_rest_parameter(self.genv, self.lenv, name);
460
- vtxs.push(vtx);
461
- }
462
- }
463
- }
464
- }
465
-
466
- vtxs
467
- }
468
-
469
- /// Install method parameters as local variables
470
- fn install_parameters(&mut self, params_node: &ruby_prism::ParametersNode) {
471
- // Required parameters: def foo(a, b)
472
- for node in params_node.requireds().iter() {
473
- if let Some(req_param) = node.as_required_parameter_node() {
474
- let name = String::from_utf8_lossy(req_param.name().as_slice()).to_string();
475
- install_required_parameter(self.genv, self.lenv, name);
476
- }
477
- }
478
-
479
- // Optional parameters: def foo(a = 1, b = "hello")
480
- for node in params_node.optionals().iter() {
481
- if let Some(opt_param) = node.as_optional_parameter_node() {
482
- let name = String::from_utf8_lossy(opt_param.name().as_slice()).to_string();
483
- let default_value = opt_param.value();
484
-
485
- // Process default value to get its type
486
- if let Some(default_vtx) = self.install_node(&default_value) {
487
- install_optional_parameter(
488
- self.genv,
489
- self.lenv,
490
- &mut self.changes,
491
- name,
492
- default_vtx,
493
- );
494
- } else {
495
- // Fallback to untyped if default can't be processed
496
- install_required_parameter(self.genv, self.lenv, name);
497
- }
498
- }
499
- }
500
-
501
- // Rest parameter: def foo(*args)
502
- if let Some(rest_node) = params_node.rest() {
503
- if let Some(rest_param) = rest_node.as_rest_parameter_node() {
504
- if let Some(name_id) = rest_param.name() {
505
- let name = String::from_utf8_lossy(name_id.as_slice()).to_string();
506
- install_rest_parameter(self.genv, self.lenv, name);
507
- }
508
- }
509
- }
510
-
511
- // Keyword rest parameter: def foo(**kwargs)
512
- if let Some(kwrest_node) = params_node.keyword_rest() {
513
- if let Some(kwrest_param) = kwrest_node.as_keyword_rest_parameter_node() {
514
- if let Some(name_id) = kwrest_param.name() {
515
- let name = String::from_utf8_lossy(name_id.as_slice()).to_string();
516
- install_keyword_rest_parameter(self.genv, self.lenv, name);
517
- }
518
- }
519
- }
36
+ install_node(self.genv, self.lenv, &mut self.changes, self.source, node)
520
37
  }
521
38
 
522
- /// Process multiple statements
523
- fn install_statements(&mut self, statements: &ruby_prism::StatementsNode) {
524
- for stmt in &statements.body() {
525
- self.install_node(&stmt);
526
- }
527
- }
528
-
529
- /// Finish installation (apply changes and execute Boxes)
530
39
  pub fn finish(self) {
531
40
  self.genv.apply_changes(self.changes);
532
41
  self.genv.run_all();
533
42
  }
534
43
  }
535
44
 
536
- #[cfg(test)]
537
- mod tests {
538
- use super::*;
539
- use crate::parser::ParseSession;
540
- use crate::types::Type;
541
-
542
- /// Helper to run full analysis pipeline on Ruby source code
543
- fn analyze(source: &str) -> (GlobalEnv, LocalEnv) {
544
- let session = ParseSession::new();
545
- let parse_result = session.parse_source(source, "test.rb").unwrap();
546
-
547
- let mut genv = GlobalEnv::new();
548
-
549
- genv.register_builtin_method(Type::string(), "upcase", Type::string());
550
- genv.register_builtin_method(Type::string(), "downcase", Type::string());
551
-
552
- genv.register_builtin_method(Type::float(), "to_s", Type::string());
553
- genv.register_builtin_method(Type::float(), "to_i", Type::integer());
554
- genv.register_builtin_method(Type::float(), "round", Type::integer());
555
- genv.register_builtin_method(Type::float(), "ceil", Type::integer());
556
- genv.register_builtin_method(Type::float(), "floor", Type::integer());
557
- genv.register_builtin_method(Type::float(), "abs", Type::float());
558
-
559
- genv.register_builtin_method(Type::array(), "each", Type::array());
560
- genv.register_builtin_method(Type::array(), "map", Type::array());
561
- genv.register_builtin_method(Type::hash(), "each", Type::hash());
562
-
563
- genv.register_builtin_method(Type::regexp(), "match", Type::instance("MatchData"));
564
- genv.register_builtin_method(Type::regexp(), "match?", Type::instance("TrueClass"));
565
- genv.register_builtin_method(Type::regexp(), "source", Type::string());
566
-
567
- genv.register_builtin_method(Type::range(), "to_a", Type::array());
568
- genv.register_builtin_method(Type::range(), "size", Type::integer());
569
- genv.register_builtin_method(Type::range(), "count", Type::integer());
570
- genv.register_builtin_method(Type::range(), "first", Type::Bot);
571
- genv.register_builtin_method(Type::range(), "last", Type::Bot);
572
- genv.register_builtin_method(Type::range(), "include?", Type::instance("TrueClass"));
573
- genv.register_builtin_method(Type::range(), "cover?", Type::instance("TrueClass"));
574
-
575
- let mut lenv = LocalEnv::new();
576
- let mut installer = AstInstaller::new(&mut genv, &mut lenv, source);
577
-
578
- let root = parse_result.node();
579
- if let Some(program_node) = root.as_program_node() {
580
- let statements = program_node.statements();
581
- for stmt in &statements.body() {
582
- installer.install_node(&stmt);
583
- }
584
- }
585
-
586
- installer.finish();
587
- (genv, lenv)
588
- }
589
-
590
- #[test]
591
- fn test_install_literal() {
592
- let source = r#"x = "hello""#;
593
- let session = ParseSession::new();
594
- let parse_result = session.parse_source(source, "test.rb").unwrap();
595
-
596
- let mut genv = GlobalEnv::new();
597
- let mut lenv = LocalEnv::new();
598
- let mut installer = AstInstaller::new(&mut genv, &mut lenv, source);
599
-
600
- let root = parse_result.node();
601
- if let Some(program_node) = root.as_program_node() {
602
- let statements = program_node.statements();
603
- for stmt in &statements.body() {
604
- installer.install_node(&stmt);
605
- }
606
- }
607
-
608
- installer.finish();
609
-
610
- let x_vtx = lenv.get_var("x").unwrap();
611
- assert_eq!(genv.get_vertex(x_vtx).unwrap().show(), "String");
612
- }
613
-
614
- #[test]
615
- fn test_install_multiple_vars() {
616
- let source = r#"
617
- x = "hello"
618
- y = 42
619
- "#;
620
- let session = ParseSession::new();
621
- let parse_result = session.parse_source(source, "test.rb").unwrap();
622
-
623
- let mut genv = GlobalEnv::new();
624
- let mut lenv = LocalEnv::new();
625
- let mut installer = AstInstaller::new(&mut genv, &mut lenv, source);
626
-
627
- let root = parse_result.node();
628
- if let Some(program_node) = root.as_program_node() {
629
- let statements = program_node.statements();
630
- for stmt in &statements.body() {
631
- installer.install_node(&stmt);
632
- }
633
- }
634
-
635
- installer.finish();
636
-
637
- let x_vtx = lenv.get_var("x").unwrap();
638
- let y_vtx = lenv.get_var("y").unwrap();
639
-
640
- assert_eq!(genv.get_vertex(x_vtx).unwrap().show(), "String");
641
- assert_eq!(genv.get_vertex(y_vtx).unwrap().show(), "Integer");
642
- }
643
-
644
- #[test]
645
- fn test_install_method_call() {
646
- let source = r#"
647
- x = "hello"
648
- y = x.upcase
649
- "#;
650
- let session = ParseSession::new();
651
- let parse_result = session.parse_source(source, "test.rb").unwrap();
652
-
653
- let mut genv = GlobalEnv::new();
654
- genv.register_builtin_method(Type::string(), "upcase", Type::string());
655
-
656
- let mut lenv = LocalEnv::new();
657
- let mut installer = AstInstaller::new(&mut genv, &mut lenv, source);
658
-
659
- let root = parse_result.node();
660
- if let Some(program_node) = root.as_program_node() {
661
- let statements = program_node.statements();
662
- for stmt in &statements.body() {
663
- installer.install_node(&stmt);
664
- }
665
- }
666
-
667
- installer.finish();
668
-
669
- let x_vtx = lenv.get_var("x").unwrap();
670
- let y_vtx = lenv.get_var("y").unwrap();
671
-
672
- assert_eq!(genv.get_vertex(x_vtx).unwrap().show(), "String");
673
- assert_eq!(genv.get_vertex(y_vtx).unwrap().show(), "String");
674
- }
675
-
676
- #[test]
677
- fn test_install_module_with_method() {
678
- let source = r#"
679
- module Utils
680
- def helper
681
- x = "test"
682
- end
683
- end
684
- "#;
685
- let session = ParseSession::new();
686
- let parse_result = session.parse_source(source, "test.rb").unwrap();
687
-
688
- let mut genv = GlobalEnv::new();
689
- let mut lenv = LocalEnv::new();
690
- let mut installer = AstInstaller::new(&mut genv, &mut lenv, source);
691
-
692
- let root = parse_result.node();
693
- if let Some(program_node) = root.as_program_node() {
694
- let statements = program_node.statements();
695
- for stmt in &statements.body() {
696
- installer.install_node(&stmt);
697
- }
698
- }
699
-
700
- installer.finish();
701
-
702
- // After processing, we should be back at top-level scope
703
- assert_eq!(genv.scope_manager.current_module_name(), None);
704
- assert_eq!(genv.scope_manager.current_class_name(), None);
705
- }
706
-
707
- #[test]
708
- fn test_install_nested_module_class() {
709
- let source = r#"
710
- module Api
711
- class User
712
- def greet
713
- name = "hello"
714
- end
715
- end
716
- end
717
- "#;
718
- let session = ParseSession::new();
719
- let parse_result = session.parse_source(source, "test.rb").unwrap();
720
-
721
- let mut genv = GlobalEnv::new();
722
- let mut lenv = LocalEnv::new();
723
- let mut installer = AstInstaller::new(&mut genv, &mut lenv, source);
724
-
725
- let root = parse_result.node();
726
- if let Some(program_node) = root.as_program_node() {
727
- let statements = program_node.statements();
728
- for stmt in &statements.body() {
729
- installer.install_node(&stmt);
730
- }
731
- }
732
-
733
- installer.finish();
734
-
735
- // After processing, we should be back at top-level scope
736
- assert_eq!(genv.scope_manager.current_module_name(), None);
737
- assert_eq!(genv.scope_manager.current_class_name(), None);
738
- }
739
-
740
- // ============================================
741
- // Float Type Inference Tests
742
- // ============================================
743
-
744
- #[test]
745
- fn test_float_literal_basic() {
746
- let (genv, lenv) = analyze(r#"x = 3.14"#);
747
- let x_vtx = lenv.get_var("x").unwrap();
748
- assert_eq!(genv.get_vertex(x_vtx).unwrap().show(), "Float");
749
- }
750
-
751
- #[test]
752
- fn test_float_ceil() {
753
- let (genv, lenv) = analyze("x = 3.14\na = x.ceil");
754
- let a_vtx = lenv.get_var("a").unwrap();
755
- assert_eq!(genv.get_vertex(a_vtx).unwrap().show(), "Integer");
45
+ /// Install node (returns Vertex ID)
46
+ pub(crate) fn install_node(
47
+ genv: &mut GlobalEnv,
48
+ lenv: &mut LocalEnv,
49
+ changes: &mut ChangeSet,
50
+ source: &str,
51
+ node: &Node,
52
+ ) -> Option<VertexId> {
53
+ if let Some(class_node) = node.as_class_node() {
54
+ return process_class_node(genv, lenv, changes, source, &class_node);
756
55
  }
757
56
 
758
- #[test]
759
- fn test_float_floor() {
760
- let (genv, lenv) = analyze("x = 3.14\nb = x.floor");
761
- let b_vtx = lenv.get_var("b").unwrap();
762
- assert_eq!(genv.get_vertex(b_vtx).unwrap().show(), "Integer");
57
+ if let Some(module_node) = node.as_module_node() {
58
+ return process_module_node(genv, lenv, changes, source, &module_node);
763
59
  }
764
60
 
765
- #[test]
766
- fn test_float_abs() {
767
- let (genv, lenv) = analyze("x = 3.14\nc = x.abs");
768
- let c_vtx = lenv.get_var("c").unwrap();
769
- assert_eq!(genv.get_vertex(c_vtx).unwrap().show(), "Float");
61
+ if let Some(def_node) = node.as_def_node() {
62
+ return process_def_node(genv, lenv, changes, source, &def_node);
770
63
  }
771
64
 
772
- // ============================================
773
- // Regexp Type Inference Tests
774
- // ============================================
775
-
776
- #[test]
777
- fn test_regexp_literal_basic() {
778
- let (genv, lenv) = analyze(r#"x = /hello/"#);
779
- let x_vtx = lenv.get_var("x").unwrap();
780
- assert_eq!(genv.get_vertex(x_vtx).unwrap().show(), "Regexp");
781
- }
782
-
783
- #[test]
784
- fn test_regexp_source() {
785
- let (genv, lenv) = analyze("x = /hello/\na = x.source");
786
- let a_vtx = lenv.get_var("a").unwrap();
787
- assert_eq!(genv.get_vertex(a_vtx).unwrap().show(), "String");
788
- }
789
-
790
- // ============================================
791
- // Range Type Inference Tests
792
- // ============================================
793
-
794
- #[test]
795
- fn test_range_integer() {
796
- let (genv, lenv) = analyze(r#"x = 1..5"#);
797
- let x_vtx = lenv.get_var("x").unwrap();
798
- assert_eq!(genv.get_vertex(x_vtx).unwrap().show(), "Range[Integer]");
65
+ if let Some(block_node) = node.as_block_node() {
66
+ return process_block_node(genv, lenv, changes, source, &block_node);
799
67
  }
800
68
 
801
- #[test]
802
- fn test_range_exclusive() {
803
- let (genv, lenv) = analyze(r#"x = 1...5"#);
804
- let x_vtx = lenv.get_var("x").unwrap();
805
- assert_eq!(genv.get_vertex(x_vtx).unwrap().show(), "Range[Integer]");
69
+ if let Some(if_node) = node.as_if_node() {
70
+ return process_if_node(genv, lenv, changes, source, &if_node);
806
71
  }
807
-
808
- #[test]
809
- fn test_range_string() {
810
- let (genv, lenv) = analyze(r#"x = "a".."z""#);
811
- let x_vtx = lenv.get_var("x").unwrap();
812
- assert_eq!(genv.get_vertex(x_vtx).unwrap().show(), "Range[String]");
813
- }
814
-
815
- #[test]
816
- fn test_range_float() {
817
- let (genv, lenv) = analyze(r#"x = 1.0..5.0"#);
818
- let x_vtx = lenv.get_var("x").unwrap();
819
- assert_eq!(genv.get_vertex(x_vtx).unwrap().show(), "Range[Float]");
72
+ if let Some(unless_node) = node.as_unless_node() {
73
+ return process_unless_node(genv, lenv, changes, source, &unless_node);
820
74
  }
821
-
822
- #[test]
823
- fn test_range_to_a() {
824
- let (genv, lenv) = analyze("x = 1..10\na = x.to_a");
825
- let a_vtx = lenv.get_var("a").unwrap();
826
- assert_eq!(genv.get_vertex(a_vtx).unwrap().show(), "Array");
75
+ if let Some(case_node) = node.as_case_node() {
76
+ return process_case_node(genv, lenv, changes, source, &case_node);
827
77
  }
828
78
 
829
- #[test]
830
- fn test_range_size() {
831
- let (genv, lenv) = analyze("x = 1..10\nb = x.size");
832
- let b_vtx = lenv.get_var("b").unwrap();
833
- assert_eq!(genv.get_vertex(b_vtx).unwrap().show(), "Integer");
79
+ match dispatch_simple(genv, lenv, node) {
80
+ DispatchResult::Vertex(vtx) => return Some(vtx),
81
+ DispatchResult::NotHandled => {}
834
82
  }
835
83
 
836
- // ============================================
837
- // Nested Array Type Inference Tests
838
- // ============================================
839
-
840
- #[test]
841
- fn test_nested_array_integer() {
842
- let (genv, lenv) = analyze(r#"x = [[1, 2], [3]]"#);
843
- let x_vtx = lenv.get_var("x").unwrap();
844
- assert_eq!(
845
- genv.get_vertex(x_vtx).unwrap().show(),
846
- "Array[Array[Integer]]"
847
- );
84
+ if let Some(vtx) = install_literal_node(genv, lenv, changes, source, node) {
85
+ return Some(vtx);
848
86
  }
849
87
 
850
- #[test]
851
- fn test_deeply_nested_array() {
852
- let (genv, lenv) = analyze(r#"x = [[[1]]]"#);
853
- let x_vtx = lenv.get_var("x").unwrap();
854
- assert_eq!(
855
- genv.get_vertex(x_vtx).unwrap().show(),
856
- "Array[Array[Array[Integer]]]"
857
- );
88
+ if let Some(kind) = dispatch_needs_child(node, source) {
89
+ return process_needs_child(genv, lenv, changes, source, kind);
858
90
  }
859
91
 
860
- #[test]
861
- fn test_nested_array_mixed() {
862
- let (genv, lenv) = analyze(r#"x = [[1], ["a"]]"#);
863
- let x_vtx = lenv.get_var("x").unwrap();
864
- let result = genv.get_vertex(x_vtx).unwrap().show();
865
- assert!(
866
- result == "Array[Array[Integer] | Array[String]]"
867
- || result == "Array[Array[String] | Array[Integer]]",
868
- "Expected nested array with union, got: {}",
869
- result
870
- );
871
- }
872
-
873
- // ============================================
874
- // Hash Type Inference Tests
875
- // ============================================
876
-
877
- #[test]
878
- fn test_hash_symbol_integer() {
879
- let (genv, lenv) = analyze(r#"x = { a: 1, b: 2 }"#);
880
- let x_vtx = lenv.get_var("x").unwrap();
881
- assert_eq!(
882
- genv.get_vertex(x_vtx).unwrap().show(),
883
- "Hash[Symbol, Integer]"
884
- );
885
- }
886
-
887
- #[test]
888
- fn test_hash_string_string() {
889
- let (genv, lenv) = analyze(r#"x = { "k" => "v" }"#);
890
- let x_vtx = lenv.get_var("x").unwrap();
891
- assert_eq!(
892
- genv.get_vertex(x_vtx).unwrap().show(),
893
- "Hash[String, String]"
894
- );
895
- }
896
-
897
- #[test]
898
- fn test_hash_mixed_values() {
899
- let (genv, lenv) = analyze(r#"x = { a: 1, b: "x" }"#);
900
- let x_vtx = lenv.get_var("x").unwrap();
901
- let result = genv.get_vertex(x_vtx).unwrap().show();
902
- assert!(
903
- result == "Hash[Symbol, Integer | String]"
904
- || result == "Hash[Symbol, String | Integer]",
905
- "Expected Hash with union value type, got: {}",
906
- result
907
- );
908
- }
909
-
910
- #[test]
911
- fn test_hash_empty() {
912
- let (genv, lenv) = analyze(r#"x = {}"#);
913
- let x_vtx = lenv.get_var("x").unwrap();
914
- assert_eq!(genv.get_vertex(x_vtx).unwrap().show(), "Hash");
915
- }
92
+ None
93
+ }
916
94
 
917
- #[test]
918
- fn test_hash_nested() {
919
- let (genv, lenv) = analyze(r#"x = { a: [1] }"#);
920
- let x_vtx = lenv.get_var("x").unwrap();
921
- assert_eq!(
922
- genv.get_vertex(x_vtx).unwrap().show(),
923
- "Hash[Symbol, Array[Integer]]"
924
- );
925
- }
95
+ /// Process multiple statements (returns last expression's VertexId)
96
+ pub(crate) fn install_statements(
97
+ genv: &mut GlobalEnv,
98
+ lenv: &mut LocalEnv,
99
+ changes: &mut ChangeSet,
100
+ source: &str,
101
+ statements: &ruby_prism::StatementsNode,
102
+ ) -> Option<VertexId> {
103
+ let mut last_vtx = None;
104
+ for stmt in &statements.body() {
105
+ last_vtx = install_node(genv, lenv, changes, source, &stmt);
106
+ }
107
+ last_vtx
926
108
  }