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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +20 -0
- data/README.md +9 -11
- data/core/Cargo.toml +1 -1
- data/core/src/analyzer/assignments.rs +0 -280
- data/core/src/analyzer/blocks.rs +0 -190
- data/core/src/analyzer/calls.rs +3 -32
- data/core/src/analyzer/conditionals.rs +0 -348
- data/core/src/analyzer/definitions.rs +11 -526
- data/core/src/analyzer/dispatch.rs +54 -1000
- data/core/src/analyzer/exceptions.rs +0 -454
- data/core/src/analyzer/literals.rs +0 -54
- data/core/src/analyzer/loops.rs +0 -207
- data/core/src/analyzer/mod.rs +0 -15
- data/core/src/analyzer/operators.rs +0 -205
- data/core/src/analyzer/parameters.rs +4 -227
- data/core/src/analyzer/parentheses.rs +0 -88
- data/core/src/analyzer/returns.rs +0 -152
- data/core/src/analyzer/super_calls.rs +1 -212
- data/core/src/analyzer/variables.rs +5 -25
- data/core/src/checker.rs +0 -13
- data/core/src/diagnostics/diagnostic.rs +0 -41
- data/core/src/diagnostics/formatter.rs +0 -38
- data/core/src/env/box_manager.rs +0 -30
- data/core/src/env/global_env.rs +52 -79
- data/core/src/env/local_env.rs +0 -50
- data/core/src/env/method_registry.rs +52 -233
- data/core/src/env/scope.rs +0 -375
- data/core/src/env/vertex_manager.rs +0 -73
- data/core/src/graph/box.rs +20 -439
- data/core/src/graph/change_set.rs +0 -65
- data/core/src/graph/vertex.rs +0 -69
- data/core/src/parser.rs +0 -77
- data/ext/Cargo.toml +1 -1
- data/lib/methodray/version.rb +1 -1
- metadata +5 -4
|
@@ -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
|
|
105
|
-
|
|
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
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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::
|
|
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
|
-
|
|
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::
|
|
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
|
-
|
|
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
|
}
|