method-ray 0.1.9 → 0.1.10

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.
@@ -54,6 +54,8 @@ pub struct MethodCallBox {
54
54
  arg_vtxs: Vec<VertexId>,
55
55
  kwarg_vtxs: Option<HashMap<String, VertexId>>,
56
56
  location: Option<SourceLocation>, // Source code location
57
+ /// Whether this is a safe navigation call (`&.`)
58
+ safe_navigation: bool,
57
59
  /// Number of times this box has been rescheduled
58
60
  reschedule_count: u8,
59
61
  }
@@ -62,6 +64,7 @@ pub struct MethodCallBox {
62
64
  const MAX_RESCHEDULE_COUNT: u8 = 3;
63
65
 
64
66
  impl MethodCallBox {
67
+ #[allow(clippy::too_many_arguments)]
65
68
  pub fn new(
66
69
  id: BoxId,
67
70
  recv: VertexId,
@@ -70,6 +73,7 @@ impl MethodCallBox {
70
73
  arg_vtxs: Vec<VertexId>,
71
74
  kwarg_vtxs: Option<HashMap<String, VertexId>>,
72
75
  location: Option<SourceLocation>,
76
+ safe_navigation: bool,
73
77
  ) -> Self {
74
78
  Self {
75
79
  id,
@@ -79,6 +83,7 @@ impl MethodCallBox {
79
83
  arg_vtxs,
80
84
  kwarg_vtxs,
81
85
  location,
86
+ safe_navigation,
82
87
  reschedule_count: 0,
83
88
  }
84
89
  }
@@ -100,6 +105,13 @@ impl MethodCallBox {
100
105
  genv: &mut GlobalEnv,
101
106
  changes: &mut ChangeSet,
102
107
  ) {
108
+ // Safe navigation (`&.`): skip nil receiver entirely.
109
+ // Ruby's &. short-circuits: no method resolution, no argument evaluation, no error.
110
+ // The nil return type is added in run() after processing all receiver types.
111
+ if self.safe_navigation && matches!(recv_ty, Type::Nil) {
112
+ return;
113
+ }
114
+
103
115
  if let Some(method_info) = genv.resolve_method(recv_ty, &self.method_name) {
104
116
  if let Some(return_vtx) = method_info.return_vertex {
105
117
  // User-defined method: connect body's return vertex to call site
@@ -186,8 +198,14 @@ impl BoxTrait for MethodCallBox {
186
198
  return;
187
199
  }
188
200
 
189
- for recv_ty in recv_types {
190
- self.process_recv_type(&recv_ty, genv, changes);
201
+ for recv_ty in &recv_types {
202
+ self.process_recv_type(recv_ty, genv, changes);
203
+ }
204
+
205
+ // Safe navigation (`&.`): if receiver can be nil, return type includes nil
206
+ if self.safe_navigation && recv_types.iter().any(|t| matches!(t, Type::Nil)) {
207
+ let nil_src = genv.new_source(Type::Nil);
208
+ changes.add_edge(nil_src, self.ret);
191
209
  }
192
210
  }
193
211
  }
@@ -327,440 +345,3 @@ impl BoxTrait for BlockParameterTypeBox {
327
345
  }
328
346
  }
329
347
  }
330
-
331
- #[cfg(test)]
332
- mod tests {
333
- use super::*;
334
- use crate::env::GlobalEnv;
335
- use crate::types::Type;
336
-
337
- #[test]
338
- fn test_method_call_box() {
339
- let mut genv = GlobalEnv::new();
340
-
341
- // Register String#upcase
342
- genv.register_builtin_method(Type::string(), "upcase", Type::string());
343
-
344
- // x = "hello" (Source<String> -> Vertex)
345
- let x_vtx = genv.new_vertex();
346
- let str_src = genv.new_source(Type::string());
347
- genv.add_edge(str_src, x_vtx);
348
-
349
- // x.upcase
350
- let ret_vtx = genv.new_vertex();
351
- let box_id = BoxId(0);
352
- let mut call_box = MethodCallBox::new(
353
- box_id,
354
- x_vtx,
355
- "upcase".to_string(),
356
- ret_vtx,
357
- vec![],
358
- None,
359
- None, // No location in test
360
- );
361
-
362
- // Execute Box
363
- let mut changes = ChangeSet::new();
364
- call_box.run(&mut genv, &mut changes);
365
- genv.apply_changes(changes);
366
-
367
- // Check return type
368
- let ret_vertex = genv.get_vertex(ret_vtx).unwrap();
369
- assert_eq!(ret_vertex.show(), "String");
370
- }
371
-
372
- #[test]
373
- fn test_method_call_box_undefined() {
374
- let mut genv = GlobalEnv::new();
375
-
376
- // Don't register method
377
-
378
- let x_vtx = genv.new_vertex();
379
- let str_src = genv.new_source(Type::string());
380
- genv.add_edge(str_src, x_vtx);
381
-
382
- // x.unknown_method (undefined)
383
- let ret_vtx = genv.new_vertex();
384
- let box_id = BoxId(0);
385
- let mut call_box = MethodCallBox::new(
386
- box_id,
387
- x_vtx,
388
- "unknown_method".to_string(),
389
- ret_vtx,
390
- vec![],
391
- None,
392
- None, // No location in test
393
- );
394
-
395
- let mut changes = ChangeSet::new();
396
- call_box.run(&mut genv, &mut changes);
397
- genv.apply_changes(changes);
398
-
399
- // Return value is untyped
400
- let ret_vertex = genv.get_vertex(ret_vtx).unwrap();
401
- assert_eq!(ret_vertex.show(), "untyped");
402
- }
403
-
404
- #[test]
405
- fn test_block_param_type_box_simple() {
406
- let mut genv = GlobalEnv::new();
407
-
408
- // Register String#each_char with block param type String
409
- genv.register_builtin_method_with_block(
410
- Type::string(),
411
- "each_char",
412
- Type::string(),
413
- Some(vec![Type::string()]),
414
- );
415
-
416
- // Create receiver vertex with String type
417
- let recv_vtx = genv.new_vertex();
418
- let str_src = genv.new_source(Type::string());
419
- genv.add_edge(str_src, recv_vtx);
420
-
421
- // Create block parameter vertex
422
- let param_vtx = genv.new_vertex();
423
-
424
- // Create and run BlockParameterTypeBox
425
- let box_id = genv.alloc_box_id();
426
- let block_box = BlockParameterTypeBox::new(
427
- box_id,
428
- recv_vtx,
429
- "each_char".to_string(),
430
- vec![param_vtx],
431
- );
432
- genv.register_box(box_id, Box::new(block_box));
433
-
434
- // Run all boxes
435
- genv.run_all();
436
-
437
- // Block parameter should now have String type
438
- assert_eq!(genv.get_vertex(param_vtx).unwrap().show(), "String");
439
- }
440
-
441
- #[test]
442
- fn test_block_param_type_variable_skipped() {
443
- let mut genv = GlobalEnv::new();
444
-
445
- // Register Array#each with block param type Elem (type variable)
446
- genv.register_builtin_method_with_block(
447
- Type::array(),
448
- "each",
449
- Type::array(),
450
- Some(vec![Type::instance("Elem")]),
451
- );
452
-
453
- let recv_vtx = genv.new_vertex();
454
- let arr_src = genv.new_source(Type::array());
455
- genv.add_edge(arr_src, recv_vtx);
456
-
457
- let param_vtx = genv.new_vertex();
458
-
459
- let box_id = genv.alloc_box_id();
460
- let block_box = BlockParameterTypeBox::new(
461
- box_id,
462
- recv_vtx,
463
- "each".to_string(),
464
- vec![param_vtx],
465
- );
466
- genv.register_box(box_id, Box::new(block_box));
467
-
468
- genv.run_all();
469
-
470
- // Block parameter should remain untyped (type variable skipped)
471
- assert_eq!(genv.get_vertex(param_vtx).unwrap().show(), "untyped");
472
- }
473
-
474
- #[test]
475
- fn test_block_param_multiple_params() {
476
- let mut genv = GlobalEnv::new();
477
-
478
- // Register a method with multiple block params
479
- genv.register_builtin_method_with_block(
480
- Type::string(),
481
- "each_with_index",
482
- Type::string(),
483
- Some(vec![Type::string(), Type::integer()]),
484
- );
485
-
486
- let recv_vtx = genv.new_vertex();
487
- let str_src = genv.new_source(Type::string());
488
- genv.add_edge(str_src, recv_vtx);
489
-
490
- let param1_vtx = genv.new_vertex();
491
- let param2_vtx = genv.new_vertex();
492
-
493
- let box_id = genv.alloc_box_id();
494
- let block_box = BlockParameterTypeBox::new(
495
- box_id,
496
- recv_vtx,
497
- "each_with_index".to_string(),
498
- vec![param1_vtx, param2_vtx],
499
- );
500
- genv.register_box(box_id, Box::new(block_box));
501
-
502
- genv.run_all();
503
-
504
- // Both params should have their types
505
- assert_eq!(genv.get_vertex(param1_vtx).unwrap().show(), "String");
506
- assert_eq!(genv.get_vertex(param2_vtx).unwrap().show(), "Integer");
507
- }
508
-
509
- #[test]
510
- fn test_block_param_type_variable_resolved() {
511
- let mut genv = GlobalEnv::new();
512
-
513
- // Register Array#each with block param type Elem (type variable)
514
- genv.register_builtin_method_with_block(
515
- Type::array(),
516
- "each",
517
- Type::array(),
518
- Some(vec![Type::instance("Elem")]),
519
- );
520
-
521
- // Create receiver vertex with Array[Integer] type
522
- let recv_vtx = genv.new_vertex();
523
- let arr_src = genv.new_source(Type::array_of(Type::integer()));
524
- genv.add_edge(arr_src, recv_vtx);
525
-
526
- let param_vtx = genv.new_vertex();
527
-
528
- let box_id = genv.alloc_box_id();
529
- let block_box = BlockParameterTypeBox::new(
530
- box_id,
531
- recv_vtx,
532
- "each".to_string(),
533
- vec![param_vtx],
534
- );
535
- genv.register_box(box_id, Box::new(block_box));
536
-
537
- genv.run_all();
538
-
539
- // Block parameter should be Integer (resolved from Array[Integer])
540
- assert_eq!(genv.get_vertex(param_vtx).unwrap().show(), "Integer");
541
- }
542
-
543
- #[test]
544
- fn test_hash_type_variable_resolved() {
545
- let mut genv = GlobalEnv::new();
546
-
547
- // Register Hash#each with block param types K, V
548
- genv.register_builtin_method_with_block(
549
- Type::hash(),
550
- "each",
551
- Type::hash(),
552
- Some(vec![Type::instance("K"), Type::instance("V")]),
553
- );
554
-
555
- // Create receiver vertex with Hash[String, Integer] type
556
- let recv_vtx = genv.new_vertex();
557
- let hash_src = genv.new_source(Type::hash_of(Type::string(), Type::integer()));
558
- genv.add_edge(hash_src, recv_vtx);
559
-
560
- let key_vtx = genv.new_vertex();
561
- let value_vtx = genv.new_vertex();
562
-
563
- let box_id = genv.alloc_box_id();
564
- let block_box = BlockParameterTypeBox::new(
565
- box_id,
566
- recv_vtx,
567
- "each".to_string(),
568
- vec![key_vtx, value_vtx],
569
- );
570
- genv.register_box(box_id, Box::new(block_box));
571
-
572
- genv.run_all();
573
-
574
- // Block parameters should be resolved from Hash[String, Integer]
575
- assert_eq!(genv.get_vertex(key_vtx).unwrap().show(), "String");
576
- assert_eq!(genv.get_vertex(value_vtx).unwrap().show(), "Integer");
577
- }
578
-
579
- #[test]
580
- fn test_method_call_box_user_defined_method() {
581
- let mut genv = GlobalEnv::new();
582
-
583
- // Simulate: def name; "Alice"; end
584
- let body_src = genv.new_source(Type::string());
585
-
586
- // Register user-defined method User#name with return_vertex
587
- genv.register_user_method(Type::instance("User"), "name", body_src, vec![], None);
588
-
589
- // Simulate: user.name (receiver has type User)
590
- let recv_vtx = genv.new_vertex();
591
- let recv_src = genv.new_source(Type::instance("User"));
592
- genv.add_edge(recv_src, recv_vtx);
593
-
594
- let ret_vtx = genv.new_vertex();
595
- let box_id = genv.alloc_box_id();
596
- let call_box = MethodCallBox::new(
597
- box_id,
598
- recv_vtx,
599
- "name".to_string(),
600
- ret_vtx,
601
- vec![],
602
- None,
603
- None,
604
- );
605
- genv.register_box(box_id, Box::new(call_box));
606
-
607
- genv.run_all();
608
-
609
- // Return type should be String (propagated from body's last expression)
610
- assert_eq!(genv.get_vertex(ret_vtx).unwrap().show(), "String");
611
- }
612
-
613
- #[test]
614
- fn test_method_call_box_param_type_propagation() {
615
- let mut genv = GlobalEnv::new();
616
-
617
- // Simulate: def format(value); value.to_s; end
618
- // 1. Create parameter vertex for 'value'
619
- let param_vtx = genv.new_vertex();
620
-
621
- // 2. Register Integer#to_s -> String (builtin)
622
- genv.register_builtin_method(Type::integer(), "to_s", Type::string());
623
-
624
- // 3. Create MethodCallBox for value.to_s (inside method body)
625
- let inner_ret_vtx = genv.new_vertex();
626
- let inner_box_id = genv.alloc_box_id();
627
- let inner_call = MethodCallBox::new(
628
- inner_box_id,
629
- param_vtx,
630
- "to_s".to_string(),
631
- inner_ret_vtx,
632
- vec![],
633
- None,
634
- None,
635
- );
636
- genv.register_box(inner_box_id, Box::new(inner_call));
637
-
638
- // 4. Register user-defined method Formatter#format with return_vertex and param_vertices
639
- genv.register_user_method(
640
- Type::instance("Formatter"),
641
- "format",
642
- inner_ret_vtx,
643
- vec![param_vtx],
644
- None,
645
- );
646
-
647
- // 5. Simulate call: Formatter.new.format(42)
648
- let recv_vtx = genv.new_vertex();
649
- let recv_src = genv.new_source(Type::instance("Formatter"));
650
- genv.add_edge(recv_src, recv_vtx);
651
-
652
- let arg_vtx = genv.new_source(Type::integer()); // argument: 42
653
-
654
- let call_ret_vtx = genv.new_vertex();
655
- let call_box_id = genv.alloc_box_id();
656
- let call_box = MethodCallBox::new(
657
- call_box_id,
658
- recv_vtx,
659
- "format".to_string(),
660
- call_ret_vtx,
661
- vec![arg_vtx],
662
- None,
663
- None,
664
- );
665
- genv.register_box(call_box_id, Box::new(call_box));
666
-
667
- // Run all boxes
668
- genv.run_all();
669
-
670
- // param_vtx should have Integer type (propagated from argument)
671
- assert_eq!(genv.get_vertex(param_vtx).unwrap().show(), "Integer");
672
-
673
- // Return type should be String (Integer#to_s -> String)
674
- assert_eq!(genv.get_vertex(call_ret_vtx).unwrap().show(), "String");
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
- }
766
- }
@@ -76,68 +76,3 @@ pub enum EdgeUpdate {
76
76
  Add { src: VertexId, dst: VertexId },
77
77
  Remove { src: VertexId, dst: VertexId },
78
78
  }
79
-
80
- #[cfg(test)]
81
- mod tests {
82
- use super::*;
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
-
92
- #[test]
93
- fn test_change_set_add() {
94
- let mut cs = ChangeSet::new();
95
-
96
- cs.add_edge(VertexId(1), VertexId(2));
97
- cs.add_edge(VertexId(2), VertexId(3));
98
-
99
- let updates = cs.reinstall();
100
-
101
- assert_eq!(updates.len(), 2);
102
- assert!(updates.contains(&EdgeUpdate::Add {
103
- src: VertexId(1),
104
- dst: VertexId(2)
105
- }));
106
- assert!(updates.contains(&EdgeUpdate::Add {
107
- src: VertexId(2),
108
- dst: VertexId(3)
109
- }));
110
- }
111
-
112
- #[test]
113
- fn test_change_set_dedup() {
114
- let mut cs = ChangeSet::new();
115
-
116
- cs.add_edge(VertexId(1), VertexId(2));
117
- cs.add_edge(VertexId(1), VertexId(2)); // Duplicate
118
-
119
- let updates = cs.reinstall();
120
-
121
- assert_eq!(updates.len(), 1); // Duplicates removed
122
- }
123
-
124
- #[test]
125
- fn test_change_set_remove() {
126
- let mut cs = ChangeSet::new();
127
-
128
- // First commit
129
- cs.add_edge(VertexId(1), VertexId(2));
130
- cs.add_edge(VertexId(2), VertexId(3));
131
- cs.reinstall();
132
-
133
- // Second time: keep only (1,2)
134
- cs.add_edge(VertexId(1), VertexId(2));
135
- let updates = cs.reinstall();
136
-
137
- assert_eq!(updates.len(), 1);
138
- assert!(updates.contains(&EdgeUpdate::Remove {
139
- src: VertexId(2),
140
- dst: VertexId(3)
141
- }));
142
- }
143
- }
@@ -96,72 +96,3 @@ impl Default for Vertex {
96
96
  Self::new()
97
97
  }
98
98
  }
99
-
100
- #[cfg(test)]
101
- mod tests {
102
- use super::*;
103
-
104
- #[test]
105
- fn test_source_type() {
106
- let src = Source::new(Type::string());
107
- assert_eq!(src.ty.show(), "String");
108
- }
109
-
110
- #[test]
111
- fn test_vertex_empty() {
112
- let vtx = Vertex::new();
113
- assert_eq!(vtx.show(), "untyped");
114
- }
115
-
116
- #[test]
117
- fn test_vertex_single_type() {
118
- let mut vtx = Vertex::new();
119
-
120
- // Add String type
121
- let propagations = vtx.on_type_added(VertexId(1), vec![Type::string()]);
122
- assert_eq!(vtx.show(), "String");
123
- assert_eq!(propagations.len(), 0); // No propagation since no connections
124
- }
125
-
126
- #[test]
127
- fn test_vertex_union_type() {
128
- let mut vtx = Vertex::new();
129
-
130
- // Add String type
131
- vtx.on_type_added(VertexId(1), vec![Type::string()]);
132
- assert_eq!(vtx.show(), "String");
133
-
134
- // Add Integer type → becomes Union type
135
- vtx.on_type_added(VertexId(1), vec![Type::integer()]);
136
- assert_eq!(vtx.show(), "(Integer | String)");
137
- }
138
-
139
- #[test]
140
- fn test_vertex_propagation() {
141
- let mut vtx = Vertex::new();
142
-
143
- // Add connections
144
- vtx.add_next(VertexId(3));
145
- vtx.add_next(VertexId(4));
146
-
147
- // Add type → propagated to connections
148
- let propagations = vtx.on_type_added(VertexId(1), vec![Type::string()]);
149
-
150
- assert_eq!(propagations.len(), 2);
151
- assert!(propagations.contains(&(VertexId(3), vec![Type::string()])));
152
- assert!(propagations.contains(&(VertexId(4), vec![Type::string()])));
153
- }
154
-
155
- #[test]
156
- fn test_vertex_no_duplicate_propagation() {
157
- let mut vtx = Vertex::new();
158
- vtx.add_next(VertexId(3));
159
-
160
- // Add same type twice → only first time propagates
161
- let prop1 = vtx.on_type_added(VertexId(1), vec![Type::string()]);
162
- assert_eq!(prop1.len(), 1);
163
-
164
- let prop2 = vtx.on_type_added(VertexId(1), vec![Type::string()]);
165
- assert_eq!(prop2.len(), 0); // No propagation since already exists
166
- }
167
- }