method-ray 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,17 +4,25 @@
4
4
  //! and dispatches them to specialized handlers.
5
5
 
6
6
  use crate::env::{GlobalEnv, LocalEnv};
7
- use crate::graph::{ChangeSet, VertexId};
7
+ use crate::graph::{BlockParameterTypeBox, ChangeSet, VertexId};
8
8
  use crate::source_map::SourceLocation;
9
+ use crate::types::Type;
9
10
  use ruby_prism::Node;
10
11
 
11
12
  use super::calls::install_method_call;
12
- use super::literals::install_literal;
13
13
  use super::variables::{
14
14
  install_ivar_read, install_ivar_write, install_local_var_read, install_local_var_write,
15
15
  install_self,
16
16
  };
17
17
 
18
+ /// Kind of attr_* declaration
19
+ #[derive(Debug, Clone, Copy)]
20
+ pub enum AttrKind {
21
+ Reader,
22
+ Writer,
23
+ Accessor,
24
+ }
25
+
18
26
  /// Result of dispatching a simple node (no child processing needed)
19
27
  pub enum DispatchResult {
20
28
  /// Node produced a vertex
@@ -34,10 +42,29 @@ pub enum NeedsChildKind<'a> {
34
42
  receiver: Node<'a>,
35
43
  method_name: String,
36
44
  location: SourceLocation,
45
+ /// Optional block attached to the method call
46
+ block: Option<Node<'a>>,
47
+ /// Arguments to the method call
48
+ arguments: Vec<Node<'a>>,
49
+ },
50
+ /// Implicit self method call: method call without explicit receiver (implicit self)
51
+ ImplicitSelfCall {
52
+ method_name: String,
53
+ location: SourceLocation,
54
+ block: Option<Node<'a>>,
55
+ arguments: Vec<Node<'a>>,
56
+ },
57
+ /// attr_reader / attr_writer / attr_accessor declaration
58
+ AttrDeclaration {
59
+ kind: AttrKind,
60
+ attr_names: Vec<String>,
37
61
  },
38
62
  }
39
63
 
40
64
  /// First pass: check if node can be handled immediately without child processing
65
+ ///
66
+ /// Note: Literals (including Array) are handled in install.rs via install_literal
67
+ /// because Array literals need child processing for element type inference.
41
68
  pub fn dispatch_simple(genv: &mut GlobalEnv, lenv: &mut LocalEnv, node: &Node) -> DispatchResult {
42
69
  // Instance variable read: @name
43
70
  if let Some(ivar_read) = node.as_instance_variable_read_node() {
@@ -62,14 +89,41 @@ pub fn dispatch_simple(genv: &mut GlobalEnv, lenv: &mut LocalEnv, node: &Node) -
62
89
  };
63
90
  }
64
91
 
65
- // Literals (String, Integer, Array, Hash, nil, true, false, Symbol)
66
- if let Some(vtx) = install_literal(genv, node) {
92
+ // ConstantReadNode: User Type::Singleton("User")
93
+ if let Some(const_read) = node.as_constant_read_node() {
94
+ let name = String::from_utf8_lossy(const_read.name().as_slice()).to_string();
95
+ let vtx = genv.new_source(Type::singleton(&name));
67
96
  return DispatchResult::Vertex(vtx);
68
97
  }
69
98
 
99
+ // ConstantPathNode: Api::User → Type::Singleton("Api::User")
100
+ if node.as_constant_path_node().is_some() {
101
+ if let Some(name) = super::definitions::extract_constant_path(node) {
102
+ let vtx = genv.new_source(Type::singleton(&name));
103
+ return DispatchResult::Vertex(vtx);
104
+ }
105
+ }
106
+
70
107
  DispatchResult::NotHandled
71
108
  }
72
109
 
110
+ /// Extract symbol names from attr_* arguments (e.g., `attr_reader :name, :email`)
111
+ fn extract_symbol_names(call_node: &ruby_prism::CallNode) -> Vec<String> {
112
+ call_node
113
+ .arguments()
114
+ .map(|args| {
115
+ args.arguments()
116
+ .iter()
117
+ .filter_map(|arg| {
118
+ arg.as_symbol_node().map(|sym| {
119
+ String::from_utf8_lossy(&sym.unescaped()).to_string()
120
+ })
121
+ })
122
+ .collect()
123
+ })
124
+ .unwrap_or_default()
125
+ }
126
+
73
127
  /// Check if node needs child processing
