method-ray 0.1.4 → 0.1.5
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +21 -0
- data/ext/Cargo.toml +1 -1
- data/ext/src/lib.rs +38 -59
- data/lib/methodray/cli.rb +1 -0
- data/lib/methodray/commands.rb +0 -1
- data/lib/methodray/version.rb +1 -1
- data/rust/Cargo.toml +1 -1
- data/rust/src/analyzer/attributes.rs +2 -2
- data/rust/src/analyzer/conditionals.rs +72 -0
- data/rust/src/analyzer/definitions.rs +385 -8
- data/rust/src/analyzer/dispatch.rs +31 -12
- data/rust/src/analyzer/install.rs +18 -0
- data/rust/src/analyzer/literals.rs +45 -0
- data/rust/src/analyzer/mod.rs +3 -0
- data/rust/src/analyzer/operators.rs +192 -0
- data/rust/src/analyzer/parentheses.rs +113 -0
- data/rust/src/analyzer/returns.rs +191 -0
- data/rust/src/checker.rs +2 -4
- data/rust/src/cli/args.rs +1 -1
- data/rust/src/env/global_env.rs +3 -4
- data/rust/src/env/scope.rs +21 -0
- data/rust/src/graph/box.rs +4 -2
- data/rust/src/main.rs +1 -0
- metadata +4 -1
|
@@ -65,8 +65,17 @@ pub(crate) fn process_def_node(
|
|
|
65
65
|
def_node: &ruby_prism::DefNode,
|
|
66
66
|
) -> Option<VertexId> {
|
|
67
67
|
let method_name = String::from_utf8_lossy(def_node.name().as_slice()).to_string();
|
|
68
|
+
|
|
69
|
+
// Check if this is a class method (def self.foo)
|
|
70
|
+
let is_class_method = def_node
|
|
71
|
+
.receiver()
|
|
72
|
+
.map(|r| r.as_self_node().is_some())
|
|
73
|
+
.unwrap_or(false);
|
|
74
|
+
|
|
68
75
|
install_method(genv, method_name.clone());
|
|
69
76
|
|
|
77
|
+
let merge_vtx = genv.scope_manager.current_method_return_vertex();
|
|
78
|
+
|
|
70
79
|
// Process parameters BEFORE processing body
|
|
71
80
|
let param_vtxs = if let Some(params_node) = def_node.parameters() {
|
|
72
81
|
install_parameters(genv, lenv, changes, source, ¶ms_node)
|
|
@@ -81,18 +90,27 @@ pub(crate) fn process_def_node(
|
|
|
81
90
|
}
|
|
82
91
|
}
|
|
83
92
|
|
|
84
|
-
//
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
93
|
+
// Connect last expression to merge vertex so that implicit return
|
|
94
|
+
// (Ruby's last-expression-is-return-value) is included in the union type
|
|
95
|
+
if let (Some(last), Some(merge)) = (last_vtx, merge_vtx) {
|
|
96
|
+
genv.add_edge(last, merge);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Register user-defined method with merge vertex as return vertex
|
|
100
|
+
let return_vtx = merge_vtx.or(last_vtx);
|
|
101
|
+
if let Some(ret_vtx) = return_vtx {
|
|
102
|
+
let recv_type_name = genv.scope_manager.current_qualified_name();
|
|
90
103
|
|
|
91
104
|
if let Some(name) = recv_type_name {
|
|
105
|
+
let recv_type = if is_class_method {
|
|
106
|
+
Type::singleton(&name)
|
|
107
|
+
} else {
|
|
108
|
+
Type::instance(&name)
|
|
109
|
+
};
|
|
92
110
|
genv.register_user_method(
|
|
93
|
-
|
|
111
|
+
recv_type,
|
|
94
112
|
&method_name,
|
|
95
|
-
|
|
113
|
+
ret_vtx,
|
|
96
114
|
param_vtxs,
|
|
97
115
|
);
|
|
98
116
|
}
|
|
@@ -334,4 +352,363 @@ mod tests {
|
|
|
334
352
|
.expect("User#name should be registered");
|
|
335
353
|
assert!(info.return_vertex.is_some());
|
|
336
354
|
}
|
|
355
|
+
|
|
356
|
+
#[test]
|
|
357
|
+
fn test_qualified_name_method_registration() {
|
|
358
|
+
let source = r#"
|
|
359
|
+
module Api
|
|
360
|
+
module V1
|
|
361
|
+
class User
|
|
362
|
+
def name
|
|
363
|
+
"Alice"
|
|
364
|
+
end
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
"#;
|
|
369
|
+
let session = ParseSession::new();
|
|
370
|
+
let parse_result = session.parse_source(source, "test.rb").unwrap();
|
|
371
|
+
let root = parse_result.node();
|
|
372
|
+
let program = root.as_program_node().unwrap();
|
|
373
|
+
|
|
374
|
+
let mut genv = GlobalEnv::new();
|
|
375
|
+
let mut lenv = LocalEnv::new();
|
|
376
|
+
let mut changes = ChangeSet::new();
|
|
377
|
+
|
|
378
|
+
for stmt in &program.statements().body() {
|
|
379
|
+
crate::analyzer::install::install_node(&mut genv, &mut lenv, &mut changes, source, &stmt);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Method should be registered with qualified name "Api::V1::User"
|
|
383
|
+
let info = genv
|
|
384
|
+
.resolve_method(&Type::instance("Api::V1::User"), "name")
|
|
385
|
+
.expect("Api::V1::User#name should be registered");
|
|
386
|
+
assert!(info.return_vertex.is_some());
|
|
387
|
+
|
|
388
|
+
// Should NOT be registered with simple name "User"
|
|
389
|
+
assert!(
|
|
390
|
+
genv.resolve_method(&Type::instance("User"), "name").is_none(),
|
|
391
|
+
"User#name should not exist — method should be registered under qualified name"
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
#[test]
|
|
396
|
+
fn test_same_class_name_different_namespace() {
|
|
397
|
+
let source = r#"
|
|
398
|
+
module Api
|
|
399
|
+
class User
|
|
400
|
+
def name
|
|
401
|
+
"api_user"
|
|
402
|
+
end
|
|
403
|
+
end
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
module Admin
|
|
407
|
+
class User
|
|
408
|
+
def name
|
|
409
|
+
"admin_user"
|
|
410
|
+
end
|
|
411
|
+
end
|
|
412
|
+
end
|
|
413
|
+
"#;
|
|
414
|
+
let session = ParseSession::new();
|
|
415
|
+
let parse_result = session.parse_source(source, "test.rb").unwrap();
|
|
416
|
+
let root = parse_result.node();
|
|
417
|
+
let program = root.as_program_node().unwrap();
|
|
418
|
+
|
|
419
|
+
let mut genv = GlobalEnv::new();
|
|
420
|
+
let mut lenv = LocalEnv::new();
|
|
421
|
+
let mut changes = ChangeSet::new();
|
|
422
|
+
|
|
423
|
+
for stmt in &program.statements().body() {
|
|
424
|
+
crate::analyzer::install::install_node(&mut genv, &mut lenv, &mut changes, source, &stmt);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Both should be registered separately
|
|
428
|
+
let api_info = genv
|
|
429
|
+
.resolve_method(&Type::instance("Api::User"), "name")
|
|
430
|
+
.expect("Api::User#name should be registered");
|
|
431
|
+
assert!(api_info.return_vertex.is_some());
|
|
432
|
+
|
|
433
|
+
let admin_info = genv
|
|
434
|
+
.resolve_method(&Type::instance("Admin::User"), "name")
|
|
435
|
+
.expect("Admin::User#name should be registered");
|
|
436
|
+
assert!(admin_info.return_vertex.is_some());
|
|
437
|
+
|
|
438
|
+
// Simple "User" should not resolve
|
|
439
|
+
assert!(
|
|
440
|
+
genv.resolve_method(&Type::instance("User"), "name").is_none(),
|
|
441
|
+
"User#name should not exist — both are under qualified names"
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
#[test]
|
|
446
|
+
fn test_class_method_registration() {
|
|
447
|
+
let source = r#"
|
|
448
|
+
class User
|
|
449
|
+
def self.create
|
|
450
|
+
"created"
|
|
451
|
+
end
|
|
452
|
+
end
|
|
453
|
+
"#;
|
|
454
|
+
let session = ParseSession::new();
|
|
455
|
+
let parse_result = session.parse_source(source, "test.rb").unwrap();
|
|
456
|
+
let root = parse_result.node();
|
|
457
|
+
let program = root.as_program_node().unwrap();
|
|
458
|
+
|
|
459
|
+
let mut genv = GlobalEnv::new();
|
|
460
|
+
let mut lenv = LocalEnv::new();
|
|
461
|
+
let mut changes = ChangeSet::new();
|
|
462
|
+
|
|
463
|
+
for stmt in &program.statements().body() {
|
|
464
|
+
crate::analyzer::install::install_node(&mut genv, &mut lenv, &mut changes, source, &stmt);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// def self.create should be registered as singleton method
|
|
468
|
+
let info = genv
|
|
469
|
+
.resolve_method(&Type::singleton("User"), "create")
|
|
470
|
+
.expect("User.create should be registered as singleton method");
|
|
471
|
+
assert!(info.return_vertex.is_some());
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
#[test]
|
|
475
|
+
fn test_class_method_with_params() {
|
|
476
|
+
let source = r#"
|
|
477
|
+
class User
|
|
478
|
+
def self.find(id)
|
|
479
|
+
"user"
|
|
480
|
+
end
|
|
481
|
+
end
|
|
482
|
+
"#;
|
|
483
|
+
let session = ParseSession::new();
|
|
484
|
+
let parse_result = session.parse_source(source, "test.rb").unwrap();
|
|
485
|
+
let root = parse_result.node();
|
|
486
|
+
let program = root.as_program_node().unwrap();
|
|
487
|
+
|
|
488
|
+
let mut genv = GlobalEnv::new();
|
|
489
|
+
let mut lenv = LocalEnv::new();
|
|
490
|
+
let mut changes = ChangeSet::new();
|
|
491
|
+
|
|
492
|
+
for stmt in &program.statements().body() {
|
|
493
|
+
crate::analyzer::install::install_node(&mut genv, &mut lenv, &mut changes, source, &stmt);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
let info = genv
|
|
497
|
+
.resolve_method(&Type::singleton("User"), "find")
|
|
498
|
+
.expect("User.find should be registered");
|
|
499
|
+
assert!(info.return_vertex.is_some());
|
|
500
|
+
assert_eq!(info.param_vertices.as_ref().unwrap().len(), 1);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
#[test]
|
|
504
|
+
fn test_class_method_in_qualified_namespace() {
|
|
505
|
+
let source = r#"
|
|
506
|
+
module Api
|
|
507
|
+
class User
|
|
508
|
+
def self.create
|
|
509
|
+
"created"
|
|
510
|
+
end
|
|
511
|
+
end
|
|
512
|
+
end
|
|
513
|
+
"#;
|
|
514
|
+
let session = ParseSession::new();
|
|
515
|
+
let parse_result = session.parse_source(source, "test.rb").unwrap();
|
|
516
|
+
let root = parse_result.node();
|
|
517
|
+
let program = root.as_program_node().unwrap();
|
|
518
|
+
|
|
519
|
+
let mut genv = GlobalEnv::new();
|
|
520
|
+
let mut lenv = LocalEnv::new();
|
|
521
|
+
let mut changes = ChangeSet::new();
|
|
522
|
+
|
|
523
|
+
for stmt in &program.statements().body() {
|
|
524
|
+
crate::analyzer::install::install_node(&mut genv, &mut lenv, &mut changes, source, &stmt);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
let info = genv
|
|
528
|
+
.resolve_method(&Type::singleton("Api::User"), "create")
|
|
529
|
+
.expect("Api::User.create should be registered");
|
|
530
|
+
assert!(info.return_vertex.is_some());
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
#[test]
|
|
534
|
+
fn test_class_method_not_registered_as_instance() {
|
|
535
|
+
let source = r#"
|
|
536
|
+
class User
|
|
537
|
+
def self.create
|
|
538
|
+
"created"
|
|
539
|
+
end
|
|
540
|
+
end
|
|
541
|
+
"#;
|
|
542
|
+
let session = ParseSession::new();
|
|
543
|
+
let parse_result = session.parse_source(source, "test.rb").unwrap();
|
|
544
|
+
let root = parse_result.node();
|
|
545
|
+
let program = root.as_program_node().unwrap();
|
|
546
|
+
|
|
547
|
+
let mut genv = GlobalEnv::new();
|
|
548
|
+
let mut lenv = LocalEnv::new();
|
|
549
|
+
let mut changes = ChangeSet::new();
|
|
550
|
+
|
|
551
|
+
for stmt in &program.statements().body() {
|
|
552
|
+
crate::analyzer::install::install_node(&mut genv, &mut lenv, &mut changes, source, &stmt);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// def self.create should NOT be registered as instance method
|
|
556
|
+
assert!(
|
|
557
|
+
genv.resolve_method(&Type::instance("User"), "create").is_none(),
|
|
558
|
+
"User#create should not exist — it's a class method"
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
#[test]
|
|
563
|
+
fn test_non_self_receiver_not_treated_as_class_method() {
|
|
564
|
+
let source = r#"
|
|
565
|
+
class User
|
|
566
|
+
def other.foo
|
|
567
|
+
"test"
|
|
568
|
+
end
|
|
569
|
+
end
|
|
570
|
+
"#;
|
|
571
|
+
let session = ParseSession::new();
|
|
572
|
+
let parse_result = session.parse_source(source, "test.rb").unwrap();
|
|
573
|
+
let root = parse_result.node();
|
|
574
|
+
let program = root.as_program_node().unwrap();
|
|
575
|
+
|
|
576
|
+
let mut genv = GlobalEnv::new();
|
|
577
|
+
let mut lenv = LocalEnv::new();
|
|
578
|
+
let mut changes = ChangeSet::new();
|
|
579
|
+
|
|
580
|
+
for stmt in &program.statements().body() {
|
|
581
|
+
crate::analyzer::install::install_node(&mut genv, &mut lenv, &mut changes, source, &stmt);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// def other.foo should NOT be registered as singleton method
|
|
585
|
+
assert!(
|
|
586
|
+
genv.resolve_method(&Type::singleton("User"), "foo").is_none(),
|
|
587
|
+
"User.foo should not exist — receiver is not self"
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
#[test]
|
|
592
|
+
fn test_class_method_return_type_inference() {
|
|
593
|
+
let source = r#"
|
|
594
|
+
class User
|
|
595
|
+
def self.create
|
|
596
|
+
"created"
|
|
597
|
+
end
|
|
598
|
+
end
|
|
599
|
+
"#;
|
|
600
|
+
let session = ParseSession::new();
|
|
601
|
+
let parse_result = session.parse_source(source, "test.rb").unwrap();
|
|
602
|
+
let root = parse_result.node();
|
|
603
|
+
let program = root.as_program_node().unwrap();
|
|
604
|
+
|
|
605
|
+
let mut genv = GlobalEnv::new();
|
|
606
|
+
let mut lenv = LocalEnv::new();
|
|
607
|
+
let mut changes = ChangeSet::new();
|
|
608
|
+
|
|
609
|
+
for stmt in &program.statements().body() {
|
|
610
|
+
crate::analyzer::install::install_node(&mut genv, &mut lenv, &mut changes, source, &stmt);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
let info = genv
|
|
614
|
+
.resolve_method(&Type::singleton("User"), "create")
|
|
615
|
+
.expect("User.create should be registered");
|
|
616
|
+
let ret_vtx = info.return_vertex.expect("should have return vertex");
|
|
617
|
+
|
|
618
|
+
// Run solver to propagate types
|
|
619
|
+
genv.apply_changes(changes);
|
|
620
|
+
genv.run_all();
|
|
621
|
+
|
|
622
|
+
let vertex = genv.get_vertex(ret_vtx).or_else(|| {
|
|
623
|
+
// return vertex might be a source
|
|
624
|
+
None
|
|
625
|
+
});
|
|
626
|
+
if let Some(v) = vertex {
|
|
627
|
+
assert_eq!(v.show(), "String");
|
|
628
|
+
} else {
|
|
629
|
+
// Check if it's a source
|
|
630
|
+
let src = genv.get_source(ret_vtx).expect("should have source or vertex");
|
|
631
|
+
assert_eq!(src.ty, Type::string());
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
#[test]
|
|
636
|
+
fn test_class_method_in_reopened_class() {
|
|
637
|
+
let source = r#"
|
|
638
|
+
class User
|
|
639
|
+
def self.create
|
|
640
|
+
"created"
|
|
641
|
+
end
|
|
642
|
+
end
|
|
643
|
+
|
|
644
|
+
class User
|
|
645
|
+
def self.destroy
|
|
646
|
+
"destroyed"
|
|
647
|
+
end
|
|
648
|
+
end
|
|
649
|
+
"#;
|
|
650
|
+
let session = ParseSession::new();
|
|
651
|
+
let parse_result = session.parse_source(source, "test.rb").unwrap();
|
|
652
|
+
let root = parse_result.node();
|
|
653
|
+
let program = root.as_program_node().unwrap();
|
|
654
|
+
|
|
655
|
+
let mut genv = GlobalEnv::new();
|
|
656
|
+
let mut lenv = LocalEnv::new();
|
|
657
|
+
let mut changes = ChangeSet::new();
|
|
658
|
+
|
|
659
|
+
for stmt in &program.statements().body() {
|
|
660
|
+
crate::analyzer::install::install_node(&mut genv, &mut lenv, &mut changes, source, &stmt);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// Both class methods should be registered
|
|
664
|
+
assert!(
|
|
665
|
+
genv.resolve_method(&Type::singleton("User"), "create").is_some(),
|
|
666
|
+
"User.create should be registered"
|
|
667
|
+
);
|
|
668
|
+
assert!(
|
|
669
|
+
genv.resolve_method(&Type::singleton("User"), "destroy").is_some(),
|
|
670
|
+
"User.destroy should be registered"
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
#[test]
|
|
675
|
+
fn test_class_method_param_type_propagation() {
|
|
676
|
+
let source = r#"
|
|
677
|
+
class User
|
|
678
|
+
def self.find(id)
|
|
679
|
+
id
|
|
680
|
+
end
|
|
681
|
+
end
|
|
682
|
+
|
|
683
|
+
User.find(42)
|
|
684
|
+
"#;
|
|
685
|
+
let session = ParseSession::new();
|
|
686
|
+
let parse_result = session.parse_source(source, "test.rb").unwrap();
|
|
687
|
+
let root = parse_result.node();
|
|
688
|
+
let program = root.as_program_node().unwrap();
|
|
689
|
+
|
|
690
|
+
let mut genv = GlobalEnv::new();
|
|
691
|
+
let mut lenv = LocalEnv::new();
|
|
692
|
+
let mut changes = ChangeSet::new();
|
|
693
|
+
|
|
694
|
+
for stmt in &program.statements().body() {
|
|
695
|
+
crate::analyzer::install::install_node(&mut genv, &mut lenv, &mut changes, source, &stmt);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
let info = genv
|
|
699
|
+
.resolve_method(&Type::singleton("User"), "find")
|
|
700
|
+
.expect("User.find should be registered");
|
|
701
|
+
let param_vtxs = info.param_vertices.as_ref().expect("should have param vertices");
|
|
702
|
+
assert_eq!(param_vtxs.len(), 1);
|
|
703
|
+
|
|
704
|
+
let param_vtx = param_vtxs[0];
|
|
705
|
+
|
|
706
|
+
// Run solver to propagate argument types
|
|
707
|
+
genv.apply_changes(changes);
|
|
708
|
+
genv.run_all();
|
|
709
|
+
|
|
710
|
+
// Parameter should have Integer type propagated from call site
|
|
711
|
+
let vertex = genv.get_vertex(param_vtx).expect("param vertex should exist");
|
|
712
|
+
assert_eq!(vertex.show(), "Integer");
|
|
713
|
+
}
|
|
337
714
|
}
|
|
@@ -241,12 +241,8 @@ pub(crate) fn process_needs_child(
|
|
|
241
241
|
block,
|
|
242
242
|
arguments,
|
|
243
243
|
} => {
|
|
244
|
-
// Use
|
|
245
|
-
let
|
|
246
|
-
.scope_manager
|
|
247
|
-
.current_class_name()
|
|
248
|
-
.or_else(|| genv.scope_manager.current_module_name());
|
|
249
|
-
let recv_vtx = if let Some(name) = recv_type_name {
|
|
244
|
+
// Use qualified name to match method registration in definitions.rs
|
|
245
|
+
let recv_vtx = if let Some(name) = genv.scope_manager.current_qualified_name() {
|
|
250
246
|
genv.new_source(Type::instance(&name))
|
|
251
247
|
} else {
|
|
252
248
|
genv.new_source(Type::instance("Object"))
|
|
@@ -439,10 +435,10 @@ end
|
|
|
439
435
|
"#;
|
|
440
436
|
let genv = analyze(source);
|
|
441
437
|
|
|
442
|
-
// Method registered with
|
|
438
|
+
// Method registered with qualified name "Api::V1::User"
|
|
443
439
|
let info = genv
|
|
444
|
-
.resolve_method(&Type::instance("User"), "greet")
|
|
445
|
-
.expect("User#greet should be registered");
|
|
440
|
+
.resolve_method(&Type::instance("Api::V1::User"), "greet")
|
|
441
|
+
.expect("Api::V1::User#greet should be registered");
|
|
446
442
|
assert!(info.return_vertex.is_some());
|
|
447
443
|
|
|
448
444
|
let ret_vtx = info.return_vertex.unwrap();
|
|
@@ -685,10 +681,10 @@ end
|
|
|
685
681
|
"#;
|
|
686
682
|
let genv = analyze(source);
|
|
687
683
|
|
|
688
|
-
// Registered with
|
|
684
|
+
// Registered with qualified name "Api::User"
|
|
689
685
|
let info = genv
|
|
690
|
-
.resolve_method(&Type::instance("User"), "name")
|
|
691
|
-
.expect("User#name should be registered");
|
|
686
|
+
.resolve_method(&Type::instance("Api::User"), "name")
|
|
687
|
+
.expect("Api::User#name should be registered");
|
|
692
688
|
assert!(info.return_vertex.is_some());
|
|
693
689
|
assert_eq!(get_type_show(&genv, info.return_vertex.unwrap()), "String");
|
|
694
690
|
}
|
|
@@ -874,4 +870,27 @@ User.some_method
|
|
|
874
870
|
genv.type_errors
|
|
875
871
|
);
|
|
876
872
|
}
|
|
873
|
+
|
|
874
|
+
// Test: ConstantPathNode.new resolves with qualified method name
|
|
875
|
+
#[test]
|
|
876
|
+
fn test_constant_path_new_with_qualified_method() {
|
|
877
|
+
let source = r#"
|
|
878
|
+
module Api
|
|
879
|
+
class User
|
|
880
|
+
def name
|
|
881
|
+
"Alice"
|
|
882
|
+
end
|
|
883
|
+
end
|
|
884
|
+
end
|
|
885
|
+
|
|
886
|
+
Api::User.new.name
|
|
887
|
+
"#;
|
|
888
|
+
let genv = analyze(source);
|
|
889
|
+
// Api::User.new.name should resolve correctly — no type errors
|
|
890
|
+
assert!(
|
|
891
|
+
genv.type_errors.is_empty(),
|
|
892
|
+
"Api::User.new.name should not produce type errors: {:?}",
|
|
893
|
+
genv.type_errors
|
|
894
|
+
);
|
|
895
|
+
}
|
|
877
896
|
}
|
|
@@ -13,6 +13,9 @@ use super::conditionals::{process_case_node, process_if_node, process_unless_nod
|
|
|
13
13
|
use super::definitions::{process_class_node, process_def_node, process_module_node};
|
|
14
14
|
use super::dispatch::{dispatch_needs_child, dispatch_simple, process_needs_child, DispatchResult};
|
|
15
15
|
use super::literals::install_literal_node;
|
|
16
|
+
use super::parentheses::process_parentheses_node;
|
|
17
|
+
use super::operators::{process_and_node, process_or_node};
|
|
18
|
+
use super::returns::process_return_node;
|
|
16
19
|
|
|
17
20
|
/// Build graph from AST (public API wrapper)
|
|
18
21
|
pub struct AstInstaller<'a> {
|
|
@@ -76,6 +79,21 @@ pub(crate) fn install_node(
|
|
|
76
79
|
return process_case_node(genv, lenv, changes, source, &case_node);
|
|
77
80
|
}
|
|
78
81
|
|
|
82
|
+
if let Some(paren_node) = node.as_parentheses_node() {
|
|
83
|
+
return process_parentheses_node(genv, lenv, changes, source, &paren_node);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if let Some(return_node) = node.as_return_node() {
|
|
87
|
+
return process_return_node(genv, lenv, changes, source, &return_node);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if let Some(and_node) = node.as_and_node() {
|
|
91
|
+
return process_and_node(genv, lenv, changes, source, &and_node);
|
|
92
|
+
}
|
|
93
|
+
if let Some(or_node) = node.as_or_node() {
|
|
94
|
+
return process_or_node(genv, lenv, changes, source, &or_node);
|
|
95
|
+
}
|
|
96
|
+
|
|
79
97
|
match dispatch_simple(genv, lenv, node) {
|
|
80
98
|
DispatchResult::Vertex(vtx) => return Some(vtx),
|
|
81
99
|
DispatchResult::NotHandled => {}
|
|
@@ -34,6 +34,30 @@ pub(crate) fn install_literal_node(
|
|
|
34
34
|
return install_range_literal(genv, lenv, changes, source, &range_node);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
// InterpolatedStringNode: "Hello, #{expr}" → String
|
|
38
|
+
if let Some(interp) = node.as_interpolated_string_node() {
|
|
39
|
+
for part in &interp.parts() {
|
|
40
|
+
super::install::install_node(genv, lenv, changes, source, &part);
|
|
41
|
+
}
|
|
42
|
+
return Some(genv.new_source(Type::string()));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// InterpolatedSymbolNode: :"hello_#{expr}" → Symbol
|
|
46
|
+
if let Some(interp) = node.as_interpolated_symbol_node() {
|
|
47
|
+
for part in &interp.parts() {
|
|
48
|
+
super::install::install_node(genv, lenv, changes, source, &part);
|
|
49
|
+
}
|
|
50
|
+
return Some(genv.new_source(Type::symbol()));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// InterpolatedRegularExpressionNode: /hello #{expr}/ → Regexp
|
|
54
|
+
if let Some(interp) = node.as_interpolated_regular_expression_node() {
|
|
55
|
+
for part in &interp.parts() {
|
|
56
|
+
super::install::install_node(genv, lenv, changes, source, &part);
|
|
57
|
+
}
|
|
58
|
+
return Some(genv.new_source(Type::regexp()));
|
|
59
|
+
}
|
|
60
|
+
|
|
37
61
|
install_simple_literal(genv, node)
|
|
38
62
|
}
|
|
39
63
|
|
|
@@ -246,4 +270,25 @@ mod tests {
|
|
|
246
270
|
let vtx = genv.new_source(Type::regexp());
|
|
247
271
|
assert_eq!(genv.get_source(vtx).unwrap().ty.show(), "Regexp");
|
|
248
272
|
}
|
|
273
|
+
|
|
274
|
+
#[test]
|
|
275
|
+
fn test_install_interpolated_string_literal() {
|
|
276
|
+
let mut genv = GlobalEnv::new();
|
|
277
|
+
let vtx = genv.new_source(Type::string());
|
|
278
|
+
assert_eq!(genv.get_source(vtx).unwrap().ty.show(), "String");
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
#[test]
|
|
282
|
+
fn test_install_interpolated_symbol_literal() {
|
|
283
|
+
let mut genv = GlobalEnv::new();
|
|
284
|
+
let vtx = genv.new_source(Type::symbol());
|
|
285
|
+
assert_eq!(genv.get_source(vtx).unwrap().ty.show(), "Symbol");
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
#[test]
|
|
289
|
+
fn test_install_interpolated_regexp_literal() {
|
|
290
|
+
let mut genv = GlobalEnv::new();
|
|
291
|
+
let vtx = genv.new_source(Type::regexp());
|
|
292
|
+
assert_eq!(genv.get_source(vtx).unwrap().ty.show(), "Regexp");
|
|
293
|
+
}
|
|
249
294
|
}
|