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.
@@ -59,14 +59,14 @@ pub(crate) fn collect_arguments<'a>(
59
59
 
60
60
  /// Kind of attr_* declaration
61
61
  #[derive(Debug, Clone, Copy)]
62
- pub enum AttrKind {
62
+ pub(crate) enum AttrKind {
63
63
  Reader,
64
64
  Writer,
65
65
  Accessor,
66
66
  }
67
67
 
68
68
  /// Result of dispatching a simple node (no child processing needed)
69
- pub enum DispatchResult {
69
+ pub(crate) enum DispatchResult {
70
70
  /// Node produced a vertex
71
71
  Vertex(VertexId),
72
72
  /// Node was not handled
@@ -74,7 +74,7 @@ pub enum DispatchResult {
74
74
  }
75
75
 
76
76
  /// Kind of child processing needed
77
- pub enum NeedsChildKind<'a> {
77
+ pub(crate) enum NeedsChildKind<'a> {
78
78
  /// Instance variable write: need to process value, then call finish_ivar_write
79
79
  IvarWrite { ivar_name: String, value: Node<'a> },
80
80
  /// Local variable write: need to process value, then call finish_local_var_write
@@ -88,6 +88,8 @@ pub enum NeedsChildKind<'a> {
88
88
  block: Option<Node<'a>>,
89
89
  /// Arguments to the method call
90
90
  arguments: Vec<Node<'a>>,
91
+ /// Whether this is a safe navigation call (`&.`)
92
+ safe_navigation: bool,
91
93
  },
92
94
  /// Implicit self method call: method call without explicit receiver (implicit self)
93
95
  ImplicitSelfCall {
@@ -101,17 +103,25 @@ pub enum NeedsChildKind<'a> {
101
103
  kind: AttrKind,
102
104
  attr_names: Vec<String>,
103
105
  },
104
- /// include declaration: `include Greetable, Enumerable`
105
- IncludeDeclaration {
106
+ /// include / extend declaration: `include Greetable`, `extend ClassMethods`
107
+ ModuleMixinDeclaration {
106
108
  module_names: Vec<String>,
109
+ kind: MixinKind,
107
110
  },
108
111
  }
109
112
 
113
+ /// Kind of module mixin (include or extend)
114
+ #[derive(Debug, Clone, Copy)]
115
+ pub(crate) enum MixinKind {
116
+ Include,
117
+ Extend,
118
+ }
119
+
110
120
  /// First pass: check if node can be handled immediately without child processing
111
121
  ///
112
122
  /// Note: Literals (including Array) are handled in install.rs via install_literal
113
123
  /// because Array literals need child processing for element type inference.
114
- pub fn dispatch_simple(genv: &mut GlobalEnv, lenv: &mut LocalEnv, node: &Node) -> DispatchResult {
124
+ pub(crate) fn dispatch_simple(genv: &mut GlobalEnv, lenv: &mut LocalEnv, node: &Node) -> DispatchResult {
115
125
  // Instance variable read: @name
116
126
  if let Some(ivar_read) = node.as_instance_variable_read_node() {
117
127
  let ivar_name = bytes_to_name(ivar_read.name().as_slice());
@@ -172,8 +182,21 @@ fn extract_symbol_names(call_node: &ruby_prism::CallNode) -> Vec<String> {
172
182
  .unwrap_or_default()
173
183
  }
174
184
 
185
+ /// Extract module names from include/extend arguments
186
+ fn extract_mixin_module_names(call_node: &ruby_prism::CallNode) -> Vec<String> {
187
+ call_node
188
+ .arguments()
189
+ .map(|args| {
190
+ args.arguments()
191
+ .iter()
192
+ .filter_map(|arg| super::definitions::extract_constant_path(&arg))
193
+ .collect()
194
+ })
195
+ .unwrap_or_default()
196
+ }
197
+
175
198
  /// Check if node needs child processing
176
- pub fn dispatch_needs_child<'a>(node: &Node<'a>, source: &str) -> Option<NeedsChildKind<'a>> {
199
+ pub(crate) fn dispatch_needs_child<'a>(node: &Node<'a>, source: &str) -> Option<NeedsChildKind<'a>> {
177
200
  // Instance variable write: @name = value
178
201
  if let Some(ivar_write) = node.as_instance_variable_write_node() {
179
202
  let ivar_name = bytes_to_name(ivar_write.name().as_slice());
@@ -215,6 +238,7 @@ pub fn dispatch_needs_child<'a>(node: &Node<'a>, source: &str) -> Option<NeedsCh
215
238
  location,
216
239
  block,
217
240
  arguments,
241
+ safe_navigation: call_node.is_safe_navigation(),
218
242
  });
219
243
  } else {
220
244
  // No receiver: implicit self method call (e.g., `name`, `puts "hello"`)
@@ -232,21 +256,16 @@ pub fn dispatch_needs_child<'a>(node: &Node<'a>, source: &str) -> Option<NeedsCh
232
256
  return None;
233
257
  }
