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,1137 +0,0 @@
1
- //! Node Dispatch - Dispatch AST nodes to appropriate handlers
2
- //!
3
- //! This module handles the pattern matching of Ruby AST nodes
4
- //! and dispatches them to specialized handlers.
5
-
6
- use std::collections::HashMap;
7
-
8
- use crate::env::{GlobalEnv, LocalEnv};
9
- use crate::graph::{BlockParameterTypeBox, ChangeSet, VertexId};
10
- use crate::source_map::SourceLocation;
11
- use crate::types::Type;
12
- use ruby_prism::Node;
13
-
14
- use super::bytes_to_name;
15
- use super::calls::install_method_call;
16
- use super::variables::{
17
- install_ivar_read, install_ivar_write, install_local_var_read, install_local_var_write,
18
- install_self,
19
- };
20
-
21
- /// Kind of attr_* declaration
22
- #[derive(Debug, Clone, Copy)]
23
- pub enum AttrKind {
24
- Reader,
25
- Writer,
26
- Accessor,
27
- }
28
-
29
- /// Result of dispatching a simple node (no child processing needed)
30
- pub enum DispatchResult {
31
- /// Node produced a vertex
32
- Vertex(VertexId),
33
- /// Node was not handled
34
- NotHandled,
35
- }
36
-
37
- /// Kind of child processing needed
38
- pub enum NeedsChildKind<'a> {
39
- /// Instance variable write: need to process value, then call finish_ivar_write
40
- IvarWrite { ivar_name: String, value: Node<'a> },
41
- /// Local variable write: need to process value, then call finish_local_var_write
42
- LocalVarWrite { var_name: String, value: Node<'a> },
43
- /// Method call: need to process receiver, then call finish_method_call
44
- MethodCall {
45
- receiver: Node<'a>,
46
- method_name: String,
47
- location: SourceLocation,
48
- /// Optional block attached to the method call
49
- block: Option<Node<'a>>,
50
- /// Arguments to the method call
51
- arguments: Vec<Node<'a>>,
52
- },
53
- /// Implicit self method call: method call without explicit receiver (implicit self)
54
- ImplicitSelfCall {
55
- method_name: String,
56
- location: SourceLocation,
57
- block: Option<Node<'a>>,
58
- arguments: Vec<Node<'a>>,
59
- },
60
- /// attr_reader / attr_writer / attr_accessor declaration
61
- AttrDeclaration {
62
- kind: AttrKind,
63
- attr_names: Vec<String>,
64
- },
65
- }
66
-
67
- /// First pass: check if node can be handled immediately without child processing
68
- ///
69
- /// Note: Literals (including Array) are handled in install.rs via install_literal
70
- /// because Array literals need child processing for element type inference.
71
- pub fn dispatch_simple(genv: &mut GlobalEnv, lenv: &mut LocalEnv, node: &Node) -> DispatchResult {
72
- // Instance variable read: @name
73
- if let Some(ivar_read) = node.as_instance_variable_read_node() {
74
- let ivar_name = bytes_to_name(ivar_read.name().as_slice());
75
- return match install_ivar_read(genv, &ivar_name) {
76
- Some(vtx) => DispatchResult::Vertex(vtx),
77
- None => DispatchResult::NotHandled,
78
- };
79
- }
80
-
81
- // self
82
- if node.as_self_node().is_some() {
83
- return DispatchResult::Vertex(install_self(genv));
84
- }
85
-
86
- // Local variable read: x
87
- if let Some(read_node) = node.as_local_variable_read_node() {
88
- let var_name = bytes_to_name(read_node.name().as_slice());
89
- return match install_local_var_read(lenv, &var_name) {
90
- Some(vtx) => DispatchResult::Vertex(vtx),
91
- None => DispatchResult::NotHandled,
92
- };
93
- }
94
-
95
- // ConstantReadNode: User → Type::Singleton("User") or Type::Singleton("Api::User")
96
- if let Some(const_read) = node.as_constant_read_node() {
97
- let name = bytes_to_name(const_read.name().as_slice());
98
- let resolved_name = genv.scope_manager.lookup_constant(&name)
99
- .unwrap_or(name);
100
- let vtx = genv.new_source(Type::singleton(&resolved_name));
101
- return DispatchResult::Vertex(vtx);
102
- }
103
-
104
- // ConstantPathNode: Api::User → Type::Singleton("Api::User")
105
- if node.as_constant_path_node().is_some() {
106
- if let Some(name) = super::definitions::extract_constant_path(node) {
107
- let vtx = genv.new_source(Type::singleton(&name));
108
- return DispatchResult::Vertex(vtx);
109
- }
110
- }
111
-
112
- DispatchResult::NotHandled
113
- }
114
-
115
- /// Extract symbol names from attr_* arguments (e.g., `attr_reader :name, :email`)
116
- fn extract_symbol_names(call_node: &ruby_prism::CallNode) -> Vec<String> {
117
- call_node
118
- .arguments()
119
- .map(|args| {
120
- args.arguments()
121
- .iter()
122
- .filter_map(|arg| {
123
- arg.as_symbol_node().map(|sym| {
124
- bytes_to_name(sym.unescaped())
125
- })
126
- })
127
- .collect()
128
- })
129
- .unwrap_or_default()
130
- }
131
-
132
- /// Check if node needs child processing
133
- pub fn dispatch_needs_child<'a>(node: &Node<'a>, source: &str) -> Option<NeedsChildKind<'a>> {
134
- // Instance variable write: @name = value
135
- if let Some(ivar_write) = node.as_instance_variable_write_node() {
136
- let ivar_name = bytes_to_name(ivar_write.name().as_slice());
137
- return Some(NeedsChildKind::IvarWrite {
138
- ivar_name,
139
- value: ivar_write.value(),
140
- });
141
- }
142
-
143
- // Local variable write: x = value
144
- if let Some(write_node) = node.as_local_variable_write_node() {
145
- let var_name = bytes_to_name(write_node.name().as_slice());
146
- return Some(NeedsChildKind::LocalVarWrite {
147
- var_name,
148
- value: write_node.value(),
149
- });
150
- }
151
-
152
- // Method call: x.upcase, x.each { |i| ... }, or name (implicit self)
153
- if let Some(call_node) = node.as_call_node() {
154
- let method_name = bytes_to_name(call_node.name().as_slice());
155
- let block = call_node.block();
156
- let arguments: Vec<Node<'a>> = call_node
157
- .arguments()
158
- .map(|args| args.arguments().iter().collect())
159
- .unwrap_or_default();
160
-
161
- if let Some(receiver) = call_node.receiver() {
162
- // Explicit receiver: x.upcase, x.each { |i| ... }
163
- let prism_location = call_node
164
- .call_operator_loc()
165
- .unwrap_or_else(|| node.location());
166
- let location =
167
- SourceLocation::from_prism_location_with_source(&prism_location, source);
168
-
169
- return Some(NeedsChildKind::MethodCall {
170
- receiver,
171
- method_name,
172
- location,
173
- block,
174
- arguments,
175
- });
176
- } else {
177
- // No receiver: implicit self method call (e.g., `name`, `puts "hello"`)
178
-
179
- if let Some(kind) = match method_name.as_str() {
180
- "attr_reader" => Some(AttrKind::Reader),
181
- "attr_writer" => Some(AttrKind::Writer),
182
- "attr_accessor" => Some(AttrKind::Accessor),
183
- _ => None,
184
- } {
185
- let attr_names = extract_symbol_names(&call_node);
186
- if !attr_names.is_empty() {
187
- return Some(NeedsChildKind::AttrDeclaration { kind, attr_names });
188
- }
189
- return None;
190
- }
191
-
192
- let prism_location = call_node
193
- .message_loc()
194
- .unwrap_or_else(|| node.location());
195
- let location =
196
- SourceLocation::from_prism_location_with_source(&prism_location, source);
197
-
198
- return Some(NeedsChildKind::ImplicitSelfCall {
199
- method_name,
200
- location,
201
- block,
202
- arguments,
203
- });
204
- }
205
- }
206
-
207
- None
208
- }
209
-
210
- /// Process a node that needs child processing
211
- ///
212
- /// This function handles the second phase of two-phase dispatch:
213
- /// 1. `dispatch_needs_child` identifies the node kind and extracts data
214
- /// 2. `process_needs_child` processes child nodes and completes the operation
215
- pub(crate) fn process_needs_child(
216
- genv: &mut GlobalEnv,
217
- lenv: &mut LocalEnv,
218
- changes: &mut ChangeSet,
219
- source: &str,
220
- kind: NeedsChildKind,
221
- ) -> Option<VertexId> {
222
- match kind {
223
- NeedsChildKind::IvarWrite { ivar_name, value } => {
224
- let value_vtx = super::install::install_node(genv, lenv, changes, source, &value)?;
225
- Some(finish_ivar_write(genv, ivar_name, value_vtx))
226
- }
227
- NeedsChildKind::LocalVarWrite { var_name, value } => {
228
- let value_vtx = super::install::install_node(genv, lenv, changes, source, &value)?;
229
- Some(finish_local_var_write(genv, lenv, changes, var_name, value_vtx))
230
- }
231
- NeedsChildKind::MethodCall {
232
- receiver,
233
- method_name,
234
- location,
235
- block,
236
- arguments,
237
- } => {
238
- let recv_vtx = super::install::install_node(genv, lenv, changes, source, &receiver)?;
239
- process_method_call_common(
240
- genv, lenv, changes, source,
241
- MethodCallContext { recv_vtx, method_name, location, block, arguments },
242
- )
243
- }
244
- NeedsChildKind::ImplicitSelfCall {
245
- method_name,
246
- location,
247
- block,
248
- arguments,
249
- } => {
250
- // Use qualified name to match method registration in definitions.rs
251
- let recv_vtx = if let Some(name) = genv.scope_manager.current_qualified_name() {
252
- genv.new_source(Type::instance(&name))
253
- } else {
254
- genv.new_source(Type::instance("Object"))
255
- };
256
- process_method_call_common(
257
- genv, lenv, changes, source,
258
- MethodCallContext { recv_vtx, method_name, location, block, arguments },
259
- )
260
- }
261
- NeedsChildKind::AttrDeclaration { kind, attr_names } => {
262
- super::attributes::process_attr_declaration(genv, kind, attr_names);
263
- None
264
- }
265
- }
266
- }
267
-
268
- /// Finish instance variable write after child is processed
269
- fn finish_ivar_write(genv: &mut GlobalEnv, ivar_name: String, value_vtx: VertexId) -> VertexId {
270
- install_ivar_write(genv, ivar_name, value_vtx)
271
- }
272
-
273
- /// Finish local variable write after child is processed
274
- fn finish_local_var_write(
275
- genv: &mut GlobalEnv,
276
- lenv: &mut LocalEnv,
277
- changes: &mut ChangeSet,
278
- var_name: String,
279
- value_vtx: VertexId,
280
- ) -> VertexId {
281
- install_local_var_write(genv, lenv, changes, var_name, value_vtx)
282
- }
283
-
284
- /// Bundled parameters for method call processing
285
- struct MethodCallContext<'a> {
286
- recv_vtx: VertexId,
287
- method_name: String,
288
- location: SourceLocation,
289
- block: Option<Node<'a>>,
290
- arguments: Vec<Node<'a>>,
291
- }
292
-
293
- /// MethodCall / ImplicitSelfCall common processing:
294
- /// Handles argument processing, block processing, and MethodCallBox creation after recv_vtx is obtained
295
- fn process_method_call_common<'a>(
296
- genv: &mut GlobalEnv,
297
- lenv: &mut LocalEnv,
298
- changes: &mut ChangeSet,
299
- source: &str,
300
- ctx: MethodCallContext<'a>,
301
- ) -> Option<VertexId> {
302
- let MethodCallContext {
303
- recv_vtx,
304
- method_name,
305
- location,
306
- block,
307
- arguments,
308
- } = ctx;
309
- if method_name == "!" {
310
- return Some(super::operators::process_not_operator(genv));
311
- }
312
-
313
- // Separate positional arguments and keyword arguments
314
- let mut positional_arg_vtxs: Vec<VertexId> = Vec::new();
315
- let mut keyword_arg_vtxs: HashMap<String, VertexId> = HashMap::new();
316
-
317
- for arg in &arguments {
318
- if let Some(kw_hash) = arg.as_keyword_hash_node() {
319
- for element in kw_hash.elements().iter() {
320
- let assoc = match element.as_assoc_node() {
321
- Some(a) => a,
322
- None => continue,
323
- };
324
- let name = match assoc.key().as_symbol_node() {
325
- Some(sym) => bytes_to_name(sym.unescaped()),
326
- None => continue,
327
- };
328
- if let Some(vtx) =
329
- super::install::install_node(genv, lenv, changes, source, &assoc.value())
330
- {
331
- keyword_arg_vtxs.insert(name, vtx);
332
- }
333
- }
334
- } else if let Some(vtx) = super::install::install_node(genv, lenv, changes, source, arg) {
335
- positional_arg_vtxs.push(vtx);
336
- }
337
- }
338
-
339
- if let Some(block_node) = block {
340
- if let Some(block) = block_node.as_block_node() {
341
- let param_vtxs = super::blocks::process_block_node_with_params(
342
- genv, lenv, changes, source, &block,
343
- );
344
-
345
- if !param_vtxs.is_empty() {
346
- let box_id = genv.alloc_box_id();
347
- let block_box = BlockParameterTypeBox::new(
348
- box_id,
349
- recv_vtx,
350
- method_name.clone(),
351
- param_vtxs,
352
- );
353
- genv.register_box(box_id, Box::new(block_box));
354
- }
355
- }
356
- }
357
-
358
- let kwarg_vtxs = if keyword_arg_vtxs.is_empty() {
359
- None
360
- } else {
361
- Some(keyword_arg_vtxs)
362
- };
363
-
364
- Some(finish_method_call(
365
- genv,
366
- recv_vtx,
367
- method_name,
368
- positional_arg_vtxs,
369
- kwarg_vtxs,
370
- location,
371
- ))
372
- }
373
-
374
- /// Finish method call after receiver is processed
375
- fn finish_method_call(
376
- genv: &mut GlobalEnv,
377
- recv_vtx: VertexId,
378
- method_name: String,
379
- arg_vtxs: Vec<VertexId>,
380
- kwarg_vtxs: Option<HashMap<String, VertexId>>,
381
- location: SourceLocation,
382
- ) -> VertexId {
383
- install_method_call(genv, recv_vtx, method_name, arg_vtxs, kwarg_vtxs, Some(location))
384
- }
385
-
386
- #[cfg(test)]
387
- mod tests {
388
- use super::*;
389
- use crate::analyzer::install::AstInstaller;
390
- use crate::parser::ParseSession;
391
-
392
- /// Helper: parse Ruby source, process with AstInstaller, and return GlobalEnv
393
- fn analyze(source: &str) -> GlobalEnv {
394
- let session = ParseSession::new();
395
- let parse_result = session.parse_source(source, "test.rb").unwrap();
396
- let root = parse_result.node();
397
- let program = root.as_program_node().unwrap();
398
-
399
- let mut genv = GlobalEnv::new();
400
- let mut lenv = LocalEnv::new();
401
-
402
- let mut installer = AstInstaller::new(&mut genv, &mut lenv, source);
403
- for stmt in &program.statements().body() {
404
- installer.install_node(&stmt);
405
- }
406
- installer.finish();
407
-
408
- genv
409
- }
410
-
411
- /// Helper: get the type string for a vertex ID (checks both Vertex and Source)
412
- fn get_type_show(genv: &GlobalEnv, vtx: VertexId) -> String {
413
- if let Some(vertex) = genv.get_vertex(vtx) {
414
- vertex.show()
415
- } else if let Some(source) = genv.get_source(vtx) {
416
- source.ty.show()
417
- } else {
418
- panic!("vertex {:?} not found as either Vertex or Source", vtx);
419
- }
420
- }
421
-
422
- // Test 1: Receiverless method call type resolution
423
- #[test]
424
- fn test_implicit_self_call_type_resolution() {
425
- let source = r#"
426
- class User
427
- def name
428
- "Alice"
429
- end
430
-
431
- def greet
432
- name
433
- end
434
- end
435
- "#;
436
- let genv = analyze(source);
437
-
438
- // User#greet should resolve to String via User#name
439
- let info = genv
440
- .resolve_method(&Type::instance("User"), "greet")
441
- .expect("User#greet should be registered");
442
- assert!(info.return_vertex.is_some());
443
-
444
- let ret_vtx = info.return_vertex.unwrap();
445
- assert_eq!(get_type_show(&genv, ret_vtx), "String");
446
- }
447
-
448
- // Test 2: Receiverless method call with arguments
449
- #[test]
450
- fn test_implicit_self_call_with_arguments() {
451
- let source = r#"
452
- class Calculator
453
- def add(x, y)
454
- x
455
- end
456
-
457
- def compute
458
- add(1, 2)
459
- end
460
- end
461
- "#;
462
- let genv = analyze(source);
463
-
464
- // Calculator#compute should resolve via Calculator#add
465
- let info = genv
466
- .resolve_method(&Type::instance("Calculator"), "compute")
467
- .expect("Calculator#compute should be registered");
468
- assert!(info.return_vertex.is_some());
469
-
470
- let ret_vtx = info.return_vertex.unwrap();
471
- assert_eq!(get_type_show(&genv, ret_vtx), "Integer");
472
- }
473
-
474
- // Test 3: Receiverless call in nested class
475
- #[test]
476
- fn test_implicit_self_call_in_nested_class() {
477
- let source = r#"
478
- module Api
479
- module V1
480
- class User
481
- def name
482
- "Alice"
483
- end
484
-
485
- def greet
486
- name
487
- end
488
- end
489
- end
490
- end
491
- "#;
492
- let genv = analyze(source);
493
-
494
- // Method registered with qualified name "Api::V1::User"
495
- let info = genv
496
- .resolve_method(&Type::instance("Api::V1::User"), "greet")
497
- .expect("Api::V1::User#greet should be registered");
498
- assert!(info.return_vertex.is_some());
499
-
500
- let ret_vtx = info.return_vertex.unwrap();
501
- assert_eq!(get_type_show(&genv, ret_vtx), "String");
502
- }
503
-
504
- // Test 4: Receiverless call in module
505
- #[test]
506
- fn test_implicit_self_call_in_module() {
507
- let source = r#"
508
- module Utils
509
- def self.format(value)
510
- value
511
- end
512
-
513
- def self.run
514
- format("test")
515
- end
516
- end
517
- "#;
518
- let genv = analyze(source);
519
-
520
- // Utils.run should be registered
521
- let info = genv
522
- .resolve_method(&Type::singleton("Utils"), "run")
523
- .expect("Utils.run should be registered");
524
- assert!(info.return_vertex.is_some());
525
- }
526
-
527
- // Test 5: Receiverless call from within block
528
- #[test]
529
- fn test_implicit_self_call_from_block() {
530
- let source = r#"
531
- class User
532
- def name
533
- "Alice"
534
- end
535
-
536
- def greet
537
- [1].each { name }
538
- end
539
- end
540
- "#;
541
- let genv = analyze(source);
542
-
543
- // User#name should be registered and resolve to String
544
- let name_info = genv
545
- .resolve_method(&Type::instance("User"), "name")
546
- .expect("User#name should be registered");
547
- assert!(name_info.return_vertex.is_some());
548
- assert_eq!(get_type_show(&genv, name_info.return_vertex.unwrap()), "String");
549
-
550
- // User#greet should also be registered (block contains implicit self call)
551
- let greet_info = genv
552
- .resolve_method(&Type::instance("User"), "greet")
553
- .expect("User#greet should be registered");
554
- assert!(greet_info.return_vertex.is_some());
555
- }
556
-
557
- // Test 6: Top-level receiverless call (Object receiver)
558
- #[test]
559
- fn test_implicit_self_call_at_top_level() {
560
- let source = r#"
561
- def helper
562
- "result"
563
- end
564
-
565
- helper
566
- "#;
567
- let genv = analyze(source);
568
-
569
- // Should not panic; top-level call uses Object as receiver type
570
- // NOTE: top-level def is not registered in method_registry yet,
571
- // so this will produce a type error (false positive).
572
- // The important thing is that it doesn't panic.
573
- // No panic is the real assertion - top-level call should be processed without error
574
- let _ = genv;
575
- }
576
-
577
- // Test 7: attr_reader basic — getter type resolution
578
- #[test]
579
- fn test_attr_reader_basic() {
580
- let source = r#"
581
- class User
582
- attr_reader :name
583
-
584
- def initialize
585
- @name = "Alice"
586
- end
587
- end
588
- "#;
589
- let genv = analyze(source);
590
-
591
- // User#name should be registered and resolve to String via @name
592
- let info = genv
593
- .resolve_method(&Type::instance("User"), "name")
594
- .expect("User#name should be registered");
595
- assert!(info.return_vertex.is_some());
596
- let ret_vtx = info.return_vertex.unwrap();
597
- assert_eq!(get_type_show(&genv, ret_vtx), "String");
598
-
599
- // Other methods still work
600
- let greet_src = r#"
601
- class User
602
- attr_reader :name
603
-
604
- def greet
605
- "hello"
606
- end
607
- end
608
- "#;
609
- let genv2 = analyze(greet_src);
610
- let info2 = genv2
611
- .resolve_method(&Type::instance("User"), "greet")
612
- .expect("User#greet should be registered");
613
- assert_eq!(get_type_show(&genv2, info2.return_vertex.unwrap()), "String");
614
- }
615
-
616
- // Test 8: attr_reader + self.name method call
617
- #[test]
618
- fn test_attr_reader_with_self_call() {
619
- let source = r#"
620
- class User
621
- attr_reader :name
622
-
623
- def initialize
624
- @name = "Alice"
625
- end
626
-
627
- def greet
628
- self.name
629
- end
630
- end
631
- "#;
632
- let genv = analyze(source);
633
-
634
- // User#greet should resolve to String via User#name → @name
635
- let info = genv
636
- .resolve_method(&Type::instance("User"), "greet")
637
- .expect("User#greet 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
-
643
- // Test 9: attr_reader + receiverless name call (proposal D integration)
644
- #[test]
645
- fn test_attr_reader_receiverless_call() {
646
- let source = r#"
647
- class User
648
- attr_reader :name
649
-
650
- def initialize
651
- @name = "Alice"
652
- end
653
-
654
- def greet
655
- name
656
- end
657
- end
658
- "#;
659
- let genv = analyze(source);
660
-
661
- // User#greet should resolve to String via implicit self → User#name → @name
662
- let info = genv
663
- .resolve_method(&Type::instance("User"), "greet")
664
- .expect("User#greet should be registered");
665
- assert!(info.return_vertex.is_some());
666
- let ret_vtx = info.return_vertex.unwrap();
667
- assert_eq!(get_type_show(&genv, ret_vtx), "String");
668
- }
669
-
670
- // Test 10: attr_accessor — both getter and setter
671
- #[test]
672
- fn test_attr_accessor() {
673
- let source = r#"
674
- class User
675
- attr_accessor :age
676
-
677
- def initialize
678
- @age = 30
679
- end
680
- end
681
- "#;
682
- let genv = analyze(source);
683
-
684
- // User#age (getter) should be registered and resolve to Integer
685
- let getter = genv
686
- .resolve_method(&Type::instance("User"), "age")
687
- .expect("User#age getter should be registered");
688
- assert!(getter.return_vertex.is_some());
689
- assert_eq!(get_type_show(&genv, getter.return_vertex.unwrap()), "Integer");
690
-
691
- // User#age= (setter) should also be registered
692
- let setter = genv
693
- .resolve_method(&Type::instance("User"), "age=")
694
- .expect("User#age= setter should be registered");
695
- assert!(setter.return_vertex.is_some());
696
- }
697
-
698
- // Test 11: multiple attributes in single declaration
699
- #[test]
700
- fn test_attr_reader_multiple() {
701
- let source = r#"
702
- class User
703
- attr_reader :name, :email
704
-
705
- def initialize
706
- @name = "Alice"
707
- @email = "alice@test.com"
708
- end
709
- end
710
- "#;
711
- let genv = analyze(source);
712
-
713
- let name_info = genv
714
- .resolve_method(&Type::instance("User"), "name")
715
- .expect("User#name should be registered");
716
- assert_eq!(get_type_show(&genv, name_info.return_vertex.unwrap()), "String");
717
-
718
- let email_info = genv
719
- .resolve_method(&Type::instance("User"), "email")
720
- .expect("User#email should be registered");
721
- assert_eq!(get_type_show(&genv, email_info.return_vertex.unwrap()), "String");
722
- }
723
-
724
- // Test 12: attr_reader in nested class
725
- #[test]
726
- fn test_attr_reader_nested_class() {
727
- let source = r#"
728
- module Api
729
- class User
730
- attr_reader :name
731
-
732
- def initialize
733
- @name = "Alice"
734
- end
735
- end
736
- end
737
- "#;
738
- let genv = analyze(source);
739
-
740
- // Registered with qualified name "Api::User"
741
- let info = genv
742
- .resolve_method(&Type::instance("Api::User"), "name")
743
- .expect("Api::User#name should be registered");
744
- assert!(info.return_vertex.is_some());
745
- assert_eq!(get_type_show(&genv, info.return_vertex.unwrap()), "String");
746
- }
747
-
748
- // Test 13: attr_writer only — setter registered, getter not
749
- #[test]
750
- fn test_attr_writer_only() {
751
- let source = r#"
752
- class User
753
- attr_writer :name
754
- end
755
- "#;
756
- let genv = analyze(source);
757
-
758
- // User#name= should be registered
759
- let setter = genv.resolve_method(&Type::instance("User"), "name=");
760
- assert!(setter.is_some(), "User#name= should be registered");
761
-
762
- // User#name (getter) should NOT be registered
763
- let getter = genv.resolve_method(&Type::instance("User"), "name");
764
- assert!(getter.is_none(), "User#name getter should NOT be registered for attr_writer");
765
- }
766
-
767
- // Test 14: attr_reader with no assignment (empty vertex)
768
- #[test]
769
- fn test_attr_reader_unassigned() {
770
- let source = r#"
771
- class User
772
- attr_reader :unknown
773
- end
774
- "#;
775
- let genv = analyze(source);
776
-
777
- // User#unknown should be registered (with empty vertex)
778
- let info = genv
779
- .resolve_method(&Type::instance("User"), "unknown")
780
- .expect("User#unknown should be registered");
781
- assert!(info.return_vertex.is_some());
782
- }
783
-
784
- // Test 15: super call independence (SuperNode is not CallNode)
785
- #[test]
786
- fn test_super_call_independence() {
787
- let source = r#"
788
- class Base
789
- def greet
790
- "hello"
791
- end
792
- end
793
-
794
- class Child < Base
795
- def greet
796
- super
797
- end
798
- end
799
- "#;
800
- let genv = analyze(source);
801
-
802
- // super is a SuperNode, not a CallNode, so ImplicitSelfCall should not be triggered.
803
- // Base#greet should still work.
804
- let info = genv
805
- .resolve_method(&Type::instance("Base"), "greet")
806
- .expect("Base#greet should be registered");
807
- assert!(info.return_vertex.is_some());
808
-
809
- let ret_vtx = info.return_vertex.unwrap();
810
- assert_eq!(get_type_show(&genv, ret_vtx), "String");
811
- }
812
-
813
- // Test 16: User.new → instance(User)
814
- #[test]
815
- fn test_constant_read_user_new() {
816
- let source = r#"
817
- class User
818
- def name
819
- "Alice"
820
- end
821
- end
822
-
823
- x = User.new
824
- "#;
825
- let genv = analyze(source);
826
- assert!(
827
- genv.type_errors.is_empty(),
828
- "User.new should not produce type errors: {:?}",
829
- genv.type_errors
830
- );
831
- }
832
-
833
- // Test 17: User.new.name → String
834
- #[test]
835
- fn test_constant_read_user_new_method_chain() {
836
- let source = r#"
837
- class User
838
- def name
839
- "Alice"
840
- end
841
- end
842
-
843
- x = User.new.name
844
- "#;
845
- let genv = analyze(source);
846
- assert!(
847
- genv.type_errors.is_empty(),
848
- "User.new.name should not produce type errors: {:?}",
849
- genv.type_errors
850
- );
851
- }
852
-
853
- // Test 18: Api::User.new → instance(Api::User) (ConstantPathNode)
854
- #[test]
855
- fn test_constant_path_qualified_new() {
856
- let source = r#"
857
- class Api::User
858
- def name
859
- "Alice"
860
- end
861
- end
862
-
863
- x = Api::User.new
864
- "#;
865
- let genv = analyze(source);
866
- assert!(
867
- genv.type_errors.is_empty(),
868
- "Api::User.new should not produce type errors: {:?}",
869
- genv.type_errors
870
- );
871
- }
872
-
873
- // Test 19: User.new("Alice") → initialize parameter propagation
874
- #[test]
875
- fn test_constant_read_new_with_initialize_params() {
876
- let source = r#"
877
- class User
878
- def initialize(name)
879
- @name = name
880
- end
881
- end
882
-
883
- x = User.new("Alice")
884
- "#;
885
- let genv = analyze(source);
886
- assert!(genv.type_errors.is_empty());
887
- }
888
-
889
- // Test 20: user = User.new; user.name → String
890
- #[test]
891
- fn test_constant_read_assign_and_call() {
892
- let source = r#"
893
- class User
894
- def name
895
- "Alice"
896
- end
897
- end
898
-
899
- user = User.new
900
- user.name
901
- "#;
902
- let genv = analyze(source);
903
- assert!(
904
- genv.type_errors.is_empty(),
905
- "user = User.new; user.name should not produce type errors: {:?}",
906
- genv.type_errors
907
- );
908
- }
909
-
910
- // Test 21: User.some_method should not produce type error (Singleton error suppression)
911
- #[test]
912
- fn test_constant_read_no_false_positive() {
913
- let source = r#"
914
- class User
915
- def name
916
- "Alice"
917
- end
918
- end
919
-
920
- User.some_method
921
- "#;
922
- let genv = analyze(source);
923
- assert!(
924
- genv.type_errors.is_empty(),
925
- "User.some_method should not produce type errors (Singleton suppression): {:?}",
926
- genv.type_errors
927
- );
928
- }
929
-
930
- // Test: ConstantPathNode.new resolves with qualified method name
931
- #[test]
932
- fn test_constant_path_new_with_qualified_method() {
933
- let source = r#"
934
- module Api
935
- class User
936
- def name
937
- "Alice"
938
- end
939
- end
940
- end
941
-
942
- Api::User.new.name
943
- "#;
944
- let genv = analyze(source);
945
- // Api::User.new.name should resolve correctly — no type errors
946
- assert!(
947
- genv.type_errors.is_empty(),
948
- "Api::User.new.name should not produce type errors: {:?}",
949
- genv.type_errors
950
- );
951
- }
952
-
953
- // Test 23: ConstantReadNode inside module resolves to qualified name
954
- #[test]
955
- fn test_constant_read_inside_module_resolves_qualified() {
956
- let source = r#"
957
- module Api
958
- class User
959
- def name
960
- "Alice"
961
- end
962
- end
963
-
964
- class Service
965
- def run
966
- User.new.name
967
- end
968
- end
969
- end
970
- "#;
971
- let genv = analyze(source);
972
- assert!(
973
- genv.type_errors.is_empty(),
974
- "User.new inside module Api should resolve to Api::User: {:?}",
975
- genv.type_errors
976
- );
977
- }
978
-
979
- // Test 24: ConstantReadNode in deeply nested modules
980
- #[test]
981
- fn test_constant_read_deeply_nested() {
982
- let source = r#"
983
- module Api
984
- module V1
985
- class User
986
- def name
987
- "Alice"
988
- end
989
- end
990
-
991
- class Service
992
- def run
993
- User.new.name
994
- end
995
- end
996
- end
997
- end
998
- "#;
999
- let genv = analyze(source);
1000
- assert!(
1001
- genv.type_errors.is_empty(),
1002
- "User.new inside Api::V1 should resolve to Api::V1::User: {:?}",
1003
- genv.type_errors
1004
- );
1005
- }
1006
-
1007
- // Test 25: Same constant name in different modules
1008
- #[test]
1009
- fn test_constant_read_same_name_different_modules() {
1010
- let source = r#"
1011
- module Api
1012
- class User
1013
- def name; "Api User"; end
1014
- end
1015
- end
1016
-
1017
- module Admin
1018
- class User
1019
- def name; "Admin User"; end
1020
- end
1021
-
1022
- class Service
1023
- def run
1024
- User.new.name
1025
- end
1026
- end
1027
- end
1028
- "#;
1029
- let genv = analyze(source);
1030
- assert!(
1031
- genv.type_errors.is_empty(),
1032
- "User.new inside Admin should resolve to Admin::User: {:?}",
1033
- genv.type_errors
1034
- );
1035
- }
1036
-
1037
- // === Keyword argument tests ===
1038
-
1039
- // Test 26: Required keyword argument type propagation
1040
- #[test]
1041
- fn test_keyword_arg_required_propagation() {
1042
- let source = r#"
1043
- class Greeter
1044
- def greet(name:)
1045
- name
1046
- end
1047
- end
1048
-
1049
- Greeter.new.greet(name: "Alice")
1050
- "#;
1051
- let genv = analyze(source);
1052
-
1053
- let info = genv
1054
- .resolve_method(&Type::instance("Greeter"), "greet")
1055
- .expect("Greeter#greet should be registered");
1056
- let ret_vtx = info.return_vertex.unwrap();
1057
- assert_eq!(get_type_show(&genv, ret_vtx), "String");
1058
- }
1059
-
1060
- // Test 27: Optional keyword argument with default type
1061
- #[test]
1062
- fn test_keyword_arg_optional_default_type() {
1063
- let source = r#"
1064
- class Counter
1065
- def count(step: 1)
1066
- step
1067
- end
1068
- end
1069
- "#;
1070
- let genv = analyze(source);
1071
-
1072
- let info = genv
1073
- .resolve_method(&Type::instance("Counter"), "count")
1074
- .expect("Counter#count should be registered");
1075
- let ret_vtx = info.return_vertex.unwrap();
1076
- // step has Integer type from default value
1077
- assert_eq!(get_type_show(&genv, ret_vtx), "Integer");
1078
- }
1079
-
1080
- // Test 28: Positional and keyword arguments mixed
1081
- #[test]
1082
- fn test_positional_and_keyword_mixed() {
1083
- let source = r#"
1084
- class User
1085
- def initialize(id, name:)
1086
- @id = id
1087
- @name = name
1088
- end
1089
- end
1090
-
1091
- User.new(1, name: "Alice")
1092
- "#;
1093
- let genv = analyze(source);
1094
- assert!(genv.type_errors.is_empty());
1095
- }
1096
-
1097
- // Test 29: Keyword argument via .new propagation to initialize
1098
- #[test]
1099
- fn test_keyword_arg_via_new_to_initialize() {
1100
- let source = r#"
1101
- class Config
1102
- def initialize(debug:)
1103
- @debug = debug
1104
- end
1105
-
1106
- def debug?
1107
- @debug
1108
- end
1109
- end
1110
-
1111
- Config.new(debug: true)
1112
- "#;
1113
- let genv = analyze(source);
1114
- assert!(genv.type_errors.is_empty());
1115
- }
1116
-
1117
- // Test 30: Multiple keyword arguments
1118
- #[test]
1119
- fn test_multiple_keyword_args() {
1120
- let source = r#"
1121
- class User
1122
- def profile(name:, age:)
1123
- name
1124
- end
1125
- end
1126
-
1127
- User.new.profile(name: "Alice", age: 30)
1128
- "#;
1129
- let genv = analyze(source);
1130
-
1131
- let info = genv
1132
- .resolve_method(&Type::instance("User"), "profile")
1133
- .expect("User#profile should be registered");
1134
- let ret_vtx = info.return_vertex.unwrap();
1135
- assert_eq!(get_type_show(&genv, ret_vtx), "String");
1136
- }
1137
- }