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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +38 -0
- data/README.md +9 -11
- data/{rust → core}/Cargo.toml +1 -1
- data/core/src/analyzer/assignments.rs +219 -0
- data/{rust → core}/src/analyzer/blocks.rs +0 -50
- data/{rust → core}/src/analyzer/calls.rs +3 -32
- data/core/src/analyzer/conditionals.rs +190 -0
- data/core/src/analyzer/definitions.rs +205 -0
- data/core/src/analyzer/dispatch.rs +455 -0
- data/core/src/analyzer/exceptions.rs +168 -0
- data/{rust → core}/src/analyzer/install.rs +16 -1
- data/{rust → core}/src/analyzer/literals.rs +3 -71
- data/core/src/analyzer/loops.rs +94 -0
- data/{rust → core}/src/analyzer/mod.rs +1 -15
- data/core/src/analyzer/operators.rs +79 -0
- data/{rust → core}/src/analyzer/parameters.rs +4 -67
- data/core/src/analyzer/parentheses.rs +25 -0
- data/core/src/analyzer/returns.rs +39 -0
- data/core/src/analyzer/super_calls.rs +74 -0
- data/{rust → core}/src/analyzer/variables.rs +5 -25
- data/{rust → core}/src/checker.rs +0 -13
- data/{rust → core}/src/diagnostics/diagnostic.rs +0 -41
- data/{rust → core}/src/diagnostics/formatter.rs +0 -38
- data/{rust → core}/src/env/box_manager.rs +0 -30
- data/{rust → core}/src/env/global_env.rs +67 -80
- data/core/src/env/local_env.rs +42 -0
- data/core/src/env/method_registry.rs +173 -0
- data/core/src/env/scope.rs +299 -0
- data/{rust → core}/src/env/vertex_manager.rs +0 -73
- data/core/src/graph/box.rs +347 -0
- data/{rust → core}/src/graph/change_set.rs +0 -65
- data/{rust → core}/src/graph/vertex.rs +0 -69
- data/{rust → core}/src/parser.rs +0 -77
- data/{rust → core}/src/types.rs +11 -0
- data/ext/Cargo.toml +2 -2
- data/lib/methodray/binary_locator.rb +2 -2
- data/lib/methodray/commands.rb +1 -1
- data/lib/methodray/version.rb +1 -1
- metadata +58 -56
- data/rust/src/analyzer/assignments.rs +0 -152
- data/rust/src/analyzer/conditionals.rs +0 -538
- data/rust/src/analyzer/definitions.rs +0 -719
- data/rust/src/analyzer/dispatch.rs +0 -1137
- data/rust/src/analyzer/exceptions.rs +0 -521
- data/rust/src/analyzer/loops.rs +0 -176
- data/rust/src/analyzer/operators.rs +0 -284
- data/rust/src/analyzer/parentheses.rs +0 -113
- data/rust/src/analyzer/returns.rs +0 -191
- data/rust/src/env/local_env.rs +0 -92
- data/rust/src/env/method_registry.rs +0 -268
- data/rust/src/env/scope.rs +0 -596
- data/rust/src/graph/box.rs +0 -766
- /data/{rust → core}/src/analyzer/attributes.rs +0 -0
- /data/{rust → core}/src/cache/mod.rs +0 -0
- /data/{rust → core}/src/cache/rbs_cache.rs +0 -0
- /data/{rust → core}/src/cli/args.rs +0 -0
- /data/{rust → core}/src/cli/commands.rs +0 -0
- /data/{rust → core}/src/cli/mod.rs +0 -0
- /data/{rust → core}/src/diagnostics/mod.rs +0 -0
- /data/{rust → core}/src/env/mod.rs +0 -0
- /data/{rust → core}/src/env/type_error.rs +0 -0
- /data/{rust → core}/src/graph/mod.rs +0 -0
- /data/{rust → core}/src/lib.rs +0 -0
- /data/{rust → core}/src/lsp/diagnostics.rs +0 -0
- /data/{rust → core}/src/lsp/main.rs +0 -0
- /data/{rust → core}/src/lsp/mod.rs +0 -0
- /data/{rust → core}/src/lsp/server.rs +0 -0
- /data/{rust → core}/src/main.rs +0 -0
- /data/{rust → core}/src/rbs/converter.rs +0 -0
- /data/{rust → core}/src/rbs/error.rs +0 -0
- /data/{rust → core}/src/rbs/loader.rs +0 -0
- /data/{rust → core}/src/rbs/mod.rs +0 -0
- /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
|
-
}
|