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.
@@ -27,6 +27,17 @@ pub(crate) fn process_class_node(
27
27
  ) -> Option<VertexId> {
28
28
  let class_name = extract_class_name(class_node);
29
29
  let superclass = class_node.superclass().and_then(|sup| extract_constant_path(&sup));
30
+
31
+ // Warn if superclass is a dynamic expression (not a constant path)
32
+ // TODO: Replace eprintln! with structured diagnostic (record_type_error or warning)
33
+ // so this is visible in LSP mode and includes source location.
34
+ if class_node.superclass().is_some() && superclass.is_none() {
35
+ eprintln!(
36
+ "[methodray] warning: dynamic superclass expression in class {}; inheritance will be ignored",
37
+ class_name
38
+ );
39
+ }
40
+
30
41
  install_class(genv, class_name, superclass.as_deref());
31
42
 
32
43
  if let Some(body) = class_node.body() {
@@ -192,529 +203,3 @@ pub(crate) fn extract_constant_path(node: &Node) -> Option<String> {
192
203
 
193
204
  None
194
205
  }
195
-
196
- #[cfg(test)]
197
- mod tests {
198
- use super::*;
199
- use crate::graph::ChangeSet;
200
- use crate::parser::ParseSession;
201
- use crate::types::Type;
202
-
203
- #[test]
204
- fn test_enter_exit_class_scope() {
205
- let mut genv = GlobalEnv::new();
206
-
207
- install_class(&mut genv, "User".to_string(), None);
208
- assert_eq!(
209
- genv.scope_manager.current_class_name(),
210
- Some("User".to_string())
211
- );
212
-
213
- exit_scope(&mut genv);
214
- assert_eq!(genv.scope_manager.current_class_name(), None);
215
- }
216
-
217
- #[test]
218
- fn test_enter_exit_module_scope() {
219
- let mut genv = GlobalEnv::new();
220
-
221
- install_module(&mut genv, "Utils".to_string());
222
- assert_eq!(
223
- genv.scope_manager.current_module_name(),
224
- Some("Utils".to_string())
225
- );
226
-
227
- exit_scope(&mut genv);
228
- assert_eq!(genv.scope_manager.current_module_name(), None);
229
- }
230
-
231
- #[test]
232
- fn test_nested_method_scope() {
233
- let mut genv = GlobalEnv::new();
234
-
235
- install_class(&mut genv, "User".to_string(), None);
236
- install_method(&mut genv, "greet".to_string());
237
-
238
- // Still in User class context
239
- assert_eq!(
240
- genv.scope_manager.current_class_name(),
241
- Some("User".to_string())
242
- );
243
-
244
- exit_scope(&mut genv); // exit method
245
- exit_scope(&mut genv); // exit class
246
-
247
- assert_eq!(genv.scope_manager.current_class_name(), None);
248
- }
249
-
250
- #[test]
251
- fn test_method_in_module() {
252
- let mut genv = GlobalEnv::new();
253
-
254
- install_module(&mut genv, "Helpers".to_string());
255
- install_method(&mut genv, "format".to_string());
256
-
257
- // Should find module context from within method
258
- assert_eq!(
259
- genv.scope_manager.current_module_name(),
260
- Some("Helpers".to_string())
261
- );
262
-
263
- exit_scope(&mut genv); // exit method
264
- exit_scope(&mut genv); // exit module
265
-
266
- assert_eq!(genv.scope_manager.current_module_name(), None);
267
- }
268
-
269
- #[test]
270
- fn test_extract_simple_class_name() {
271
- let source = "class User; end";
272
- let session = ParseSession::new();
273
- let parse_result = session.parse_source(source, "test.rb").unwrap();
274
- let root = parse_result.node();
275
- let program = root.as_program_node().unwrap();
276
- let stmt = program.statements().body().first().unwrap();
277
- let class_node = stmt.as_class_node().unwrap();
278
-
279
- let name = extract_class_name(&class_node);
280
- assert_eq!(name, "User");
281
- }
282
-
283
- #[test]
284
- fn test_extract_qualified_class_name() {
285
- let source = "class Api::User; end";
286
- let session = ParseSession::new();
287
- let parse_result = session.parse_source(source, "test.rb").unwrap();
288
- let root = parse_result.node();
289
- let program = root.as_program_node().unwrap();
290
- let stmt = program.statements().body().first().unwrap();
291
- let class_node = stmt.as_class_node().unwrap();
292
-
293
- let name = extract_class_name(&class_node);
294
- assert_eq!(name, "Api::User");
295
- }
296
-
297
- #[test]
298
- fn test_extract_deeply_qualified_class_name() {
299
- let source = "class Api::V1::Admin::User; end";
300
- let session = ParseSession::new();
301
- let parse_result = session.parse_source(source, "test.rb").unwrap();
302
- let root = parse_result.node();
303
- let program = root.as_program_node().unwrap();
304
- let stmt = program.statements().body().first().unwrap();
305
- let class_node = stmt.as_class_node().unwrap();
306
-
307
- let name = extract_class_name(&class_node);
308
- assert_eq!(name, "Api::V1::Admin::User");
309
- }
310
-
311
- #[test]
312
- fn test_extract_simple_module_name() {
313
- let source = "module Utils; end";
314
- let session = ParseSession::new();
315
- let parse_result = session.parse_source(source, "test.rb").unwrap();
316
- let root = parse_result.node();
317
- let program = root.as_program_node().unwrap();
318
- let stmt = program.statements().body().first().unwrap();
319
- let module_node = stmt.as_module_node().unwrap();
320
-
321
- let name = extract_module_name(&module_node);
322
- assert_eq!(name, "Utils");
323
- }
324
-
325
- #[test]
326
- fn test_extract_qualified_module_name() {
327
- let source = "module Api::V1; end";
328
- let session = ParseSession::new();
329
- let parse_result = session.parse_source(source, "test.rb").unwrap();
330
- let root = parse_result.node();
331
- let program = root.as_program_node().unwrap();
332
- let stmt = program.statements().body().first().unwrap();
333
- let module_node = stmt.as_module_node().unwrap();
334
-
335
- let name = extract_module_name(&module_node);
336
- assert_eq!(name, "Api::V1");
337
- }
338
-
339
- #[test]
340
- fn test_process_def_node_registers_user_method() {
341
- let source = "class User; def name; \"Alice\"; end; end";
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
- let mut changes = ChangeSet::new();
350
-
351
- let stmt = program.statements().body().first().unwrap();
352
- let class_node = stmt.as_class_node().unwrap();
353
- process_class_node(&mut genv, &mut lenv, &mut changes, source, &class_node);
354
-
355
- // User#name should be registered as a user-defined method
356
- let info = genv
357
- .resolve_method(&Type::instance("User"), "name")
358
- .expect("User#name should be registered");
359
- assert!(info.return_vertex.is_some());
360
- }
361
-
362
- #[test]
363
- fn test_qualified_name_method_registration() {
364
- let source = r#"
365
- module Api
366
- module V1
367
- class User
368
- def name
369
- "Alice"
370
- end
371
- end
372
- end
373
- end
374
- "#;
375
- let session = ParseSession::new();
376
- let parse_result = session.parse_source(source, "test.rb").unwrap();
377
- let root = parse_result.node();
378
- let program = root.as_program_node().unwrap();
379
-
380
- let mut genv = GlobalEnv::new();
381
- let mut lenv = LocalEnv::new();
382
- let mut changes = ChangeSet::new();
383
-
384
- for stmt in &program.statements().body() {
385
- crate::analyzer::install::install_node(&mut genv, &mut lenv, &mut changes, source, &stmt);
386
- }
387
-
388
- // Method should be registered with qualified name "Api::V1::User"
389
- let info = genv
390
- .resolve_method(&Type::instance("Api::V1::User"), "name")
391
- .expect("Api::V1::User#name should be registered");
392
- assert!(info.return_vertex.is_some());
393
-
394
- // Should NOT be registered with simple name "User"
395
- assert!(
396
- genv.resolve_method(&Type::instance("User"), "name").is_none(),
397
- "User#name should not exist — method should be registered under qualified name"
398
- );
399
- }
400
-
401
- #[test]
402
- fn test_same_class_name_different_namespace() {
403
- let source = r#"
404
- module Api
405
- class User
406
- def name
407
- "api_user"
408
- end
409
- end
410
- end
411
-
412
- module Admin
413
- class User
414
- def name
415
- "admin_user"
416
- end
417
- end
418
- end
419
- "#;
420
- let session = ParseSession::new();
421
- let parse_result = session.parse_source(source, "test.rb").unwrap();
422
- let root = parse_result.node();
423
- let program = root.as_program_node().unwrap();
424
-
425
- let mut genv = GlobalEnv::new();
426
- let mut lenv = LocalEnv::new();
427
- let mut changes = ChangeSet::new();
428
-
429
- for stmt in &program.statements().body() {
430
- crate::analyzer::install::install_node(&mut genv, &mut lenv, &mut changes, source, &stmt);
431
- }
432
-
433
- // Both should be registered separately
434
- let api_info = genv
435
- .resolve_method(&Type::instance("Api::User"), "name")
436
- .expect("Api::User#name should be registered");
437
- assert!(api_info.return_vertex.is_some());
438
-
439
- let admin_info = genv
440
- .resolve_method(&Type::instance("Admin::User"), "name")
441
- .expect("Admin::User#name should be registered");
442
- assert!(admin_info.return_vertex.is_some());
443
-
444
- // Simple "User" should not resolve
445
- assert!(
446
- genv.resolve_method(&Type::instance("User"), "name").is_none(),
447
- "User#name should not exist — both are under qualified names"
448
- );
449
- }
450
-
451
- #[test]
452
- fn test_class_method_registration() {
453
- let source = r#"
454
- class User
455
- def self.create
456
- "created"
457
- end
458
- end
459
- "#;
460
- let session = ParseSession::new();
461
- let parse_result = session.parse_source(source, "test.rb").unwrap();
462
- let root = parse_result.node();
463
- let program = root.as_program_node().unwrap();
464
-
465
- let mut genv = GlobalEnv::new();
466
- let mut lenv = LocalEnv::new();
467
- let mut changes = ChangeSet::new();
468
-
469
- for stmt in &program.statements().body() {
470
- crate::analyzer::install::install_node(&mut genv, &mut lenv, &mut changes, source, &stmt);
471
- }
472
-
473
- // def self.create should be registered as singleton method
474
- let info = genv
475
- .resolve_method(&Type::singleton("User"), "create")
476
- .expect("User.create should be registered as singleton method");
477
- assert!(info.return_vertex.is_some());
478
- }
479
-
480
- #[test]
481
- fn test_class_method_with_params() {
482
- let source = r#"
483
- class User
484
- def self.find(id)
485
- "user"
486
- end
487
- end
488
- "#;
489
- let session = ParseSession::new();
490
- let parse_result = session.parse_source(source, "test.rb").unwrap();
491
- let root = parse_result.node();
492
- let program = root.as_program_node().unwrap();
493
-
494
- let mut genv = GlobalEnv::new();
495
- let mut lenv = LocalEnv::new();
496
- let mut changes = ChangeSet::new();
497
-
498
- for stmt in &program.statements().body() {
499
- crate::analyzer::install::install_node(&mut genv, &mut lenv, &mut changes, source, &stmt);
500
- }
501
-
502
- let info = genv
503
- .resolve_method(&Type::singleton("User"), "find")
504
- .expect("User.find should be registered");
505
- assert!(info.return_vertex.is_some());
506
- assert_eq!(info.param_vertices.as_ref().unwrap().len(), 1);
507
- }
508
-
509
- #[test]
510
- fn test_class_method_in_qualified_namespace() {
511
- let source = r#"
512
- module Api
513
- class User
514
- def self.create
515
- "created"
516
- end
517
- end
518
- end
519
- "#;
520
- let session = ParseSession::new();
521
- let parse_result = session.parse_source(source, "test.rb").unwrap();
522
- let root = parse_result.node();
523
- let program = root.as_program_node().unwrap();
524
-
525
- let mut genv = GlobalEnv::new();
526
- let mut lenv = LocalEnv::new();
527
- let mut changes = ChangeSet::new();
528
-
529
- for stmt in &program.statements().body() {
530
- crate::analyzer::install::install_node(&mut genv, &mut lenv, &mut changes, source, &stmt);
531
- }
532
-
533
- let info = genv
534
- .resolve_method(&Type::singleton("Api::User"), "create")
535
- .expect("Api::User.create should be registered");
536
- assert!(info.return_vertex.is_some());
537
- }
538
-
539
- #[test]
540
- fn test_class_method_not_registered_as_instance() {
541
- let source = r#"
542
- class User
543
- def self.create
544
- "created"
545
- end
546
- end
547
- "#;
548
- let session = ParseSession::new();
549
- let parse_result = session.parse_source(source, "test.rb").unwrap();
550
- let root = parse_result.node();
551
- let program = root.as_program_node().unwrap();
552
-
553
- let mut genv = GlobalEnv::new();
554
- let mut lenv = LocalEnv::new();
555
- let mut changes = ChangeSet::new();
556
-
557
- for stmt in &program.statements().body() {
558
- crate::analyzer::install::install_node(&mut genv, &mut lenv, &mut changes, source, &stmt);
559
- }
560
-
561
- // def self.create should NOT be registered as instance method
562
- assert!(
563
- genv.resolve_method(&Type::instance("User"), "create").is_none(),
564
- "User#create should not exist — it's a class method"
565
- );
566
- }
567
-
568
- #[test]
569
- fn test_non_self_receiver_not_treated_as_class_method() {
570
- let source = r#"
571
- class User
572
- def other.foo
573
- "test"
574
- end
575
- end
576
- "#;
577
- let session = ParseSession::new();
578
- let parse_result = session.parse_source(source, "test.rb").unwrap();
579
- let root = parse_result.node();
580
- let program = root.as_program_node().unwrap();
581
-
582
- let mut genv = GlobalEnv::new();
583
- let mut lenv = LocalEnv::new();
584
- let mut changes = ChangeSet::new();
585
-
586
- for stmt in &program.statements().body() {
587
- crate::analyzer::install::install_node(&mut genv, &mut lenv, &mut changes, source, &stmt);
588
- }
589
-
590
- // def other.foo should NOT be registered as singleton method
591
- assert!(
592
- genv.resolve_method(&Type::singleton("User"), "foo").is_none(),
593
- "User.foo should not exist — receiver is not self"
594
- );
595
- }
596
-
597
- #[test]
598
- fn test_class_method_return_type_inference() {
599
- let source = r#"
600
- class User
601
- def self.create
602
- "created"
603
- end
604
- end
605
- "#;
606
- let session = ParseSession::new();
607
- let parse_result = session.parse_source(source, "test.rb").unwrap();
608
- let root = parse_result.node();
609
- let program = root.as_program_node().unwrap();
610
-
611
- let mut genv = GlobalEnv::new();
612
- let mut lenv = LocalEnv::new();
613
- let mut changes = ChangeSet::new();
614
-
615
- for stmt in &program.statements().body() {
616
- crate::analyzer::install::install_node(&mut genv, &mut lenv, &mut changes, source, &stmt);
617
- }
618
-
619
- let info = genv
620
- .resolve_method(&Type::singleton("User"), "create")
621
- .expect("User.create should be registered");
622
- let ret_vtx = info.return_vertex.expect("should have return vertex");
623
-
624
- // Run solver to propagate types
625
- genv.apply_changes(changes);
626
- genv.run_all();
627
-
628
- let vertex = genv.get_vertex(ret_vtx).or_else(|| {
629
- // return vertex might be a source
630
- None
631
- });
632
- if let Some(v) = vertex {
633
- assert_eq!(v.show(), "String");
634
- } else {
635
- // Check if it's a source
636
- let src = genv.get_source(ret_vtx).expect("should have source or vertex");
637
- assert_eq!(src.ty, Type::string());
638
- }
639
- }
640
-
641
- #[test]
642
- fn test_class_method_in_reopened_class() {
643
- let source = r#"
644
- class User
645
- def self.create
646
- "created"
647
- end
648
- end
649
-
650
- class User
651
- def self.destroy
652
- "destroyed"
653
- end
654
- end
655
- "#;
656
- let session = ParseSession::new();
657
- let parse_result = session.parse_source(source, "test.rb").unwrap();
658
- let root = parse_result.node();
659
- let program = root.as_program_node().unwrap();
660
-
661
- let mut genv = GlobalEnv::new();
662
- let mut lenv = LocalEnv::new();
663
- let mut changes = ChangeSet::new();
664
-
665
- for stmt in &program.statements().body() {
666
- crate::analyzer::install::install_node(&mut genv, &mut lenv, &mut changes, source, &stmt);
667
- }
668
-
669
- // Both class methods should be registered
670
- assert!(
671
- genv.resolve_method(&Type::singleton("User"), "create").is_some(),
672
- "User.create should be registered"
673
- );
674
- assert!(
675
- genv.resolve_method(&Type::singleton("User"), "destroy").is_some(),
676
- "User.destroy should be registered"
677
- );
678
- }
679
-
680
- #[test]
681
- fn test_class_method_param_type_propagation() {
682
- let source = r#"
683
- class User
684
- def self.find(id)
685
- id
686
- end
687
- end
688
-
689
- User.find(42)
690
- "#;
691
- let session = ParseSession::new();
692
- let parse_result = session.parse_source(source, "test.rb").unwrap();
693
- let root = parse_result.node();
694
- let program = root.as_program_node().unwrap();
695
-
696
- let mut genv = GlobalEnv::new();
697
- let mut lenv = LocalEnv::new();
698
- let mut changes = ChangeSet::new();
699
-
700
- for stmt in &program.statements().body() {
701
- crate::analyzer::install::install_node(&mut genv, &mut lenv, &mut changes, source, &stmt);
702
- }
703
-
704
- let info = genv
705
- .resolve_method(&Type::singleton("User"), "find")
706
- .expect("User.find should be registered");
707
- let param_vtxs = info.param_vertices.as_ref().expect("should have param vertices");
708
- assert_eq!(param_vtxs.len(), 1);
709
-
710
- let param_vtx = param_vtxs[0];
711
-
712
- // Run solver to propagate argument types
713
- genv.apply_changes(changes);
714
- genv.run_all();
715
-
716
- // Parameter should have Integer type propagated from call site
717
- let vertex = genv.get_vertex(param_vtx).expect("param vertex should exist");
718
- assert_eq!(vertex.show(), "Integer");
719
- }
720
- }