method-ray 0.1.8 → 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.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +38 -0
  3. data/README.md +9 -11
  4. data/{rust → core}/Cargo.toml +1 -1
  5. data/core/src/analyzer/assignments.rs +219 -0
  6. data/{rust → core}/src/analyzer/blocks.rs +0 -50
  7. data/{rust → core}/src/analyzer/calls.rs +3 -32
  8. data/core/src/analyzer/conditionals.rs +190 -0
  9. data/core/src/analyzer/definitions.rs +205 -0
  10. data/core/src/analyzer/dispatch.rs +455 -0
  11. data/core/src/analyzer/exceptions.rs +168 -0
  12. data/{rust → core}/src/analyzer/install.rs +16 -1
  13. data/{rust → core}/src/analyzer/literals.rs +3 -71
  14. data/core/src/analyzer/loops.rs +94 -0
  15. data/{rust → core}/src/analyzer/mod.rs +1 -15
  16. data/core/src/analyzer/operators.rs +79 -0
  17. data/{rust → core}/src/analyzer/parameters.rs +4 -67
  18. data/core/src/analyzer/parentheses.rs +25 -0
  19. data/core/src/analyzer/returns.rs +39 -0
  20. data/core/src/analyzer/super_calls.rs +74 -0
  21. data/{rust → core}/src/analyzer/variables.rs +5 -25
  22. data/{rust → core}/src/checker.rs +0 -13
  23. data/{rust → core}/src/diagnostics/diagnostic.rs +0 -41
  24. data/{rust → core}/src/diagnostics/formatter.rs +0 -38
  25. data/{rust → core}/src/env/box_manager.rs +0 -30
  26. data/{rust → core}/src/env/global_env.rs +67 -80
  27. data/core/src/env/local_env.rs +42 -0
  28. data/core/src/env/method_registry.rs +173 -0
  29. data/core/src/env/scope.rs +299 -0
  30. data/{rust → core}/src/env/vertex_manager.rs +0 -73
  31. data/core/src/graph/box.rs +347 -0
  32. data/{rust → core}/src/graph/change_set.rs +0 -65
  33. data/{rust → core}/src/graph/vertex.rs +0 -69
  34. data/{rust → core}/src/parser.rs +0 -77
  35. data/{rust → core}/src/types.rs +11 -0
  36. data/ext/Cargo.toml +2 -2
  37. data/lib/methodray/binary_locator.rb +2 -2
  38. data/lib/methodray/commands.rb +1 -1
  39. data/lib/methodray/version.rb +1 -1
  40. metadata +58 -56
  41. data/rust/src/analyzer/assignments.rs +0 -152
  42. data/rust/src/analyzer/conditionals.rs +0 -538
  43. data/rust/src/analyzer/definitions.rs +0 -719
  44. data/rust/src/analyzer/dispatch.rs +0 -1137
  45. data/rust/src/analyzer/exceptions.rs +0 -521
  46. data/rust/src/analyzer/loops.rs +0 -176
  47. data/rust/src/analyzer/operators.rs +0 -284
  48. data/rust/src/analyzer/parentheses.rs +0 -113
  49. data/rust/src/analyzer/returns.rs +0 -191
  50. data/rust/src/env/local_env.rs +0 -92
  51. data/rust/src/env/method_registry.rs +0 -268
  52. data/rust/src/env/scope.rs +0 -596
  53. data/rust/src/graph/box.rs +0 -766
  54. /data/{rust → core}/src/analyzer/attributes.rs +0 -0
  55. /data/{rust → core}/src/cache/mod.rs +0 -0
  56. /data/{rust → core}/src/cache/rbs_cache.rs +0 -0
  57. /data/{rust → core}/src/cli/args.rs +0 -0
  58. /data/{rust → core}/src/cli/commands.rs +0 -0
  59. /data/{rust → core}/src/cli/mod.rs +0 -0
  60. /data/{rust → core}/src/diagnostics/mod.rs +0 -0
  61. /data/{rust → core}/src/env/mod.rs +0 -0
  62. /data/{rust → core}/src/env/type_error.rs +0 -0
  63. /data/{rust → core}/src/graph/mod.rs +0 -0
  64. /data/{rust → core}/src/lib.rs +0 -0
  65. /data/{rust → core}/src/lsp/diagnostics.rs +0 -0
  66. /data/{rust → core}/src/lsp/main.rs +0 -0
  67. /data/{rust → core}/src/lsp/mod.rs +0 -0
  68. /data/{rust → core}/src/lsp/server.rs +0 -0
  69. /data/{rust → core}/src/main.rs +0 -0
  70. /data/{rust → core}/src/rbs/converter.rs +0 -0
  71. /data/{rust → core}/src/rbs/error.rs +0 -0
  72. /data/{rust → core}/src/rbs/loader.rs +0 -0
  73. /data/{rust → core}/src/rbs/mod.rs +0 -0
  74. /data/{rust → core}/src/source_map.rs +0 -0