74
128
  pub fn dispatch_needs_child<'a>(node: &Node<'a>, source: &str) -> Option<NeedsChildKind<'a>> {
75
129
  // Instance variable write: @name = value
@@ -90,16 +144,57 @@ pub fn dispatch_needs_child<'a>(node: &Node<'a>, source: &str) -> Option<NeedsCh
90
144
  });
91
145
  }
92
146
 
93
- // Method call: x.upcase
147
+ // Method call: x.upcase, x.each { |i| ... }, or name (implicit self)
94
148
  if let Some(call_node) = node.as_call_node() {
149
+ let method_name = String::from_utf8_lossy(call_node.name().as_slice()).to_string();
150
+ let block = call_node.block();
151
+ let arguments: Vec<Node<'a>> = call_node
152
+ .arguments()
153
+ .map(|args| args.arguments().iter().collect())
154
+ .unwrap_or_default();
155
+
95
156
  if let Some(receiver) = call_node.receiver() {
96
- let method_name = String::from_utf8_lossy(call_node.name().as_slice()).to_string();
157
+ // Explicit receiver: x.upcase, x.each { |i| ... }
158
+ let prism_location = call_node
159
+ .call_operator_loc()
160
+ .unwrap_or_else(|| node.location());
97
161
  let location =
98
- SourceLocation::from_prism_location_with_source(&node.location(), source);
162
+ SourceLocation::from_prism_location_with_source(&prism_location, source);
163
+
99
164
  return Some(NeedsChildKind::MethodCall {
100
165
  receiver,
101
166
  method_name,
102
167
  location,
168
+ block,
169
+ arguments,
170
+ });
171
+ } else {
172
+ // No receiver: implicit self method call (e.g., `name`, `puts "hello"`)
173
+
174
+ if let Some(kind) = match method_name.as_str() {
175
+ "attr_reader" => Some(AttrKind::Reader),
176
+ "attr_writer" => Some(AttrKind::Writer),
177
+ "attr_accessor" => Some(AttrKind::Accessor),
178
+ _ => None,
179
+ } {
180
+ let attr_names = extract_symbol_names(&call_node);
181
+ if !attr_names.is_empty() {
182
+ return Some(NeedsChildKind::AttrDeclaration { kind, attr_names });
183
+ }
184
+ return None;
185
+ }
186
+
187
+ let prism_location = call_node
188
+ .message_loc()
189
+ .unwrap_or_else(|| node.location());
190
+ let location =
191
+ SourceLocation::from_prism_location_with_source(&prism_location, source);
192
+
193
+ return Some(NeedsChildKind::ImplicitSelfCall {
194
+ method_name,
195
+ location,
196
+ block,
197
+ arguments,
103
198
  });
104
199
  }
105
200
  }
@@ -107,13 +202,73 @@ pub fn dispatch_needs_child<'a>(node: &Node<'a>, source: &str) -> Option<NeedsCh
107
202
  None
108
203
  }
109
204
 
205
+ /// Process a node that needs child processing
206
+ ///
207
+ /// This function handles the second phase of two-phase dispatch:
208
+ /// 1. `dispatch_needs_child` identifies the node kind and extracts data
209
+ /// 2. `process_needs_child` processes child nodes and completes the operation
210
+ pub(crate) fn process_needs_child(
211
+ genv: &mut GlobalEnv,
212
+ lenv: &mut LocalEnv,
213
+ changes: &mut ChangeSet,
214
+ source: &str,
215
+ kind: NeedsChildKind,
216
+ ) -> Option<VertexId> {
217
+ match kind {
218
+ NeedsChildKind::IvarWrite { ivar_name, value } => {
219
+ let value_vtx = super::install::install_node(genv, lenv, changes, source, &value)?;
220
+ Some(finish_ivar_write(genv, ivar_name, value_vtx))
221
+ }
222
+ NeedsChildKind::LocalVarWrite { var_name, value } => {
223
+ let value_vtx = super::install::install_node(genv, lenv, changes, source, &value)?;
224
+ Some(finish_local_var_write(genv, lenv, changes, var_name, value_vtx))
225
+ }
226
+ NeedsChildKind::MethodCall {
227
+ receiver,
228
+ method_name,
229
+ location,
230
+ block,
231
+ arguments,
232
+ } => {
233
+ let recv_vtx = super::install::install_node(genv, lenv, changes, source, &receiver)?;
234
+ process_method_call_common(
235
+ genv, lenv, changes, source, recv_vtx, method_name, location, block, arguments,
236
+ )
237
+ }
238
+ NeedsChildKind::ImplicitSelfCall {
239
+ method_name,
240
+ location,
241
+ block,
242
+ arguments,
243
+ } => {
244
+ // Use the same naming as method registration in definitions.rs
245
+ let recv_type_name = genv
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 {
250
+ genv.new_source(Type::instance(&name))
251
+ } else {
252
+ genv.new_source(Type::instance("Object"))
253
+ };
254
+ process_method_call_common(
255
+ genv, lenv, changes, source, recv_vtx, method_name, location, block, arguments,
256
+ )
257
+ }
258
+ NeedsChildKind::AttrDeclaration { kind, attr_names } => {
259
+ super::attributes::process_attr_declaration(genv, kind, attr_names);
260
+ None
261
+ }
262
+ }
263
+ }
264
+
110
265
  /// Finish instance variable write after child is processed