234
258
 
235
- if method_name == "include" {
236
- let module_names: Vec<String> = call_node
237
- .arguments()
238
- .map(|args| {
239
- args.arguments()
240
- .iter()
241
- .filter_map(|arg| {
242
- super::definitions::extract_constant_path(&arg)
243
- })
244
- .collect()
245
- })
246
- .unwrap_or_default();
259
+ let mixin_kind = match method_name.as_str() {
260
+ "include" => Some(MixinKind::Include),
261
+ "extend" => Some(MixinKind::Extend),
262
+ _ => None,
263
+ };
247
264
 
265
+ if let Some(kind) = mixin_kind {
266
+ let module_names = extract_mixin_module_names(&call_node);
248
267
  if !module_names.is_empty() {
249
- return Some(NeedsChildKind::IncludeDeclaration { module_names });
268
+ return Some(NeedsChildKind::ModuleMixinDeclaration { module_names, kind });
250
269
  }
251
270
  return None;
252
271
  }
@@ -296,11 +315,12 @@ pub(crate) fn process_needs_child(
296
315
  location,
297
316
  block,
298
317
  arguments,
318
+ safe_navigation,
299
319
  } => {
300
320
  let recv_vtx = super::install::install_node(genv, lenv, changes, source, &receiver)?;
301
321
  process_method_call_common(
302
322
  genv, lenv, changes, source,
303
- MethodCallContext { recv_vtx, method_name, location, block, arguments },
323
+ MethodCallContext { recv_vtx, method_name, location, block, arguments, safe_navigation },
304
324
  )
305
325
  }
306
326
  NeedsChildKind::ImplicitSelfCall {
@@ -317,19 +337,23 @@ pub(crate) fn process_needs_child(
317
337
  };
318
338
  process_method_call_common(
319
339
  genv, lenv, changes, source,
320
- MethodCallContext { recv_vtx, method_name, location, block, arguments },
340
+ // Implicit self calls cannot use safe navigation (`&.` requires explicit receiver)
341
+ MethodCallContext { recv_vtx, method_name, location, block, arguments, safe_navigation: false },
321
342
  )
322
343
  }
323
344
  NeedsChildKind::AttrDeclaration { kind, attr_names } => {
324
345
  super::attributes::process_attr_declaration(genv, kind, attr_names);
325
346
  None
326
347
  }
327
- NeedsChildKind::IncludeDeclaration { module_names } => {
348
+ NeedsChildKind::ModuleMixinDeclaration { module_names, kind } => {
328
349
  if let Some(class_name) = genv.scope_manager.current_qualified_name() {
329
- // Ruby processes `include A, B` right-to-left (B first, then A on top),
350
+ // Ruby processes `include/extend A, B` right-to-left (B first, then A on top),
330
351
  // so A ends up with higher MRO priority. Reverse to match this behavior.
331
352
  for module_name in module_names.iter().rev() {
332
- genv.record_include(&class_name, module_name);
353
+ match kind {
354
+ MixinKind::Include => genv.record_include(&class_name, module_name),
355
+ MixinKind::Extend => genv.record_extend(&class_name, module_name),
356
+ }
333
357
  }
334
358
  }
335
359
  None
@@ -360,6 +384,7 @@ struct MethodCallContext<'a> {
360
384
  location: SourceLocation,
361
385
  block: Option<Node<'a>>,
362
386
  arguments: Vec<Node<'a>>,
387
+ safe_navigation: bool,
363
388
  }
364
389
 
365
390
  /// MethodCall / ImplicitSelfCall common processing:
@@ -377,6 +402,7 @@ fn process_method_call_common<'a>(
377
402
  location,
378
403
  block,
379
404
  arguments,
405
+ safe_navigation,
380
406
  } = ctx;
381
407
  if method_name == "!" {
382
408
  return Some(super::operators::process_not_operator(genv));
@@ -411,6 +437,7 @@ fn process_method_call_common<'a>(
411
437
  positional_arg_vtxs,
412
438
  kwarg_vtxs,
413
439
  location,
440
+ safe_navigation,
414
441
  ))
415
442
  }
416
443
 