@@ -1,719 +0,0 @@
1
- //! Definition Handlers - Processing Ruby class/method/module definitions
2
- //!
3
- //! This module is responsible for:
4
- //! - Class definition scope management (class Foo ... end)
5
- //! - Module definition scope management (module Bar ... end)
6
- //! - Method definition scope management (def baz ... end)
7
- //! - Extracting class/module names from AST nodes (including qualified names like Api::User)
8
-
9
- use std::collections::HashMap;
10
-
11
- use crate::env::{GlobalEnv, LocalEnv};
12
- use crate::graph::{ChangeSet, VertexId};
13
- use crate::types::Type;
14
- use ruby_prism::Node;
15
-
16
- use super::bytes_to_name;
17
- use super::install::install_statements;
18
- use super::parameters::install_parameters;
19
-
20
- /// Process class definition node
21
- pub(crate) fn process_class_node(
22
- genv: &mut GlobalEnv,
23
- lenv: &mut LocalEnv,
24
- changes: &mut ChangeSet,
25
- source: &str,
26
- class_node: &ruby_prism::ClassNode,
27
- ) -> Option<VertexId> {
28
- let class_name = extract_class_name(class_node);
29
- install_class(genv, class_name);
30
-
31
- if let Some(body) = class_node.body() {
32
- if let Some(statements) = body.as_statements_node() {
33
- install_statements(genv, lenv, changes, source, &statements);
34
- }
35
- }
36
-
37
- exit_scope(genv);
38
- None
39
- }
40
-
41
- /// Process module definition node
42
- pub(crate) fn process_module_node(
43
- genv: &mut GlobalEnv,
44
- lenv: &mut LocalEnv,
45
- changes: &mut ChangeSet,
46
- source: &str,
47
- module_node: &ruby_prism::ModuleNode,
48
- ) -> Option<VertexId> {
49
- let module_name = extract_module_name(module_node);
50
- install_module(genv, module_name);
51
-
52
- if let Some(body) = module_node.body() {
53
- if let Some(statements) = body.as_statements_node() {
54
- install_statements(genv, lenv, changes, source, &statements);
55
- }
56
- }
57
-
58
- exit_scope(genv);
59
- None
60
- }
61
-
62
- /// Process method definition node
63
- pub(crate) fn process_def_node(
64
- genv: &mut GlobalEnv,
65
- lenv: &mut LocalEnv,
66
- changes: &mut ChangeSet,
67
- source: &str,
68
- def_node: &ruby_prism::DefNode,
69
- ) -> Option<VertexId> {
70
- let method_name = bytes_to_name(def_node.name().as_slice());
71
-
72
- // Check if this is a class method (def self.foo)
73
- let is_class_method = def_node
74
- .receiver()
75
- .map(|r| r.as_self_node().is_some())
76
- .unwrap_or(false);
77
-
78
- install_method(genv, method_name.clone());
79
-
80
- let merge_vtx = genv.scope_manager.current_method_return_vertex();
81
-
82
- // Process parameters BEFORE processing body
83
- let (param_vtxs, keyword_param_vtxs) = if let Some(params_node) = def_node.parameters() {
84
- install_parameters(genv, lenv, changes, source, &params_node)
85
- } else {
86
- (vec![], HashMap::new())
87
- };
88
-
89
- let mut last_vtx = None;
90
- if let Some(body) = def_node.body() {
91
- if let Some(statements) = body.as_statements_node() {
92
- last_vtx = install_statements(genv, lenv, changes, source, &statements);
93
- }
94
- }
95
-
96
- // Connect last expression to merge vertex so that implicit return
97
- // (Ruby's last-expression-is-return-value) is included in the union type
98
- if let (Some(last), Some(merge)) = (last_vtx, merge_vtx) {
99
- genv.add_edge(last, merge);
100
- }
101
-
102
- // Register user-defined method with merge vertex as return vertex
103
- let return_vtx = merge_vtx.or(last_vtx);
104
- if let Some(ret_vtx) = return_vtx {
105
- let recv_type_name = genv.scope_manager.current_qualified_name();
106
-
107
- if let Some(name) = recv_type_name {
108
- let recv_type = if is_class_method {
109
- Type::singleton(&name)
110
- } else {
111
- Type::instance(&name)
112
- };
113
- let kw_params = (!keyword_param_vtxs.is_empty()).then_some(keyword_param_vtxs);
114
- genv.register_user_method(
115
- recv_type,
116
- &method_name,
117
- ret_vtx,
118
- param_vtxs,
119
- kw_params,
120
- );
121
- }
122
- }
123
-
124
- exit_scope(genv);
125
- None
126
- }
127
-
128
- /// Install class definition
129
- fn install_class(genv: &mut GlobalEnv, class_name: String) {
130
- genv.enter_class(class_name);
131
- }
132
-
133
- /// Install module definition
134
- fn install_module(genv: &mut GlobalEnv, module_name: String) {
135
- genv.enter_module(module_name);
136
- }
137
-
138
- /// Install method definition
139
- fn install_method(genv: &mut GlobalEnv, method_name: String) {
140
- genv.enter_method(method_name);
141
- }
142
-
143
- /// Exit current scope (class, module, or method)
144
- fn exit_scope(genv: &mut GlobalEnv) {
145
- genv.exit_scope();
146
- }
147
-
148
- /// Extract class name from ClassNode
149
- /// Supports both simple names (User) and qualified names (Api::V1::User)
150
- fn extract_class_name(class_node: &ruby_prism::ClassNode) -> String {
151
- extract_constant_path(&class_node.constant_path()).unwrap_or_else(|| "UnknownClass".to_string())
152
- }
153
-
154
- /// Extract module name from ModuleNode
155
- /// Supports both simple names (Utils) and qualified names (Api::V1::Utils)
156
- fn extract_module_name(module_node: &ruby_prism::ModuleNode) -> String {
157
- extract_constant_path(&module_node.constant_path())
158
- .unwrap_or_else(|| "UnknownModule".to_string())
159
- }
160
-
161
- /// Extract constant path from a Node (handles both ConstantReadNode and ConstantPathNode)
162
- ///
163
- /// Examples:
164
- /// - `User` (ConstantReadNode) → "User"
165
- /// - `Api::User` (ConstantPathNode) → "Api::User"
166
- /// - `Api::V1::User` (nested ConstantPathNode) → "Api::V1::User"
167
- /// - `::Api::User` (absolute path with COLON3) → "Api::User"
168
- pub(crate) fn extract_constant_path(node: &Node) -> Option<String> {
169
- // Simple constant read: `User`
170
- if let Some(constant_read) = node.as_constant_read_node() {
171
- return Some(bytes_to_name(constant_read.name().as_slice()));
172
- }
173
-
174
- // Constant path: `Api::User` or `Api::V1::User`
175
- if let Some(constant_path) = node.as_constant_path_node() {
176
- // name() returns Option<ConstantId>, use as_slice() to get &[u8]
177
- let name = constant_path
178
- .name()
179
- .map(|id| bytes_to_name(id.as_slice()))?;
180
-
181
- // Get parent path if exists
182
- if let Some(parent_node) = constant_path.parent() {
183
- if let Some(parent_path) = extract_constant_path(&parent_node) {
184
- return Some(format!("{}::{}", parent_path, name));
185
- }
186
- }
187
-
188
- // No parent (absolute path like `::User`)
189
- return Some(name);
190
- }
191
-
192
- None
193
- }
194
-
195
- #[cfg(test)]
196
- mod tests {
197
- use super::*;
198
- use crate::graph::ChangeSet;
199
- use crate::parser::ParseSession;
200
- use crate::types::Type;
201
-
202
- #[test]
203
- fn test_enter_exit_class_scope() {
204
- let mut genv = GlobalEnv::new();
205
-
206
- install_class(&mut genv, "User".to_string());
207
- assert_eq!(
208
- genv.scope_manager.current_class_name(),
209
- Some("User".to_string())
210
- );
211
-
212
- exit_scope(&mut genv);
213
- assert_eq!(genv.scope_manager.current_class_name(), None);
214
- }
215
-
216
- #[test]
217
- fn test_enter_exit_module_scope() {
218
- let mut genv = GlobalEnv::new();
219
-
220
- install_module(&mut genv, "Utils".to_string());
221
- assert_eq!(
222
- genv.scope_manager.current_module_name(),
223
- Some("Utils".to_string())
224
- );
225
-
226
- exit_scope(&mut genv);
227
- assert_eq!(genv.scope_manager.current_module_name(), None);
228
- }
229
-
230
- #[test]
231
- fn test_nested_method_scope() {
232
- let mut genv = GlobalEnv::new();
233
-
234
- install_class(&mut genv, "User".to_string());
235
- install_method(&mut genv, "greet".to_string());
236
-
237
- // Still in User class context
238
- assert_eq!(
239
- genv.scope_manager.current_class_name(),
240
- Some("User".to_string())
241
- );
242
-
243
- exit_scope(&mut genv); // exit method
244
- exit_scope(&mut genv); // exit class
245
-
246
- assert_eq!(genv.scope_manager.current_class_name(), None);
247
- }
248
-
249
- #[test]
250
- fn test_method_in_module() {
251
- let mut genv = GlobalEnv::new();
252
-
253
- install_module(&mut genv, "Helpers".to_string());
254
- install_method(&mut genv, "format".to_string());
255
-
256
- // Should find module context from within method
257
- assert_eq!(
258
- genv.scope_manager.current_module_name(),
259
- Some("Helpers".to_string())
260
- );
261
-
262
- exit_scope(&mut genv); // exit method
263
- exit_scope(&mut genv); // exit module
264
-
265
- assert_eq!(genv.scope_manager.current_module_name(), None);
266
- }
267
-
268
- #[test]
269
- fn test_extract_simple_class_name() {
270
- let source = "class User; end";
271
- let session = ParseSession::new();
272
- let parse_result = session.parse_source(source, "test.rb").unwrap();
273
- let root = parse_result.node();
274
- let program = root.as_program_node().unwrap();
275
- let stmt = program.statements().body().first().unwrap();
276
- let class_node = stmt.as_class_node().unwrap();
277
-
278
- let name = extract_class_name(&class_node);
279
- assert_eq!(name, "User");
280
- }
281
-
282
- #[test]
283
- fn test_extract_qualified_class_name() {
284
- let source = "class Api::User; end";
285
- let session = ParseSession::new();
286
- let parse_result = session.parse_source(source, "test.rb").unwrap();
287
- let root = parse_result.node();
288
- let program = root.as_program_node().unwrap();
289
- let stmt = program.statements().body().first().unwrap();
290
- let class_node = stmt.as_class_node().unwrap();
291
-
292
- let name = extract_class_name(&class_node);
293
- assert_eq!(name, "Api::User");
294
- }
295
-
296
- #[test]
297
- fn test_extract_deeply_qualified_class_name() {
298
- let source = "class Api::V1::Admin::User; end";
299
- let session = ParseSession::new();
300
- let parse_result = session.parse_source(source, "test.rb").unwrap();
301
- let root = parse_result.node();
302
- let program = root.as_program_node().unwrap();
303
- let stmt = program.statements().body().first().unwrap();
304
- let class_node = stmt.as_class_node().unwrap();
305
-
306
- let name = extract_class_name(&class_node);
307
- assert_eq!(name, "Api::V1::Admin::User");
308
- }
309
-
310
- #[test]
311
- fn test_extract_simple_module_name() {
312
- let source = "module Utils; end";
313
- let session = ParseSession::new();
314
- let parse_result = session.parse_source(source, "test.rb").unwrap();
315
- let root = parse_result.node();
316
- let program = root.as_program_node().unwrap();
317
- let stmt = program.statements().body().first().unwrap();
318
- let module_node = stmt.as_module_node().unwrap();
319
-
320
- let name = extract_module_name(&module_node);
321
- assert_eq!(name, "Utils");
322
- }
323
-
324
- #[test]
325
- fn test_extract_qualified_module_name() {
326
- let source = "module Api::V1; end";
327
- let session = ParseSession::new();
328
- let parse_result = session.parse_source(source, "test.rb").unwrap();
329
- let root = parse_result.node();
330
- let program = root.as_program_node().unwrap();
331
- let stmt = program.statements().body().first().unwrap();
332
- let module_node = stmt.as_module_node().unwrap();
333
-
334
- let name = extract_module_name(&module_node);
335
- assert_eq!(name, "Api::V1");
336
- }
337
-
338
- #[test]
339
- fn test_process_def_node_registers_user_method() {
340
- let source = "class User; def name; \"Alice\"; end; end";
341
- let session = ParseSession::new();
342
- let parse_result = session.parse_source(source, "test.rb").unwrap();
343
- let root = parse_result.node();
344
- let program = root.as_program_node().unwrap();
345
-
346
- let mut genv = GlobalEnv::new();
347
- let mut lenv = LocalEnv::new();
348
- let mut changes = ChangeSet::new();
349
-
350
- let stmt = program.statements().body().first().unwrap();
351
- let class_node = stmt.as_class_node().unwrap();
352
- process_class_node(&mut genv, &mut lenv, &mut changes, source, &class_node);
353
-
354
- // User#name should be registered as a user-defined method
355
- let info = genv
356
- .resolve_method(&Type::instance("User"), "name")
357
- .expect("User#name should be registered");
358
- assert!(info.return_vertex.is_some());
359
- }
360
-
361
- #[test]
362
- fn test_qualified_name_method_registration() {
363
- let source = r#"
364
- module Api
365
- module V1
366
- class User
367
- def name
368
- "Alice"
369
- end
370
- end
371
- end
372
- end
373
- "#;
374
- let session = ParseSession::new();
375
- let parse_result = session.parse_source(source, "test.rb").unwrap();
376
- let root = parse_result.node();
377
- let program = root.as_program_node().unwrap();
378
-
379
- let mut genv = GlobalEnv::new();
380
- let mut lenv = LocalEnv::new();
381
- let mut changes = ChangeSet::new();
382
-
383
- for stmt in &program.statements().body() {
384
- crate::analyzer::install::install_node(&mut genv, &mut lenv, &mut changes, source, &stmt);
385
- }
386
-
387
- // Method should be registered with qualified name "Api::V1::User"
388
- let info = genv
389
- .resolve_method(&Type::instance("Api::V1::User"), "name")
390
- .expect("Api::V1::User#name should be registered");
391
- assert!(info.return_vertex.is_some());
392
-
393
- // Should NOT be registered with simple name "User"
394
- assert!(
395
- genv.resolve_method(&Type::instance("User"), "name").is_none(),
396
- "User#name should not exist — method should be registered under qualified name"
397
- );
398
- }
399
-
400
- #[test]
401
- fn test_same_class_name_different_namespace() {
402
- let source = r#"
403
- module Api
404
- class User
405
- def name
406
- "api_user"
407
- end
408
- end
409
- end
410
-
411
- module Admin
412
- class User
413
- def name
414
- "admin_user"
415
- end
416
- end
417
- end
418
- "#;
419
- let session = ParseSession::new();
420
- let parse_result = session.parse_source(source, "test.rb").unwrap();
421
- let root = parse_result.node();
422
- let program = root.as_program_node().unwrap();
423
-
424
- let mut genv = GlobalEnv::new();
425
- let mut lenv = LocalEnv::new();
426
- let mut changes = ChangeSet::new();
427
-
428
- for stmt in &program.statements().body() {
429
- crate::analyzer::install::install_node(&mut genv, &mut lenv, &mut changes, source, &stmt);
430
- }
431
-
432
- // Both should be registered separately
433
- let api_info = genv
434
- .resolve_method(&Type::instance("Api::User"), "name")
435
- .expect("Api::User#name should be registered");
436
- assert!(api_info.return_vertex.is_some());
437
-
438
- let admin_info = genv
439
- .resolve_method(&Type::instance("Admin::User"), "name")
440
- .expect("Admin::User#name should be registered");
441
- assert!(admin_info.return_vertex.is_some());
442
-
443
- // Simple "User" should not resolve
444
- assert!(
445
- genv.resolve_method(&Type::instance("User"), "name").is_none(),
446
- "User#name should not exist — both are under qualified names"
447
- );
448
- }
449
-
450
- #[test]
451
- fn test_class_method_registration() {
452
- let source = r#"
453
- class User
454
- def self.create
455
- "created"
456
- end
457
- end
458
- "#;
459
- let session = ParseSession::new();
460
- let parse_result = session.parse_source(source, "test.rb").unwrap();
461
- let root = parse_result.node();
462
- let program = root.as_program_node().unwrap();
463
-
464
- let mut genv = GlobalEnv::new();
465
- let mut lenv = LocalEnv::new();
466
- let mut changes = ChangeSet::new();
467
-
468
- for stmt in &program.statements().body() {
469
- crate::analyzer::install::install_node(&mut genv, &mut lenv, &mut changes, source, &stmt);
470
- }
471
-
472
- // def self.create should be registered as singleton method
473
- let info = genv
474
- .resolve_method(&Type::singleton("User"), "create")
475
- .expect("User.create should be registered as singleton method");
476
- assert!(info.return_vertex.is_some());
477
- }
478
-
479
- #[test]
480
- fn test_class_method_with_params() {
481
- let source = r#"
482
- class User
483
- def self.find(id)
484
- "user"
485
- end
486
- end
487
- "#;
488
- let session = ParseSession::new();
489
- let parse_result = session.parse_source(source, "test.rb").unwrap();
490
- let root = parse_result.node();
491
- let program = root.as_program_node().unwrap();
492
-
493
- let mut genv = GlobalEnv::new();
494
- let mut lenv = LocalEnv::new();
495
- let mut changes = ChangeSet::new();
496
-
497
- for stmt in &program.statements().body() {
498
- crate::analyzer::install::install_node(&mut genv, &mut lenv, &mut changes, source, &stmt);
499
- }
500
-
501
- let info = genv
502
- .resolve_method(&Type::singleton("User"), "find")
503
- .expect("User.find should be registered");
504
- assert!(info.return_vertex.is_some());
505
- assert_eq!(info.param_vertices.as_ref().unwrap().len(), 1);
506
- }
507
-
508
- #[test]
509
- fn test_class_method_in_qualified_namespace() {
510
- let source = r#"
511
- module Api
512
- class User
513
- def self.create
514
- "created"
515
- end
516
- end
517
- end
518
- "#;
519
- let session = ParseSession::new();
520
- let parse_result = session.parse_source(source, "test.rb").unwrap();
521
- let root = parse_result.node();
522
- let program = root.as_program_node().unwrap();
523
-
524
- let mut genv = GlobalEnv::new();
525
- let mut lenv = LocalEnv::new();
526
- let mut changes = ChangeSet::new();
527
-
528
- for stmt in &program.statements().body() {
529
- crate::analyzer::install::install_node(&mut genv, &mut lenv, &mut changes, source, &stmt);
530
- }
531
-
532
- let info = genv
533
- .resolve_method(&Type::singleton("Api::User"), "create")
534
- .expect("Api::User.create should be registered");
535
- assert!(info.return_vertex.is_some());
536
- }
537
-
538
- #[test]
539
- fn test_class_method_not_registered_as_instance() {
540
- let source = r#"
541
- class User
542
- def self.create
543
- "created"
544
- end
545
- end
546
- "#;
547
- let session = ParseSession::new();
548
- let parse_result = session.parse_source(source, "test.rb").unwrap();
549
- let root = parse_result.node();
550
- let program = root.as_program_node().unwrap();
551
-
552
- let mut genv = GlobalEnv::new();
553
- let mut lenv = LocalEnv::new();
554
- let mut changes = ChangeSet::new();
555
-
556
- for stmt in &program.statements().body() {
557
- crate::analyzer::install::install_node(&mut genv, &mut lenv, &mut changes, source, &stmt);
558
- }
559
-
560
- // def self.create should NOT be registered as instance method
561
- assert!(
562
- genv.resolve_method(&Type::instance("User"), "create").is_none(),
563
- "User#create should not exist — it's a class method"
564
- );
565
- }
566
-
567
- #[test]
568
- fn test_non_self_receiver_not_treated_as_class_method() {
569
- let source = r#"
570
- class User
571
- def other.foo
572
- "test"
573
- end
574
- end
575
- "#;
576
- let session = ParseSession::new();
577
- let parse_result = session.parse_source(source, "test.rb").unwrap();
578
- let root = parse_result.node();
579
- let program = root.as_program_node().unwrap();
580
-
581
- let mut genv = GlobalEnv::new();
582
- let mut lenv = LocalEnv::new();
583
- let mut changes = ChangeSet::new();
584
-
585
- for stmt in &program.statements().body() {
586
- crate::analyzer::install::install_node(&mut genv, &mut lenv, &mut changes, source, &stmt);
587
- }
588
-
589
- // def other.foo should NOT be registered as singleton method
590
- assert!(
591
- genv.resolve_method(&Type::singleton("User"), "foo").is_none(),
592
- "User.foo should not exist — receiver is not self"
593
- );
594
- }
595
-
596
- #[test]
597
- fn test_class_method_return_type_inference() {
598
- let source = r#"
599
- class User
600
- def self.create
601
- "created"
602
- end
603
- end
604
- "#;
605
- let session = ParseSession::new();
606
- let parse_result = session.parse_source(source, "test.rb").unwrap();
607
- let root = parse_result.node();
608
- let program = root.as_program_node().unwrap();
609
-
610
- let mut genv = GlobalEnv::new();
611
- let mut lenv = LocalEnv::new();
612
- let mut changes = ChangeSet::new();
613
-
614
- for stmt in &program.statements().body() {
615
- crate::analyzer::install::install_node(&mut genv, &mut lenv, &mut changes, source, &stmt);
616
- }
617
-
618
- let info = genv
619
- .resolve_method(&Type::singleton("User"), "create")
620
- .expect("User.create should be registered");
621
- let ret_vtx = info.return_vertex.expect("should have return vertex");
622
-
623
- // Run solver to propagate types
624
- genv.apply_changes(changes);
625
- genv.run_all();
626
-
627
- let vertex = genv.get_vertex(ret_vtx).or_else(|| {
628
- // return vertex might be a source
629
- None
630
- });
631
- if let Some(v) = vertex {
632
- assert_eq!(v.show(), "String");
633
- } else {
634
- // Check if it's a source
635
- let src = genv.get_source(ret_vtx).expect("should have source or vertex");
636
- assert_eq!(src.ty, Type::string());
637
- }
638
- }
639
-
640
- #[test]
641
- fn test_class_method_in_reopened_class() {
642
- let source = r#"
643
- class User
644
- def self.create
645
- "created"
646
- end
647
- end
648
-
649
- class User
650
- def self.destroy
651
- "destroyed"
652
- end
653
- end
654
- "#;
655
- let session = ParseSession::new();
656
- let parse_result = session.parse_source(source, "test.rb").unwrap();
657
- let root = parse_result.node();
658
- let program = root.as_program_node().unwrap();
659
-
660
- let mut genv = GlobalEnv::new();
661
- let mut lenv = LocalEnv::new();
662
- let mut changes = ChangeSet::new();
663
-
664
- for stmt in &program.statements().body() {
665
- crate::analyzer::install::install_node(&mut genv, &mut lenv, &mut changes, source, &stmt);
666
- }
667
-
668
- // Both class methods should be registered
669
- assert!(
670
- genv.resolve_method(&Type::singleton("User"), "create").is_some(),
671
- "User.create should be registered"
672
- );
673
- assert!(
674
- genv.resolve_method(&Type::singleton("User"), "destroy").is_some(),
675
- "User.destroy should be registered"
676
- );
677
- }
678
-
679
- #[test]
680
- fn test_class_method_param_type_propagation() {
681
- let source = r#"
682
- class User
683
- def self.find(id)
684
- id
685
- end
686
- end
687
-
688
- User.find(42)
689
- "#;
690
- let session = ParseSession::new();
691
- let parse_result = session.parse_source(source, "test.rb").unwrap();
692
- let root = parse_result.node();
693
- let program = root.as_program_node().unwrap();
694
-
695
- let mut genv = GlobalEnv::new();
696
- let mut lenv = LocalEnv::new();
697
- let mut changes = ChangeSet::new();
698
-
699
- for stmt in &program.statements().body() {
700
- crate::analyzer::install::install_node(&mut genv, &mut lenv, &mut changes, source, &stmt);
701
- }
702
-
703
- let info = genv
704
- .resolve_method(&Type::singleton("User"), "find")
705
- .expect("User.find should be registered");
706
- let param_vtxs = info.param_vertices.as_ref().expect("should have param vertices");
707
- assert_eq!(param_vtxs.len(), 1);
708
-
709
- let param_vtx = param_vtxs[0];
710
-
711
- // Run solver to propagate argument types
712
- genv.apply_changes(changes);
713
- genv.run_all();
714
-
715
- // Parameter should have Integer type propagated from call site
716
- let vertex = genv.get_vertex(param_vtx).expect("param vertex should exist");
717
- assert_eq!(vertex.show(), "Integer");
718
- }
719
- }