111
- pub fn finish_ivar_write(genv: &mut GlobalEnv, ivar_name: String, value_vtx: VertexId) -> VertexId {
266
+ fn finish_ivar_write(genv: &mut GlobalEnv, ivar_name: String, value_vtx: VertexId) -> VertexId {
112
267
  install_ivar_write(genv, ivar_name, value_vtx)
113
268
  }
114
269
 
115
270
  /// Finish local variable write after child is processed
116
- pub fn finish_local_var_write(
271
+ fn finish_local_var_write(
117
272
  genv: &mut GlobalEnv,
118
273
  lenv: &mut LocalEnv,
119
274
  changes: &mut ChangeSet,
@@ -123,12 +278,600 @@ pub fn finish_local_var_write(
123
278
  install_local_var_write(genv, lenv, changes, var_name, value_vtx)
124
279
  }
125
280
 
281
+ /// MethodCall / ImplicitSelfCall common processing:
282
+ /// Handles argument processing, block processing, and MethodCallBox creation after recv_vtx is obtained
283
+ fn process_method_call_common<'a>(
284
+ genv: &mut GlobalEnv,
285
+ lenv: &mut LocalEnv,
286
+ changes: &mut ChangeSet,
287
+ source: &str,
288
+ recv_vtx: VertexId,
289
+ method_name: String,
290
+ location: SourceLocation,
291
+ block: Option<Node<'a>>,
292
+ arguments: Vec<Node<'a>>,
293
+ ) -> Option<VertexId> {
294
+ let arg_vtxs: Vec<VertexId> = arguments
295
+ .iter()
296
+ .filter_map(|arg| super::install::install_node(genv, lenv, changes, source, arg))
297
+ .collect();
298
+
299
+ if let Some(block_node) = block {
300
+ if let Some(block) = block_node.as_block_node() {
301
+ let param_vtxs = super::blocks::process_block_node_with_params(
302
+ genv, lenv, changes, source, &block,
303
+ );
304
+
305
+ if !param_vtxs.is_empty() {
306
+ let box_id = genv.alloc_box_id();
307
+ let block_box = BlockParameterTypeBox::new(
308
+ box_id,
309
+ recv_vtx,
310
+ method_name.clone(),
311
+ param_vtxs,
312
+ );
313
+ genv.register_box(box_id, Box::new(block_box));
314
+ }
315
+ }
316
+ }
317
+
318
+ Some(finish_method_call(
319
+ genv, recv_vtx, method_name, arg_vtxs, location,
320
+ ))
321
+ }
322
+
126
323
  /// Finish method call after receiver is processed
127
- pub fn finish_method_call(
324
+ fn finish_method_call(
128
325
  genv: &mut GlobalEnv,
129
326
  recv_vtx: VertexId,
130
327
  method_name: String,
328
+ arg_vtxs: Vec<VertexId>,
131
329
  location: SourceLocation,
132
330
  ) -> VertexId {
133
- install_method_call(genv, recv_vtx, method_name, Some(location))
331
+ install_method_call(genv, recv_vtx, method_name, arg_vtxs, Some(location))
332
+ }
333
+
334
+ #[cfg(test)]
335
+ mod tests {
336
+ use super::*;
337
+ use crate::analyzer::install::AstInstaller;
338
+ use crate::parser::ParseSession;
339
+
340
+ /// Helper: parse Ruby source, process with AstInstaller, and return GlobalEnv
341
+ fn analyze(source: &str) -> GlobalEnv {
342
+ let session = ParseSession::new();
343
+ let parse_result = session.parse_source(source, "test.rb").unwrap();
344
+ let root = parse_result.node();
345
+ let program = root.as_program_node().unwrap();
346
+
347
+ let mut genv = GlobalEnv::new();
348
+ let mut lenv = LocalEnv::new();
349
+
350
+ let mut installer = AstInstaller::new(&mut genv, &mut lenv, source);
351
+ for stmt in &program.statements().body() {
352
+ installer.install_node(&stmt);
353
+ }
354
+ installer.finish();
355
+
356
+ genv
357
+ }
358
+
359
+ /// Helper: get the type string for a vertex ID (checks both Vertex and Source)
360
+ fn get_type_show(genv: &GlobalEnv, vtx: VertexId) -> String {
361
+ if let Some(vertex) = genv.get_vertex(vtx) {
362
+ vertex.show()
363
+ } else if let Some(source) = genv.get_source(vtx) {
364
+ source.ty.show()
365
+ } else {
366
+ panic!("vertex {:?} not found as either Vertex or Source", vtx);
367
+ }
368
+ }
369
+
370
+ // Test 1: Receiverless method call type resolution
371
+ #[test]
372
+ fn test_implicit_self_call_type_resolution() {
373
+ let source = r#"
374
+ class User
375
+ def name
376
+ "Alice"
377
+ end
378
+
379
+ def greet
380
+ name
381
+ end
382
+ end
383
+ "#;
384
+ let genv = analyze(source);
385
+
386
+ // User#greet should resolve to String via User#name
387
+ let info = genv
388
+ .resolve_method(&Type::instance("User"), "greet")
389
+ .expect("User#greet should be registered");
390
+ assert!(info.return_vertex.is_some());
391
+
392
+ let ret_vtx = info.return_vertex.unwrap();
393
+ assert_eq!(get_type_show(&genv, ret_vtx), "String");
394
+ }
395
+
396
+ // Test 2: Receiverless method call with arguments
397
+ #[test]
398
+ fn test_implicit_self_call_with_arguments() {
399
+ let source = r#"
400
+ class Calculator
401
+ def add(x, y)
402
+ x
403
+ end
404
+
405
+ def compute
406
+ add(1, 2)
407
+ end
408
+ end
409
+ "#;
410
+ let genv = analyze(source);
411
+
412
+ // Calculator#compute should resolve via Calculator#add
413
+ let info = genv
414
+ .resolve_method(&Type::instance("Calculator"), "compute")
415
+ .expect("Calculator#compute should be registered");
416
+ assert!(info.return_vertex.is_some());
417
+
418
+ let ret_vtx = info.return_vertex.unwrap();
419
+ assert_eq!(get_type_show(&genv, ret_vtx), "Integer");
420
+ }
421
+
422
+ // Test 3: Receiverless call in nested class
423
+ #[test]
424
+ fn test_implicit_self_call_in_nested_class() {
425
+ let source = r#"
426
+ module Api
427
+ module V1
428
+ class User
429
+ def name
430
+ "Alice"
431
+ end
432
+
433
+ def greet
434
+ name
435
+ end
436
+ end
437
+ end
438
+ end
439
+ "#;
440
+ let genv = analyze(source);
441
+
442
+ // Method registered with simple class name "User" (current behavior of definitions.rs)
443
+ let info = genv
444
+ .resolve_method(&Type::instance("User"), "greet")
445
+ .expect("User#greet should be registered");
446
+ assert!(info.return_vertex.is_some());
447
+
448
+ let ret_vtx = info.return_vertex.unwrap();
449
+ assert_eq!(get_type_show(&genv, ret_vtx), "String");
450
+ }
451
+
452
+ // Test 4: Receiverless call in module
453
+ #[test]
454
+ fn test_implicit_self_call_in_module() {
455
+ let source = r#"
456
+ module Utils
457
+ def self.format(value)
458
+ value
459
+ end
460
+
461
+ def self.run
462
+ format("test")
463
+ end
464
+ end
465
+ "#;
466
+ let genv = analyze(source);
467
+
468
+ // Utils.run should be registered
469
+ let info = genv
470
+ .resolve_method(&Type::instance("Utils"), "run")
471
+ .expect("Utils#run should be registered");
472
+ assert!(info.return_vertex.is_some());
473
+ }
474
+
475
+ // Test 5: Receiverless call from within block
476
+ #[test]
477
+ fn test_implicit_self_call_from_block() {
478
+ let source = r#"
479
+ class User
480
+ def name
481
+ "Alice"
482
+ end
483
+
484
+ def greet
485
+ [1].each { name }
486
+ end
487
+ end
488
+ "#;
489
+ let genv = analyze(source);
490
+
491
+ // User#name should be registered and resolve to String
492
+ let name_info = genv
493
+ .resolve_method(&Type::instance("User"), "name")
494
+ .expect("User#name should be registered");
495
+ assert!(name_info.return_vertex.is_some());
496
+ assert_eq!(get_type_show(&genv, name_info.return_vertex.unwrap()), "String");
497
+
498
+ // User#greet should also be registered (block contains implicit self call)
499
+ let greet_info = genv
500
+ .resolve_method(&Type::instance("User"), "greet")
501
+ .expect("User#greet should be registered");
502
+ assert!(greet_info.return_vertex.is_some());
503
+ }
504
+
505
+ // Test 6: Top-level receiverless call (Object receiver)
506
+ #[test]
507
+ fn test_implicit_self_call_at_top_level() {
508
+ let source = r#"
509
+ def helper
510
+ "result"
511
+ end
512
+
513
+ helper
514
+ "#;
515
+ let genv = analyze(source);
516
+
517
+ // Should not panic; top-level call uses Object as receiver type
518
+ // NOTE: top-level def is not registered in method_registry yet,
519
+ // so this will produce a type error (false positive).
520
+ // The important thing is that it doesn't panic.
521
+ // No panic is the real assertion - top-level call should be processed without error
522
+ let _ = genv;
523
+ }
524
+
525
+ // Test 7: attr_reader basic — getter type resolution
526
+ #[test]
527
+ fn test_attr_reader_basic() {
528
+ let source = r#"
529
+ class User
530
+ attr_reader :name
531
+
532
+ def initialize
533
+ @name = "Alice"
534
+ end
535
+ end
536
+ "#;
537
+ let genv = analyze(source);
538
+
539
+ // User#name should be registered and resolve to String via @name
540
+ let info = genv
541
+ .resolve_method(&Type::instance("User"), "name")
542
+ .expect("User#name should be registered");
543
+ assert!(info.return_vertex.is_some());
544
+ let ret_vtx = info.return_vertex.unwrap();
545
+ assert_eq!(get_type_show(&genv, ret_vtx), "String");
546
+
547
+ // Other methods still work
548
+ let greet_src = r#"
549
+ class User
550
+ attr_reader :name
551
+
552
+ def greet
553
+ "hello"
554
+ end
555
+ end
556
+ "#;
557
+ let genv2 = analyze(greet_src);
558
+ let info2 = genv2
559
+ .resolve_method(&Type::instance("User"), "greet")
560
+ .expect("User#greet should be registered");
561
+ assert_eq!(get_type_show(&genv2, info2.return_vertex.unwrap()), "String");
562
+ }
563
+
564
+ // Test 8: attr_reader + self.name method call
565
+ #[test]
566
+ fn test_attr_reader_with_self_call() {
567
+ let source = r#"
568
+ class User
569
+ attr_reader :name
570
+
571
+ def initialize
572
+ @name = "Alice"
573
+ end
574
+
575
+ def greet
576
+ self.name
577
+ end
578
+ end
579
+ "#;
580
+ let genv = analyze(source);
581
+
582
+ // User#greet should resolve to String via User#name → @name
583
+ let info = genv
584
+ .resolve_method(&Type::instance("User"), "greet")
585
+ .expect("User#greet should be registered");
586
+ assert!(info.return_vertex.is_some());
587
+ let ret_vtx = info.return_vertex.unwrap();
588
+ assert_eq!(get_type_show(&genv, ret_vtx), "String");
589
+ }
590
+
591
+ // Test 9: attr_reader + receiverless name call (proposal D integration)
592
+ #[test]
593
+ fn test_attr_reader_receiverless_call() {
594
+ let source = r#"
595
+ class User
596
+ attr_reader :name
597
+
598
+ def initialize
599
+ @name = "Alice"
600
+ end
601
+
602
+ def greet
603
+ name
604
+ end
605
+ end
606
+ "#;
607
+ let genv = analyze(source);
608
+
609
+ // User#greet should resolve to String via implicit self → User#name → @name
610
+ let info = genv
611
+ .resolve_method(&Type::instance("User"), "greet")
612
+ .expect("User#greet should be registered");
613
+ assert!(info.return_vertex.is_some());
614
+ let ret_vtx = info.return_vertex.unwrap();
615
+ assert_eq!(get_type_show(&genv, ret_vtx), "String");
616
+ }
617
+
618
+ // Test 10: attr_accessor — both getter and setter
619
+ #[test]
620
+ fn test_attr_accessor() {
621
+ let source = r#"
622
+ class User
623
+ attr_accessor :age
624
+
625
+ def initialize
626
+ @age = 30
627
+ end
628
+ end
629
+ "#;
630
+ let genv = analyze(source);
631
+
632
+ // User#age (getter) should be registered and resolve to Integer
633
+ let getter = genv
634
+ .resolve_method(&Type::instance("User"), "age")
635
+ .expect("User#age getter should be registered");
636
+ assert!(getter.return_vertex.is_some());
637
+ assert_eq!(get_type_show(&genv, getter.return_vertex.unwrap()), "Integer");
638
+
639
+ // User#age= (setter) should also be registered
640
+ let setter = genv
641
+ .resolve_method(&Type::instance("User"), "age=")
642
+ .expect("User#age= setter should be registered");
643
+ assert!(setter.return_vertex.is_some());
644
+ }
645
+
646
+ // Test 11: multiple attributes in single declaration
647
+ #[test]
648
+ fn test_attr_reader_multiple() {
649
+ let source = r#"
650
+ class User
651
+ attr_reader :name, :email
652
+
653
+ def initialize
654
+ @name = "Alice"
655
+ @email = "alice@test.com"
656
+ end
657
+ end
658
+ "#;
659
+ let genv = analyze(source);
660
+
661
+ let name_info = genv
662
+ .resolve_method(&Type::instance("User"), "name")
663
+ .expect("User#name should be registered");
664
+ assert_eq!(get_type_show(&genv, name_info.return_vertex.unwrap()), "String");
665
+
666
+ let email_info = genv
667
+ .resolve_method(&Type::instance("User"), "email")
668
+ .expect("User#email should be registered");
669
+ assert_eq!(get_type_show(&genv, email_info.return_vertex.unwrap()), "String");
670
+ }
671
+
672
+ // Test 12: attr_reader in nested class
673
+ #[test]
674
+ fn test_attr_reader_nested_class() {
675
+ let source = r#"
676
+ module Api
677
+ class User
678
+ attr_reader :name
679
+
680
+ def initialize
681
+ @name = "Alice"
682
+ end
683
+ end
684
+ end
685
+ "#;
686
+ let genv = analyze(source);
687
+
688
+ // Registered with simple class name "User"
689
+ let info = genv
690
+ .resolve_method(&Type::instance("User"), "name")
691
+ .expect("User#name should be registered");
692
+ assert!(info.return_vertex.is_some());
693
+ assert_eq!(get_type_show(&genv, info.return_vertex.unwrap()), "String");
694
+ }
695
+
696
+ // Test 13: attr_writer only — setter registered, getter not
697
+ #[test]
698
+ fn test_attr_writer_only() {
699
+ let source = r#"
700
+ class User
701
+ attr_writer :name
702
+ end
703
+ "#;
704
+ let genv = analyze(source);
705
+
706
+ // User#name= should be registered
707
+ let setter = genv.resolve_method(&Type::instance("User"), "name=");
708
+ assert!(setter.is_some(), "User#name= should be registered");
709
+
710
+ // User#name (getter) should NOT be registered
711
+ let getter = genv.resolve_method(&Type::instance("User"), "name");
712
+ assert!(getter.is_none(), "User#name getter should NOT be registered for attr_writer");
713
+ }
714
+
715
+ // Test 14: attr_reader with no assignment (empty vertex)
716
+ #[test]
717
+ fn test_attr_reader_unassigned() {
718
+ let source = r#"
719
+ class User
720
+ attr_reader :unknown
721
+ end
722
+ "#;
723
+ let genv = analyze(source);
724
+
725
+ // User#unknown should be registered (with empty vertex)
726
+ let info = genv
727
+ .resolve_method(&Type::instance("User"), "unknown")
728
+ .expect("User#unknown should be registered");
729
+ assert!(info.return_vertex.is_some());
730
+ }
731
+
732
+ // Test 15: super call independence (SuperNode is not CallNode)
733
+ #[test]
734
+ fn test_super_call_independence() {
735
+ let source = r#"
736
+ class Base
737
+ def greet
738
+ "hello"
739
+ end
740
+ end
741
+
742
+ class Child < Base
743
+ def greet
744
+ super
745
+ end
746
+ end
747
+ "#;
748
+ let genv = analyze(source);
749
+
750
+ // super is a SuperNode, not a CallNode, so ImplicitSelfCall should not be triggered.
751
+ // Base#greet should still work.
752
+ let info = genv
753
+ .resolve_method(&Type::instance("Base"), "greet")
754
+ .expect("Base#greet should be registered");
755
+ assert!(info.return_vertex.is_some());
756
+
757
+ let ret_vtx = info.return_vertex.unwrap();
758
+ assert_eq!(get_type_show(&genv, ret_vtx), "String");
759
+ }
760
+
761
+ // Test 16: User.new → instance(User)
762
+ #[test]
763
+ fn test_constant_read_user_new() {
764
+ let source = r#"
765
+ class User
766
+ def name
767
+ "Alice"
768
+ end
769
+ end
770
+
771
+ x = User.new
772
+ "#;
773
+ let genv = analyze(source);
774
+ assert!(
775
+ genv.type_errors.is_empty(),
776
+ "User.new should not produce type errors: {:?}",
777
+ genv.type_errors
778
+ );
779
+ }
780
+
781
+ // Test 17: User.new.name → String
782
+ #[test]
783
+ fn test_constant_read_user_new_method_chain() {
784
+ let source = r#"
785
+ class User
786
+ def name
787
+ "Alice"
788
+ end
789
+ end
790
+
791
+ x = User.new.name
792
+ "#;
793
+ let genv = analyze(source);
794
+ assert!(
795
+ genv.type_errors.is_empty(),
796
+ "User.new.name should not produce type errors: {:?}",
797
+ genv.type_errors
798
+ );
799
+ }
800
+
801
+ // Test 18: Api::User.new → instance(Api::User) (ConstantPathNode)
802
+ #[test]
803
+ fn test_constant_path_qualified_new() {
804
+ let source = r#"
805
+ class Api::User
806
+ def name
807
+ "Alice"
808
+ end
809
+ end
810
+
811
+ x = Api::User.new
812
+ "#;
813
+ let genv = analyze(source);
814
+ assert!(
815
+ genv.type_errors.is_empty(),
816
+ "Api::User.new should not produce type errors: {:?}",
817
+ genv.type_errors
818
+ );
819
+ }
820
+
821
+ // Test 19: User.new("Alice") → initialize parameter propagation
822
+ #[test]
823
+ fn test_constant_read_new_with_initialize_params() {
824
+ let source = r#"
825
+ class User
826
+ def initialize(name)
827
+ @name = name
828
+ end
829
+ end
830
+
831
+ x = User.new("Alice")
832
+ "#;
833
+ let genv = analyze(source);
834
+ assert!(genv.type_errors.is_empty());
835
+ }
836
+
837
+ // Test 20: user = User.new; user.name → String
838
+ #[test]
839
+ fn test_constant_read_assign_and_call() {
840
+ let source = r#"
841
+ class User
842
+ def name
843
+ "Alice"
844
+ end
845
+ end
846
+
847
+ user = User.new
848
+ user.name
849
+ "#;
850
+ let genv = analyze(source);
851
+ assert!(
852
+ genv.type_errors.is_empty(),
853
+ "user = User.new; user.name should not produce type errors: {:?}",
854
+ genv.type_errors
855
+ );
856
+ }
857
+
858
+ // Test 21: User.some_method should not produce type error (Singleton error suppression)
859
+ #[test]
860
+ fn test_constant_read_no_false_positive() {
861
+ let source = r#"
862
+ class User
863
+ def name
864
+ "Alice"
865
+ end
866
+ end
867
+
868
+ User.some_method
869
+ "#;
870
+ let genv = analyze(source);
871
+ assert!(
872
+ genv.type_errors.is_empty(),
873
+ "User.some_method should not produce type errors (Singleton suppression): {:?}",
874
+ genv.type_errors
875
+ );
876
+ }
134
877
  }