@@ -422,980 +449,7 @@ fn finish_method_call(
422
449
  arg_vtxs: Vec<VertexId>,
423
450
  kwarg_vtxs: Option<HashMap<String, VertexId>>,
424
451
  location: SourceLocation,
452
+ safe_navigation: bool,
425
453
  ) -> VertexId {
426
- install_method_call(genv, recv_vtx, method_name, arg_vtxs, kwarg_vtxs, Some(location))
427
- }
428
-
429
- #[cfg(test)]
430
- mod tests {
431
- use super::*;
432
- use crate::analyzer::install::AstInstaller;
433
- use crate::parser::ParseSession;
434
-
435
- /// Helper: parse Ruby source, process with AstInstaller, and return GlobalEnv
436
- fn analyze(source: &str) -> GlobalEnv {
437
- let session = ParseSession::new();
438
- let parse_result = session.parse_source(source, "test.rb").unwrap();
439
- let root = parse_result.node();
440
- let program = root.as_program_node().unwrap();
441
-
442
- let mut genv = GlobalEnv::new();
443
- let mut lenv = LocalEnv::new();
444
-
445
- let mut installer = AstInstaller::new(&mut genv, &mut lenv, source);
446
- for stmt in &program.statements().body() {
447
- installer.install_node(&stmt);
448
- }
449
- installer.finish();
450
-
451
- genv
452
- }
453
-
454
- /// Helper: get the type string for a vertex ID (checks both Vertex and Source)
455
- fn get_type_show(genv: &GlobalEnv, vtx: VertexId) -> String {
456
- if let Some(vertex) = genv.get_vertex(vtx) {
457
- vertex.show()
458
- } else if let Some(source) = genv.get_source(vtx) {
459
- source.ty.show()
460
- } else {
461
- panic!("vertex {:?} not found as either Vertex or Source", vtx);
462
- }
463
- }
464
-
465
- // Test 1: Receiverless method call type resolution
466
- #[test]
467
- fn test_implicit_self_call_type_resolution() {
468
- let source = r#"
469
- class User
470
- def name
471
- "Alice"
472
- end
473
-
474
- def greet
475
- name
476
- end
477
- end
478
- "#;
479
- let genv = analyze(source);
480
-
481
- // User#greet should resolve to String via User#name
482
- let info = genv
483
- .resolve_method(&Type::instance("User"), "greet")
484
- .expect("User#greet should be registered");
485
- assert!(info.return_vertex.is_some());
486
-
487
- let ret_vtx = info.return_vertex.unwrap();
488
- assert_eq!(get_type_show(&genv, ret_vtx), "String");
489
- }
490
-
491
- // Test 2: Receiverless method call with arguments
492
- #[test]
493
- fn test_implicit_self_call_with_arguments() {
494
- let source = r#"
495
- class Calculator
496
- def add(x, y)
497
- x
498
- end
499
-
500
- def compute
501
- add(1, 2)
502
- end
503
- end
504
- "#;
505
- let genv = analyze(source);
506
-
507
- // Calculator#compute should resolve via Calculator#add
508
- let info = genv
509
- .resolve_method(&Type::instance("Calculator"), "compute")
510
- .expect("Calculator#compute should be registered");
511
- assert!(info.return_vertex.is_some());
512
-
513
- let ret_vtx = info.return_vertex.unwrap();
514
- assert_eq!(get_type_show(&genv, ret_vtx), "Integer");
515
- }
516
-
517
- // Test 3: Receiverless call in nested class
518
- #[test]
519
- fn test_implicit_self_call_in_nested_class() {
520
- let source = r#"
521
- module Api
522
- module V1
523
- class User
524
- def name
525
- "Alice"
526
- end
527
-
528
- def greet
529
- name
530
- end
531
- end
532
- end
533
- end
534
- "#;
535
- let genv = analyze(source);
536
-
537
- // Method registered with qualified name "Api::V1::User"
538
- let info = genv
539
- .resolve_method(&Type::instance("Api::V1::User"), "greet")
540
- .expect("Api::V1::User#greet should be registered");
541
- assert!(info.return_vertex.is_some());
542
-
543
- let ret_vtx = info.return_vertex.unwrap();
544
- assert_eq!(get_type_show(&genv, ret_vtx), "String");
545
- }
546
-
547
- // Test 4: Receiverless call in module
548
- #[test]
549
- fn test_implicit_self_call_in_module() {
550
- let source = r#"
551
- module Utils
552
- def self.format(value)
553
- value
554
- end
555
-
556
- def self.run
557
- format("test")
558
- end
559
- end
560
- "#;
561
- let genv = analyze(source);
562
-
563
- // Utils.run should be registered
564
- let info = genv
565
- .resolve_method(&Type::singleton("Utils"), "run")
566
- .expect("Utils.run should be registered");
567
- assert!(info.return_vertex.is_some());
568
- }
569
-
570
- // Test 5: Receiverless call from within block
571
- #[test]
572
- fn test_implicit_self_call_from_block() {
573
- let source = r#"
574
- class User
575
- def name
576
- "Alice"
577
- end
578
-
579
- def greet
580
- [1].each { name }
581
- end
582
- end
583
- "#;
584
- let genv = analyze(source);
585
-
586
- // User#name should be registered and resolve to String
587
- let name_info = genv
588
- .resolve_method(&Type::instance("User"), "name")
589
- .expect("User#name should be registered");
590
- assert!(name_info.return_vertex.is_some());
591
- assert_eq!(get_type_show(&genv, name_info.return_vertex.unwrap()), "String");
592
-
593
- // User#greet should also be registered (block contains implicit self call)
594
- let greet_info = genv
595
- .resolve_method(&Type::instance("User"), "greet")
596
- .expect("User#greet should be registered");
597
- assert!(greet_info.return_vertex.is_some());
598
- }
599
-
600
- // Test 6: Top-level receiverless call (Object receiver)
601
- #[test]
602
- fn test_implicit_self_call_at_top_level() {
603
- let source = r#"
604
- def helper
605
- "result"
606
- end
607
-
608
- helper
609
- "#;
610
- let genv = analyze(source);
611
-
612
- // Should not panic; top-level call uses Object as receiver type
613
- // NOTE: top-level def is not registered in method_registry yet,
614
- // so this will produce a type error (false positive).
615
- // The important thing is that it doesn't panic.
616
- // No panic is the real assertion - top-level call should be processed without error
617
- let _ = genv;
618
- }
619
-
620
- // Test 7: attr_reader basic — getter type resolution
621
- #[test]
622
- fn test_attr_reader_basic() {
623
- let source = r#"
624
- class User
625
- attr_reader :name
626
-
627
- def initialize
628
- @name = "Alice"
629
- end
630
- end
631
- "#;
632
- let genv = analyze(source);
633
-
634
- // User#name should be registered and resolve to String via @name
635
- let info = genv
636
- .resolve_method(&Type::instance("User"), "name")
637
- .expect("User#name should be registered");
638
- assert!(info.return_vertex.is_some());
639
- let ret_vtx = info.return_vertex.unwrap();
640
- assert_eq!(get_type_show(&genv, ret_vtx), "String");
641
-
642
- // Other methods still work
643
- let greet_src = r#"
644
- class User
645
- attr_reader :name
646
-
647
- def greet
648
- "hello"
649
- end
650
- end
651
- "#;
652
- let genv2 = analyze(greet_src);
653
- let info2 = genv2
654
- .resolve_method(&Type::instance("User"), "greet")
655
- .expect("User#greet should be registered");
656
- assert_eq!(get_type_show(&genv2, info2.return_vertex.unwrap()), "String");
657
- }
658
-
659
- // Test 8: attr_reader + self.name method call
660
- #[test]
661
- fn test_attr_reader_with_self_call() {
662
- let source = r#"
663
- class User
664
- attr_reader :name
665
-
666
- def initialize
667
- @name = "Alice"
668
- end
669
-
670
- def greet
671
- self.name
672
- end
673
- end
674
- "#;
675
- let genv = analyze(source);
676
-
677
- // User#greet should resolve to String via User#name → @name
678
- let info = genv
679
- .resolve_method(&Type::instance("User"), "greet")
680
- .expect("User#greet should be registered");
681
- assert!(info.return_vertex.is_some());
682
- let ret_vtx = info.return_vertex.unwrap();
683
- assert_eq!(get_type_show(&genv, ret_vtx), "String");
684
- }
685
-
686
- // Test 9: attr_reader + receiverless name call (proposal D integration)
687
- #[test]
688
- fn test_attr_reader_receiverless_call() {
689
- let source = r#"
690
- class User
691
- attr_reader :name
692
-
693
- def initialize
694
- @name = "Alice"
695
- end
696
-
697
- def greet
698
- name
699
- end
700
- end
701
- "#;
702
- let genv = analyze(source);
703
-
704
- // User#greet should resolve to String via implicit self → User#name → @name
705
- let info = genv
706
- .resolve_method(&Type::instance("User"), "greet")
707
- .expect("User#greet should be registered");
708
- assert!(info.return_vertex.is_some());
709
- let ret_vtx = info.return_vertex.unwrap();
710
- assert_eq!(get_type_show(&genv, ret_vtx), "String");
711
- }
712
-
713
- // Test 10: attr_accessor — both getter and setter
714
- #[test]
715
- fn test_attr_accessor() {
716
- let source = r#"
717
- class User
718
- attr_accessor :age
719
-
720
- def initialize
721
- @age = 30
722
- end
723
- end
724
- "#;
725
- let genv = analyze(source);
726
-
727
- // User#age (getter) should be registered and resolve to Integer
728
- let getter = genv
729
- .resolve_method(&Type::instance("User"), "age")
730
- .expect("User#age getter should be registered");
731
- assert!(getter.return_vertex.is_some());
732
- assert_eq!(get_type_show(&genv, getter.return_vertex.unwrap()), "Integer");
733
-
734
- // User#age= (setter) should also be registered
735
- let setter = genv
736
- .resolve_method(&Type::instance("User"), "age=")
737
- .expect("User#age= setter should be registered");
738
- assert!(setter.return_vertex.is_some());
739
- }
740
-
741
- // Test 11: multiple attributes in single declaration
742
- #[test]
743
- fn test_attr_reader_multiple() {
744
- let source = r#"
745
- class User
746
- attr_reader :name, :email
747
-
748
- def initialize
749
- @name = "Alice"
750
- @email = "alice@test.com"
751
- end
752
- end
753
- "#;
754
- let genv = analyze(source);
755
-
756
- let name_info = genv
757
- .resolve_method(&Type::instance("User"), "name")
758
- .expect("User#name should be registered");
759
- assert_eq!(get_type_show(&genv, name_info.return_vertex.unwrap()), "String");
760
-
761
- let email_info = genv
762
- .resolve_method(&Type::instance("User"), "email")
763
- .expect("User#email should be registered");
764
- assert_eq!(get_type_show(&genv, email_info.return_vertex.unwrap()), "String");
765
- }
766
-
767
- // Test 12: attr_reader in nested class
768
- #[test]
769
- fn test_attr_reader_nested_class() {
770
- let source = r#"
771
- module Api
772
- class User
773
- attr_reader :name
774
-
775
- def initialize
776
- @name = "Alice"
777
- end
778
- end
779
- end
780
- "#;
781
- let genv = analyze(source);
782
-
783
- // Registered with qualified name "Api::User"
784
- let info = genv
785
- .resolve_method(&Type::instance("Api::User"), "name")
786
- .expect("Api::User#name should be registered");
787
- assert!(info.return_vertex.is_some());
788
- assert_eq!(get_type_show(&genv, info.return_vertex.unwrap()), "String");
789
- }
790
-
791
- // Test 13: attr_writer only — setter registered, getter not
792
- #[test]
793
- fn test_attr_writer_only() {
794
- let source = r#"
795
- class User
796
- attr_writer :name
797
- end
798
- "#;
799
- let genv = analyze(source);
800
-
801
- // User#name= should be registered
802
- let setter = genv.resolve_method(&Type::instance("User"), "name=");
803
- assert!(setter.is_some(), "User#name= should be registered");
804
-
805
- // User#name (getter) should NOT be registered
806
- let getter = genv.resolve_method(&Type::instance("User"), "name");
807
- assert!(getter.is_none(), "User#name getter should NOT be registered for attr_writer");
808
- }
809
-
810
- // Test 14: attr_reader with no assignment (empty vertex)
811
- #[test]
812
- fn test_attr_reader_unassigned() {
813
- let source = r#"
814
- class User
815
- attr_reader :unknown
816
- end
817
- "#;
818
- let genv = analyze(source);
819
-
820
- // User#unknown should be registered (with empty vertex)
821
- let info = genv
822
- .resolve_method(&Type::instance("User"), "unknown")
823
- .expect("User#unknown should be registered");
824
- assert!(info.return_vertex.is_some());
825
- }
826
-
827
- // Test 15: super call independence (SuperNode is not CallNode)
828
- #[test]
829
- fn test_super_call_independence() {
830
- let source = r#"
831
- class Base
832
- def greet
833
- "hello"
834
- end
835
- end
836
-
837
- class Child < Base
838
- def greet
839
- super
840
- end
841
- end
842
- "#;
843
- let genv = analyze(source);
844
-
845
- // super is a SuperNode, not a CallNode, so ImplicitSelfCall should not be triggered.
846
- // Base#greet should still work.
847
- let info = genv
848
- .resolve_method(&Type::instance("Base"), "greet")
849
- .expect("Base#greet should be registered");
850
- assert!(info.return_vertex.is_some());
851
-
852
- let ret_vtx = info.return_vertex.unwrap();
853
- assert_eq!(get_type_show(&genv, ret_vtx), "String");
854
- }
855
-
856
- // Test 16: User.new → instance(User)
857
- #[test]
858
- fn test_constant_read_user_new() {
859
- let source = r#"
860
- class User
861
- def name
862
- "Alice"
863
- end
864
- end
865
-
866
- x = User.new
867
- "#;
868
- let genv = analyze(source);
869
- assert!(
870
- genv.type_errors.is_empty(),
871
- "User.new should not produce type errors: {:?}",
872
- genv.type_errors
873
- );
874
- }
875
-
876
- // Test 17: User.new.name → String
877
- #[test]
878
- fn test_constant_read_user_new_method_chain() {
879
- let source = r#"
880
- class User
881
- def name
882
- "Alice"
883
- end
884
- end
885
-
886
- x = User.new.name
887
- "#;
888
- let genv = analyze(source);
889
- assert!(
890
- genv.type_errors.is_empty(),
891
- "User.new.name should not produce type errors: {:?}",
892
- genv.type_errors
893
- );
894
- }
895
-
896
- // Test 18: Api::User.new → instance(Api::User) (ConstantPathNode)
897
- #[test]
898
- fn test_constant_path_qualified_new() {
899
- let source = r#"
900
- class Api::User
901
- def name
902
- "Alice"
903
- end
904
- end
905
-
906
- x = Api::User.new
907
- "#;
908
- let genv = analyze(source);
909
- assert!(
910
- genv.type_errors.is_empty(),
911
- "Api::User.new should not produce type errors: {:?}",
912
- genv.type_errors
913
- );
914
- }
915
-
916
- // Test 19: User.new("Alice") → initialize parameter propagation
917
- #[test]
918
- fn test_constant_read_new_with_initialize_params() {
919
- let source = r#"
920
- class User
921
- def initialize(name)
922
- @name = name
923
- end
924
- end
925
-
926
- x = User.new("Alice")
927
- "#;
928
- let genv = analyze(source);
929
- assert!(genv.type_errors.is_empty());
930
- }
931
-
932
- // Test 20: user = User.new; user.name → String
933
- #[test]
934
- fn test_constant_read_assign_and_call() {
935
- let source = r#"
936
- class User
937
- def name
938
- "Alice"
939
- end
940
- end
941
-
942
- user = User.new
943
- user.name
944
- "#;
945
- let genv = analyze(source);
946
- assert!(
947
- genv.type_errors.is_empty(),
948
- "user = User.new; user.name should not produce type errors: {:?}",
949
- genv.type_errors
950
- );
951
- }
952
-
953
- // Test 21: User.some_method should not produce type error (Singleton error suppression)
954
- #[test]
955
- fn test_constant_read_no_false_positive() {
956
- let source = r#"
957
- class User
958
- def name
959
- "Alice"
960
- end
961
- end
962
-
963
- User.some_method
964
- "#;
965
- let genv = analyze(source);
966
- assert!(
967
- genv.type_errors.is_empty(),
968
- "User.some_method should not produce type errors (Singleton suppression): {:?}",
969
- genv.type_errors
970
- );
971
- }
972
-
973
- // Test: ConstantPathNode.new resolves with qualified method name
974
- #[test]
975
- fn test_constant_path_new_with_qualified_method() {
976
- let source = r#"
977
- module Api
978
- class User
979
- def name
980
- "Alice"
981
- end
982
- end
983
- end
984
-
985
- Api::User.new.name
986
- "#;
987
- let genv = analyze(source);
988
- // Api::User.new.name should resolve correctly — no type errors
989
- assert!(
990
- genv.type_errors.is_empty(),
991
- "Api::User.new.name should not produce type errors: {:?}",
992
- genv.type_errors
993
- );
994
- }
995
-
996
- // Test 23: ConstantReadNode inside module resolves to qualified name
997
- #[test]
998
- fn test_constant_read_inside_module_resolves_qualified() {
999
- let source = r#"
1000
- module Api
1001
- class User
1002
- def name
1003
- "Alice"
1004
- end
1005
- end
1006
-
1007
- class Service
1008
- def run
1009
- User.new.name
1010
- end
1011
- end
1012
- end
1013
- "#;
1014
- let genv = analyze(source);
1015
- assert!(
1016
- genv.type_errors.is_empty(),
1017
- "User.new inside module Api should resolve to Api::User: {:?}",
1018
- genv.type_errors
1019
- );
1020
- }
1021
-
1022
- // Test 24: ConstantReadNode in deeply nested modules
1023
- #[test]
1024
- fn test_constant_read_deeply_nested() {
1025
- let source = r#"
1026
- module Api
1027
- module V1
1028
- class User
1029
- def name
1030
- "Alice"
1031
- end
1032
- end
1033
-
1034
- class Service
1035
- def run
1036
- User.new.name
1037
- end
1038
- end
1039
- end
1040
- end
1041
- "#;
1042
- let genv = analyze(source);
1043
- assert!(
1044
- genv.type_errors.is_empty(),
1045
- "User.new inside Api::V1 should resolve to Api::V1::User: {:?}",
1046
- genv.type_errors
1047
- );
1048
- }
1049
-
1050
- // Test 25: Same constant name in different modules
1051
- #[test]
1052
- fn test_constant_read_same_name_different_modules() {
1053
- let source = r#"
1054
- module Api
1055
- class User
1056
- def name; "Api User"; end
1057
- end
1058
- end
1059
-
1060
- module Admin
1061
- class User
1062
- def name; "Admin User"; end
1063
- end
1064
-
1065
- class Service
1066
- def run
1067
- User.new.name
1068
- end
1069
- end
1070
- end
1071
- "#;
1072
- let genv = analyze(source);
1073
- assert!(
1074
- genv.type_errors.is_empty(),
1075
- "User.new inside Admin should resolve to Admin::User: {:?}",
1076
- genv.type_errors
1077
- );
1078
- }
1079
-
1080
- // === Keyword argument tests ===
1081
-
1082
- // Test 26: Required keyword argument type propagation
1083
- #[test]
1084
- fn test_keyword_arg_required_propagation() {
1085
- let source = r#"
1086
- class Greeter
1087
- def greet(name:)
1088
- name
1089
- end
1090
- end
1091
-
1092
- Greeter.new.greet(name: "Alice")
1093
- "#;
1094
- let genv = analyze(source);
1095
-
1096
- let info = genv
1097
- .resolve_method(&Type::instance("Greeter"), "greet")
1098
- .expect("Greeter#greet should be registered");
1099
- let ret_vtx = info.return_vertex.unwrap();
1100
- assert_eq!(get_type_show(&genv, ret_vtx), "String");
1101
- }
1102
-
1103
- // Test 27: Optional keyword argument with default type
1104
- #[test]
1105
- fn test_keyword_arg_optional_default_type() {
1106
- let source = r#"
1107
- class Counter
1108
- def count(step: 1)
1109
- step
1110
- end
1111
- end
1112
- "#;
1113
- let genv = analyze(source);
1114
-
1115
- let info = genv
1116
- .resolve_method(&Type::instance("Counter"), "count")
1117
- .expect("Counter#count should be registered");
1118
- let ret_vtx = info.return_vertex.unwrap();
1119
- // step has Integer type from default value
1120
- assert_eq!(get_type_show(&genv, ret_vtx), "Integer");
1121
- }
1122
-
1123
- // Test 28: Positional and keyword arguments mixed
1124
- #[test]
1125
- fn test_positional_and_keyword_mixed() {
1126
- let source = r#"
1127
- class User
1128
- def initialize(id, name:)
1129
- @id = id
1130
- @name = name
1131
- end
1132
- end
1133
-
1134
- User.new(1, name: "Alice")
1135
- "#;
1136
- let genv = analyze(source);
1137
- assert!(genv.type_errors.is_empty());
1138
- }
1139
-
1140
- // Test 29: Keyword argument via .new propagation to initialize
1141
- #[test]
1142
- fn test_keyword_arg_via_new_to_initialize() {
1143
- let source = r#"
1144
- class Config
1145
- def initialize(debug:)
1146
- @debug = debug
1147
- end
1148
-
1149
- def debug?
1150
- @debug
1151
- end
1152
- end
1153
-
1154
- Config.new(debug: true)
1155
- "#;
1156
- let genv = analyze(source);
1157
- assert!(genv.type_errors.is_empty());
1158
- }
1159
-
1160
- // Test 30: Multiple keyword arguments
1161
- #[test]
1162
- fn test_multiple_keyword_args() {
1163
- let source = r#"
1164
- class User
1165
- def profile(name:, age:)
1166
- name
1167
- end
1168
- end
1169
-
1170
- User.new.profile(name: "Alice", age: 30)
1171
- "#;
1172
- let genv = analyze(source);
1173
-
1174
- let info = genv
1175
- .resolve_method(&Type::instance("User"), "profile")
1176
- .expect("User#profile should be registered");
1177
- let ret_vtx = info.return_vertex.unwrap();
1178
- assert_eq!(get_type_show(&genv, ret_vtx), "String");
1179
- }
1180
-
1181
- // === Include (mixin) tests ===
1182
-
1183
- // Test 31: Basic include — module method resolved on class instance
1184
- #[test]
1185
- fn test_include_basic() {
1186
- let source = r#"
1187
- module Greetable
1188
- def greet
1189
- "Hello!"
1190
- end
1191
- end
1192
-
1193
- class User
1194
- include Greetable
1195
- end
1196
-
1197
- User.new.greet
1198
- "#;
1199
- let genv = analyze(source);
1200
- assert!(
1201
- genv.type_errors.is_empty(),
1202
- "include should resolve Greetable#greet: {:?}",
1203
- genv.type_errors
1204
- );
1205
- }
1206
-
1207
- // Test 32: Include method return type inference
1208
- #[test]
1209
- fn test_include_method_return_type() {
1210
- let source = r#"
1211
- module Greetable
1212
- def greet
1213
- "Hello!"
1214
- end
1215
- end
1216
-
1217
- class User
1218
- include Greetable
1219
-
1220
- def say_hello
1221
- greet
1222
- end
1223
- end
1224
- "#;
1225
- let genv = analyze(source);
1226
-
1227
- let info = genv
1228
- .resolve_method(&Type::instance("User"), "say_hello")
1229
- .expect("User#say_hello should be registered");
1230
- let ret_vtx = info.return_vertex.unwrap();
1231
- assert_eq!(get_type_show(&genv, ret_vtx), "String");
1232
- }
1233
-
1234
- // Test 33: Multiple includes — last included module has priority
1235
- #[test]
1236
- fn test_include_multiple_modules() {
1237
- let source = r#"
1238
- module A
1239
- def foo
1240
- "from A"
1241
- end
1242
- end
1243
-
1244
- module B
1245
- def foo
1246
- 42
1247
- end
1248
- end
1249
-
1250
- class User
1251
- include A
1252
- include B
1253
- end
1254
-
1255
- User.new.foo
1256
- "#;
1257
- let genv = analyze(source);
1258
- assert!(genv.type_errors.is_empty());
1259
-
1260
- // B is included last → B#foo (Integer) should be resolved
1261
- let info = genv
1262
- .resolve_method(&Type::instance("User"), "foo")
1263
- .expect("User#foo should be resolved via include");
1264
- let ret_vtx = info.return_vertex.unwrap();
1265
- assert_eq!(get_type_show(&genv, ret_vtx), "Integer");
1266
- }
1267
-
1268
- // Test 34: Class's own method takes priority over included module
1269
- #[test]
1270
- fn test_include_class_method_priority() {
1271
- let source = r#"
1272
- module Greetable
1273
- def greet
1274
- "Hello from module!"
1275
- end
1276
- end
1277
-
1278
- class User
1279
- include Greetable
1280
-
1281
- def greet
1282
- 42
1283
- end
1284
- end
1285
-
1286
- User.new.greet
1287
- "#;
1288
- let genv = analyze(source);
1289
-
1290
- let info = genv
1291
- .resolve_method(&Type::instance("User"), "greet")
1292
- .expect("User#greet should be resolved");
1293
- let ret_vtx = info.return_vertex.unwrap();
1294
- // Class's own method (Integer) takes priority
1295
- assert_eq!(get_type_show(&genv, ret_vtx), "Integer");
1296
- }
1297
-
1298
- // Test 35: Include with qualified module name
1299
- #[test]
1300
- fn test_include_qualified_module() {
1301
- let source = r#"
1302
- module Api
1303
- module Helpers
1304
- def help
1305
- "help"
1306
- end
1307
- end
1308
- end
1309
-
1310
- class User
1311
- include Api::Helpers
1312
- end
1313
-
1314
- User.new.help
1315
- "#;
1316
- let genv = analyze(source);
1317
- assert!(
1318
- genv.type_errors.is_empty(),
1319
- "include Api::Helpers should resolve: {:?}",
1320
- genv.type_errors
1321
- );
1322
- }
1323
-
1324
- // Test 36: Include with simultaneous multiple modules
1325
- #[test]
1326
- fn test_include_simultaneous_multiple() {
1327
- let source = r#"
1328
- module A
1329
- def a_method
1330
- "a"
1331
- end
1332
- end
1333
-
1334
- module B
1335
- def b_method
1336
- 42
1337
- end
1338
- end
1339
-
1340
- class User
1341
- include A, B
1342
- end
1343
-
1344
- User.new.a_method
1345
- User.new.b_method
1346
- "#;
1347
- let genv = analyze(source);
1348
- assert!(
1349
- genv.type_errors.is_empty(),
1350
- "include A, B should resolve both modules: {:?}",
1351
- genv.type_errors
1352
- );
1353
- }
1354
-
1355
- // Test 37: Include with unknown module (no crash)
1356
- #[test]
1357
- fn test_include_unknown_module() {
1358
- let source = r#"
1359
- class User
1360
- include UnknownModule
1361
- end
1362
- "#;
1363
- let genv = analyze(source);
1364
- // Should not panic; unknown module is recorded but methods won't resolve
1365
- let _ = genv;
1366
- }
1367
-
1368
- // Test 38: Simultaneous include A, B — A has higher MRO priority (Ruby semantics)
1369
- #[test]
1370
- fn test_include_simultaneous_order() {
1371
- let source = r#"
1372
- module A
1373
- def foo
1374
- "from A"
1375
- end
1376
- end
1377
-
1378
- module B
1379
- def foo
1380
- 42
1381
- end
1382
- end
1383
-
1384
- class User
1385
- include A, B
1386
- end
1387
-
1388
- User.new.foo
1389
- "#;
1390
- let genv = analyze(source);
1391
- assert!(genv.type_errors.is_empty());
1392
-
1393
- // Ruby's `include A, B` processes right-to-left: B first, then A on top.
1394
- // A has higher MRO priority → A#foo (String) should be resolved.
1395
- let info = genv
1396
- .resolve_method(&Type::instance("User"), "foo")
1397
- .expect("User#foo should be resolved via include");
1398
- let ret_vtx = info.return_vertex.unwrap();
1399
- assert_eq!(get_type_show(&genv, ret_vtx), "String");
1400
- }
454
+ install_method_call(genv, recv_vtx, method_name, arg_vtxs, kwarg_vtxs, Some(location), safe_navigation)
1401
455
  }