method-ray 0.1.7 → 0.1.9
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 +33 -0
- data/{rust → core}/Cargo.toml +1 -1
- data/core/src/analyzer/assignments.rs +499 -0
- data/{rust → core}/src/analyzer/attributes.rs +2 -1
- data/{rust → core}/src/analyzer/blocks.rs +140 -0
- data/{rust → core}/src/analyzer/calls.rs +7 -3
- data/{rust → core}/src/analyzer/definitions.rs +12 -7
- data/{rust → core}/src/analyzer/dispatch.rs +431 -13
- data/core/src/analyzer/exceptions.rs +622 -0
- data/{rust → core}/src/analyzer/install.rs +37 -1
- data/{rust → core}/src/analyzer/literals.rs +3 -17
- data/core/src/analyzer/loops.rs +301 -0
- data/{rust → core}/src/analyzer/mod.rs +4 -0
- data/{rust → core}/src/analyzer/operators.rs +119 -27
- data/{rust → core}/src/analyzer/parameters.rs +214 -5
- data/core/src/analyzer/super_calls.rs +285 -0
- data/{rust → core}/src/cache/rbs_cache.rs +0 -1
- data/{rust → core}/src/cli/commands.rs +3 -3
- data/{rust → core}/src/diagnostics/diagnostic.rs +0 -3
- data/{rust → core}/src/diagnostics/formatter.rs +0 -1
- data/{rust → core}/src/env/box_manager.rs +2 -4
- data/{rust → core}/src/env/global_env.rs +28 -7
- data/{rust → core}/src/env/local_env.rs +35 -1
- data/{rust → core}/src/env/method_registry.rs +117 -25
- data/{rust → core}/src/env/scope.rs +91 -4
- data/{rust → core}/src/env/vertex_manager.rs +0 -1
- data/{rust → core}/src/graph/box.rs +134 -8
- data/{rust → core}/src/graph/change_set.rs +14 -0
- data/{rust → core}/src/lsp/server.rs +1 -1
- data/{rust → core}/src/rbs/loader.rs +1 -2
- data/{rust → core}/src/source_map.rs +0 -1
- data/{rust → core}/src/types.rs +11 -1
- 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 +54 -50
- /data/{rust → core}/src/analyzer/conditionals.rs +0 -0
- /data/{rust → core}/src/analyzer/parentheses.rs +0 -0
- /data/{rust → core}/src/analyzer/returns.rs +0 -0
- /data/{rust → core}/src/analyzer/variables.rs +0 -0
- /data/{rust → core}/src/cache/mod.rs +0 -0
- /data/{rust → core}/src/checker.rs +0 -0
- /data/{rust → core}/src/cli/args.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/graph/vertex.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/main.rs +0 -0
- /data/{rust → core}/src/parser.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/mod.rs +0 -0
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
//! This module handles the pattern matching of Ruby AST nodes
|
|
4
4
|
//! and dispatches them to specialized handlers.
|
|
5
5
|
|
|
6
|
+
use std::collections::HashMap;
|
|
7
|
+
|
|
6
8
|
use crate::env::{GlobalEnv, LocalEnv};
|
|
7
9
|
use crate::graph::{BlockParameterTypeBox, ChangeSet, VertexId};
|
|
8
10
|
use crate::source_map::SourceLocation;
|
|
@@ -16,6 +18,45 @@ use super::variables::{
|
|
|
16
18
|
install_self,
|
|
17
19
|
};
|
|
18
20
|
|
|
21
|
+
/// Collect positional and keyword arguments from AST argument nodes.
|
|
22
|
+
///
|
|
23
|
+
/// Shared by method calls (`dispatch.rs`) and super calls (`super_calls.rs`).
|
|
24
|
+
pub(crate) fn collect_arguments<'a>(
|
|
25
|
+
genv: &mut GlobalEnv,
|
|
26
|
+
lenv: &mut LocalEnv,
|
|
27
|
+
changes: &mut ChangeSet,
|
|
28
|
+
source: &str,
|
|
29
|
+
args: impl Iterator<Item = ruby_prism::Node<'a>>,
|
|
30
|
+
) -> (Vec<VertexId>, Option<HashMap<String, VertexId>>) {
|
|
31
|
+
let mut positional: Vec<VertexId> = Vec::new();
|
|
32
|
+
let mut keyword: HashMap<String, VertexId> = HashMap::new();
|
|
33
|
+
|
|
34
|
+
for arg in args {
|
|
35
|
+
if let Some(kw_hash) = arg.as_keyword_hash_node() {
|
|
36
|
+
for element in kw_hash.elements().iter() {
|
|
37
|
+
let assoc = match element.as_assoc_node() {
|
|
38
|
+
Some(a) => a,
|
|
39
|
+
None => continue,
|
|
40
|
+
};
|
|
41
|
+
let name = match assoc.key().as_symbol_node() {
|
|
42
|
+
Some(sym) => bytes_to_name(sym.unescaped()),
|
|
43
|
+
None => continue,
|
|
44
|
+
};
|
|
45
|
+
if let Some(vtx) =
|
|
46
|
+
super::install::install_node(genv, lenv, changes, source, &assoc.value())
|
|
47
|
+
{
|
|
48
|
+
keyword.insert(name, vtx);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
} else if let Some(vtx) = super::install::install_node(genv, lenv, changes, source, &arg) {
|
|
52
|
+
positional.push(vtx);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let kw = (!keyword.is_empty()).then_some(keyword);
|
|
57
|
+
(positional, kw)
|
|
58
|
+
}
|
|
59
|
+
|
|
19
60
|
/// Kind of attr_* declaration
|
|
20
61
|
#[derive(Debug, Clone, Copy)]
|
|
21
62
|
pub enum AttrKind {
|
|
@@ -60,6 +101,10 @@ pub enum NeedsChildKind<'a> {
|
|
|
60
101
|
kind: AttrKind,
|
|
61
102
|
attr_names: Vec<String>,
|
|
62
103
|
},
|
|
104
|
+
/// include declaration: `include Greetable, Enumerable`
|
|
105
|
+
IncludeDeclaration {
|
|
106
|
+
module_names: Vec<String>,
|
|
107
|
+
},
|
|
63
108
|
}
|
|
64
109
|
|
|
65
110
|
/// First pass: check if node can be handled immediately without child processing
|
|
@@ -187,6 +232,25 @@ pub fn dispatch_needs_child<'a>(node: &Node<'a>, source: &str) -> Option<NeedsCh
|
|
|
187
232
|
return None;
|
|
188
233
|
}
|
|
189
234
|
|
|
235
|
+
if method_name == "include" {
|
|
236
|
+
let module_names: Vec<String> = call_node
|
|
237
|
+
.arguments()
|
|
238
|
+
.map(|args| {
|
|
239
|
+
args.arguments()
|
|
240
|
+
.iter()
|
|
241
|
+
.filter_map(|arg| {
|
|
242
|
+
super::definitions::extract_constant_path(&arg)
|
|
243
|
+
})
|
|
244
|
+
.collect()
|
|
245
|
+
})
|
|
246
|
+
.unwrap_or_default();
|
|
247
|
+
|
|
248
|
+
if !module_names.is_empty() {
|
|
249
|
+
return Some(NeedsChildKind::IncludeDeclaration { module_names });
|
|
250
|
+
}
|
|
251
|
+
return None;
|
|
252
|
+
}
|
|
253
|
+
|
|
190
254
|
let prism_location = call_node
|
|
191
255
|
.message_loc()
|
|
192
256
|
.unwrap_or_else(|| node.location());
|
|
@@ -235,7 +299,8 @@ pub(crate) fn process_needs_child(
|
|
|
235
299
|
} => {
|
|
236
300
|
let recv_vtx = super::install::install_node(genv, lenv, changes, source, &receiver)?;
|
|
237
301
|
process_method_call_common(
|
|
238
|
-
genv, lenv, changes, source,
|
|
302
|
+
genv, lenv, changes, source,
|
|
303
|
+
MethodCallContext { recv_vtx, method_name, location, block, arguments },
|
|
239
304
|
)
|
|
240
305
|
}
|
|
241
306
|
NeedsChildKind::ImplicitSelfCall {
|
|
@@ -251,13 +316,24 @@ pub(crate) fn process_needs_child(
|
|
|
251
316
|
genv.new_source(Type::instance("Object"))
|
|
252
317
|
};
|
|
253
318
|
process_method_call_common(
|
|
254
|
-
genv, lenv, changes, source,
|
|
319
|
+
genv, lenv, changes, source,
|
|
320
|
+
MethodCallContext { recv_vtx, method_name, location, block, arguments },
|
|
255
321
|
)
|
|
256
322
|
}
|
|
257
323
|
NeedsChildKind::AttrDeclaration { kind, attr_names } => {
|
|
258
324
|
super::attributes::process_attr_declaration(genv, kind, attr_names);
|
|
259
325
|
None
|
|
260
326
|
}
|
|
327
|
+
NeedsChildKind::IncludeDeclaration { module_names } => {
|
|
328
|
+
if let Some(class_name) = genv.scope_manager.current_qualified_name() {
|
|
329
|
+
// Ruby processes `include A, B` right-to-left (B first, then A on top),
|
|
330
|
+
// so A ends up with higher MRO priority. Reverse to match this behavior.
|
|
331
|
+
for module_name in module_names.iter().rev() {
|
|
332
|
+
genv.record_include(&class_name, module_name);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
None
|
|
336
|
+
}
|
|
261
337
|
}
|
|
262
338
|
}
|
|
263
339
|
|
|
@@ -277,6 +353,15 @@ fn finish_local_var_write(
|
|
|
277
353
|
install_local_var_write(genv, lenv, changes, var_name, value_vtx)
|
|
278
354
|
}
|
|
279
355
|
|
|
356
|
+
/// Bundled parameters for method call processing
|
|
357
|
+
struct MethodCallContext<'a> {
|
|
358
|
+
recv_vtx: VertexId,
|
|
359
|
+
method_name: String,
|
|
360
|
+
location: SourceLocation,
|
|
361
|
+
block: Option<Node<'a>>,
|
|
362
|
+
arguments: Vec<Node<'a>>,
|
|
363
|
+
}
|
|
364
|
+
|
|
280
365
|
/// MethodCall / ImplicitSelfCall common processing:
|
|
281
366
|
/// Handles argument processing, block processing, and MethodCallBox creation after recv_vtx is obtained
|
|
282
367
|
fn process_method_call_common<'a>(
|
|
@@ -284,16 +369,21 @@ fn process_method_call_common<'a>(
|
|
|
284
369
|
lenv: &mut LocalEnv,
|
|
285
370
|
changes: &mut ChangeSet,
|
|
286
371
|
source: &str,
|
|
287
|
-
|
|
288
|
-
method_name: String,
|
|
289
|
-
location: SourceLocation,
|
|
290
|
-
block: Option<Node<'a>>,
|
|
291
|
-
arguments: Vec<Node<'a>>,
|
|
372
|
+
ctx: MethodCallContext<'a>,
|
|
292
373
|
) -> Option<VertexId> {
|
|
293
|
-
let
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
374
|
+
let MethodCallContext {
|
|
375
|
+
recv_vtx,
|
|
376
|
+
method_name,
|
|
377
|
+
location,
|
|
378
|
+
block,
|
|
379
|
+
arguments,
|
|
380
|
+
} = ctx;
|
|
381
|
+
if method_name == "!" {
|
|
382
|
+
return Some(super::operators::process_not_operator(genv));
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
let (positional_arg_vtxs, kwarg_vtxs) =
|
|
386
|
+
collect_arguments(genv, lenv, changes, source, arguments.into_iter());
|
|
297
387
|
|
|
298
388
|
if let Some(block_node) = block {
|
|
299
389
|
if let Some(block) = block_node.as_block_node() {
|
|
@@ -315,7 +405,12 @@ fn process_method_call_common<'a>(
|
|
|
315
405
|
}
|
|
316
406
|
|
|
317
407
|
Some(finish_method_call(
|
|
318
|
-
genv,
|
|
408
|
+
genv,
|
|
409
|
+
recv_vtx,
|
|
410
|
+
method_name,
|
|
411
|
+
positional_arg_vtxs,
|
|
412
|
+
kwarg_vtxs,
|
|
413
|
+
location,
|
|
319
414
|
))
|
|
320
415
|
}
|
|
321
416
|
|
|
@@ -325,9 +420,10 @@ fn finish_method_call(
|
|
|
325
420
|
recv_vtx: VertexId,
|
|
326
421
|
method_name: String,
|
|
327
422
|
arg_vtxs: Vec<VertexId>,
|
|
423
|
+
kwarg_vtxs: Option<HashMap<String, VertexId>>,
|
|
328
424
|
location: SourceLocation,
|
|
329
425
|
) -> VertexId {
|
|
330
|
-
install_method_call(genv, recv_vtx, method_name, arg_vtxs, Some(location))
|
|
426
|
+
install_method_call(genv, recv_vtx, method_name, arg_vtxs, kwarg_vtxs, Some(location))
|
|
331
427
|
}
|
|
332
428
|
|
|
333
429
|
#[cfg(test)]
|
|
@@ -980,4 +1076,326 @@ end
|
|
|
980
1076
|
genv.type_errors
|
|
981
1077
|
);
|
|
982
1078
|
}
|
|
1079
|
+
|
|
1080
|
+
// === Keyword argument tests ===
|
|
1081
|
+
|
|
1082
|
+
// Test 26: Required keyword argument type propagation
|
|
1083
|
+
#[test]
|
|
1084
|
+
fn test_keyword_arg_required_propagation() {
|
|
1085
|
+
let source = r#"
|
|
1086
|
+
class Greeter
|
|
1087
|
+
def greet(name:)
|
|
1088
|
+
name
|
|
1089
|
+
end
|
|
1090
|
+
end
|
|
1091
|
+
|
|
1092
|
+
Greeter.new.greet(name: "Alice")
|
|
1093
|
+
"#;
|
|
1094
|
+
let genv = analyze(source);
|
|
1095
|
+
|
|
1096
|
+
let info = genv
|
|
1097
|
+
.resolve_method(&Type::instance("Greeter"), "greet")
|
|
1098
|
+
.expect("Greeter#greet should be registered");
|
|
1099
|
+
let ret_vtx = info.return_vertex.unwrap();
|
|
1100
|
+
assert_eq!(get_type_show(&genv, ret_vtx), "String");
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
// Test 27: Optional keyword argument with default type
|
|
1104
|
+
#[test]
|
|
1105
|
+
fn test_keyword_arg_optional_default_type() {
|
|
1106
|
+
let source = r#"
|
|
1107
|
+
class Counter
|
|
1108
|
+
def count(step: 1)
|
|
1109
|
+
step
|
|
1110
|
+
end
|
|
1111
|
+
end
|
|
1112
|
+
"#;
|
|
1113
|
+
let genv = analyze(source);
|
|
1114
|
+
|
|
1115
|
+
let info = genv
|
|
1116
|
+
.resolve_method(&Type::instance("Counter"), "count")
|
|
1117
|
+
.expect("Counter#count should be registered");
|
|
1118
|
+
let ret_vtx = info.return_vertex.unwrap();
|
|
1119
|
+
// step has Integer type from default value
|
|
1120
|
+
assert_eq!(get_type_show(&genv, ret_vtx), "Integer");
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
// Test 28: Positional and keyword arguments mixed
|
|
1124
|
+
#[test]
|
|
1125
|
+
fn test_positional_and_keyword_mixed() {
|
|
1126
|
+
let source = r#"
|
|
1127
|
+
class User
|
|
1128
|
+
def initialize(id, name:)
|
|
1129
|
+
@id = id
|
|
1130
|
+
@name = name
|
|
1131
|
+
end
|
|
1132
|
+
end
|
|
1133
|
+
|
|
1134
|
+
User.new(1, name: "Alice")
|
|
1135
|
+
"#;
|
|
1136
|
+
let genv = analyze(source);
|
|
1137
|
+
assert!(genv.type_errors.is_empty());
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
// Test 29: Keyword argument via .new propagation to initialize
|
|
1141
|
+
#[test]
|
|
1142
|
+
fn test_keyword_arg_via_new_to_initialize() {
|
|
1143
|
+
let source = r#"
|
|
1144
|
+
class Config
|
|
1145
|
+
def initialize(debug:)
|
|
1146
|
+
@debug = debug
|
|
1147
|
+
end
|
|
1148
|
+
|
|
1149
|
+
def debug?
|
|
1150
|
+
@debug
|
|
1151
|
+
end
|
|
1152
|
+
end
|
|
1153
|
+
|
|
1154
|
+
Config.new(debug: true)
|
|
1155
|
+
"#;
|
|
1156
|
+
let genv = analyze(source);
|
|
1157
|
+
assert!(genv.type_errors.is_empty());
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
// Test 30: Multiple keyword arguments
|
|
1161
|
+
#[test]
|
|
1162
|
+
fn test_multiple_keyword_args() {
|
|
1163
|
+
let source = r#"
|
|
1164
|
+
class User
|
|
1165
|
+
def profile(name:, age:)
|
|
1166
|
+
name
|
|
1167
|
+
end
|
|
1168
|
+
end
|
|
1169
|
+
|
|
1170
|
+
User.new.profile(name: "Alice", age: 30)
|
|
1171
|
+
"#;
|
|
1172
|
+
let genv = analyze(source);
|
|
1173
|
+
|
|
1174
|
+
let info = genv
|
|
1175
|
+
.resolve_method(&Type::instance("User"), "profile")
|
|
1176
|
+
.expect("User#profile should be registered");
|
|
1177
|
+
let ret_vtx = info.return_vertex.unwrap();
|
|
1178
|
+
assert_eq!(get_type_show(&genv, ret_vtx), "String");
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
// === Include (mixin) tests ===
|
|
1182
|
+
|
|
1183
|
+
// Test 31: Basic include — module method resolved on class instance
|
|
1184
|
+
#[test]
|
|
1185
|
+
fn test_include_basic() {
|
|
1186
|
+
let source = r#"
|
|
1187
|
+
module Greetable
|
|
1188
|
+
def greet
|
|
1189
|
+
"Hello!"
|
|
1190
|
+
end
|
|
1191
|
+
end
|
|
1192
|
+
|
|
1193
|
+
class User
|
|
1194
|
+
include Greetable
|
|
1195
|
+
end
|
|
1196
|
+
|
|
1197
|
+
User.new.greet
|
|
1198
|
+
"#;
|
|
1199
|
+
let genv = analyze(source);
|
|
1200
|
+
assert!(
|
|
1201
|
+
genv.type_errors.is_empty(),
|
|
1202
|
+
"include should resolve Greetable#greet: {:?}",
|
|
1203
|
+
genv.type_errors
|
|
1204
|
+
);
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
// Test 32: Include method return type inference
|
|
1208
|
+
#[test]
|
|
1209
|
+
fn test_include_method_return_type() {
|
|
1210
|
+
let source = r#"
|
|
1211
|
+
module Greetable
|
|
1212
|
+
def greet
|
|
1213
|
+
"Hello!"
|
|
1214
|
+
end
|
|
1215
|
+
end
|
|
1216
|
+
|
|
1217
|
+
class User
|
|
1218
|
+
include Greetable
|
|
1219
|
+
|
|
1220
|
+
def say_hello
|
|
1221
|
+
greet
|
|
1222
|
+
end
|
|
1223
|
+
end
|
|
1224
|
+
"#;
|
|
1225
|
+
let genv = analyze(source);
|
|
1226
|
+
|
|
1227
|
+
let info = genv
|
|
1228
|
+
.resolve_method(&Type::instance("User"), "say_hello")
|
|
1229
|
+
.expect("User#say_hello should be registered");
|
|
1230
|
+
let ret_vtx = info.return_vertex.unwrap();
|
|
1231
|
+
assert_eq!(get_type_show(&genv, ret_vtx), "String");
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
// Test 33: Multiple includes — last included module has priority
|
|
1235
|
+
#[test]
|
|
1236
|
+
fn test_include_multiple_modules() {
|
|
1237
|
+
let source = r#"
|
|
1238
|
+
module A
|
|
1239
|
+
def foo
|
|
1240
|
+
"from A"
|
|
1241
|
+
end
|
|
1242
|
+
end
|
|
1243
|
+
|
|
1244
|
+
module B
|
|
1245
|
+
def foo
|
|
1246
|
+
42
|
|
1247
|
+
end
|
|
1248
|
+
end
|
|
1249
|
+
|
|
1250
|
+
class User
|
|
1251
|
+
include A
|
|
1252
|
+
include B
|
|
1253
|
+
end
|
|
1254
|
+
|
|
1255
|
+
User.new.foo
|
|
1256
|
+
"#;
|
|
1257
|
+
let genv = analyze(source);
|
|
1258
|
+
assert!(genv.type_errors.is_empty());
|
|
1259
|
+
|
|
1260
|
+
// B is included last → B#foo (Integer) should be resolved
|
|
1261
|
+
let info = genv
|
|
1262
|
+
.resolve_method(&Type::instance("User"), "foo")
|
|
1263
|
+
.expect("User#foo should be resolved via include");
|
|
1264
|
+
let ret_vtx = info.return_vertex.unwrap();
|
|
1265
|
+
assert_eq!(get_type_show(&genv, ret_vtx), "Integer");
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
// Test 34: Class's own method takes priority over included module
|
|
1269
|
+
#[test]
|
|
1270
|
+
fn test_include_class_method_priority() {
|
|
1271
|
+
let source = r#"
|
|
1272
|
+
module Greetable
|
|
1273
|
+
def greet
|
|
1274
|
+
"Hello from module!"
|
|
1275
|
+
end
|
|
1276
|
+
end
|
|
1277
|
+
|
|
1278
|
+
class User
|
|
1279
|
+
include Greetable
|
|
1280
|
+
|
|
1281
|
+
def greet
|
|
1282
|
+
42
|
|
1283
|
+
end
|
|
1284
|
+
end
|
|
1285
|
+
|
|
1286
|
+
User.new.greet
|
|
1287
|
+
"#;
|
|
1288
|
+
let genv = analyze(source);
|
|
1289
|
+
|
|
1290
|
+
let info = genv
|
|
1291
|
+
.resolve_method(&Type::instance("User"), "greet")
|
|
1292
|
+
.expect("User#greet should be resolved");
|
|
1293
|
+
let ret_vtx = info.return_vertex.unwrap();
|
|
1294
|
+
// Class's own method (Integer) takes priority
|
|
1295
|
+
assert_eq!(get_type_show(&genv, ret_vtx), "Integer");
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
// Test 35: Include with qualified module name
|
|
1299
|
+
#[test]
|
|
1300
|
+
fn test_include_qualified_module() {
|
|
1301
|
+
let source = r#"
|
|
1302
|
+
module Api
|
|
1303
|
+
module Helpers
|
|
1304
|
+
def help
|
|
1305
|
+
"help"
|
|
1306
|
+
end
|
|
1307
|
+
end
|
|
1308
|
+
end
|
|
1309
|
+
|
|
1310
|
+
class User
|
|
1311
|
+
include Api::Helpers
|
|
1312
|
+
end
|
|
1313
|
+
|
|
1314
|
+
User.new.help
|
|
1315
|
+
"#;
|
|
1316
|
+
let genv = analyze(source);
|
|
1317
|
+
assert!(
|
|
1318
|
+
genv.type_errors.is_empty(),
|
|
1319
|
+
"include Api::Helpers should resolve: {:?}",
|
|
1320
|
+
genv.type_errors
|
|
1321
|
+
);
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
// Test 36: Include with simultaneous multiple modules
|
|
1325
|
+
#[test]
|
|
1326
|
+
fn test_include_simultaneous_multiple() {
|
|
1327
|
+
let source = r#"
|
|
1328
|
+
module A
|
|
1329
|
+
def a_method
|
|
1330
|
+
"a"
|
|
1331
|
+
end
|
|
1332
|
+
end
|
|
1333
|
+
|
|
1334
|
+
module B
|
|
1335
|
+
def b_method
|
|
1336
|
+
42
|
|
1337
|
+
end
|
|
1338
|
+
end
|
|
1339
|
+
|
|
1340
|
+
class User
|
|
1341
|
+
include A, B
|
|
1342
|
+
end
|
|
1343
|
+
|
|
1344
|
+
User.new.a_method
|
|
1345
|
+
User.new.b_method
|
|
1346
|
+
"#;
|
|
1347
|
+
let genv = analyze(source);
|
|
1348
|
+
assert!(
|
|
1349
|
+
genv.type_errors.is_empty(),
|
|
1350
|
+
"include A, B should resolve both modules: {:?}",
|
|
1351
|
+
genv.type_errors
|
|
1352
|
+
);
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
// Test 37: Include with unknown module (no crash)
|
|
1356
|
+
#[test]
|
|
1357
|
+
fn test_include_unknown_module() {
|
|
1358
|
+
let source = r#"
|
|
1359
|
+
class User
|
|
1360
|
+
include UnknownModule
|
|
1361
|
+
end
|
|
1362
|
+
"#;
|
|
1363
|
+
let genv = analyze(source);
|
|
1364
|
+
// Should not panic; unknown module is recorded but methods won't resolve
|
|
1365
|
+
let _ = genv;
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
// Test 38: Simultaneous include A, B — A has higher MRO priority (Ruby semantics)
|
|
1369
|
+
#[test]
|
|
1370
|
+
fn test_include_simultaneous_order() {
|
|
1371
|
+
let source = r#"
|
|
1372
|
+
module A
|
|
1373
|
+
def foo
|
|
1374
|
+
"from A"
|
|
1375
|
+
end
|
|
1376
|
+
end
|
|
1377
|
+
|
|
1378
|
+
module B
|
|
1379
|
+
def foo
|
|
1380
|
+
42
|
|
1381
|
+
end
|
|
1382
|
+
end
|
|
1383
|
+
|
|
1384
|
+
class User
|
|
1385
|
+
include A, B
|
|
1386
|
+
end
|
|
1387
|
+
|
|
1388
|
+
User.new.foo
|
|
1389
|
+
"#;
|
|
1390
|
+
let genv = analyze(source);
|
|
1391
|
+
assert!(genv.type_errors.is_empty());
|
|
1392
|
+
|
|
1393
|
+
// Ruby's `include A, B` processes right-to-left: B first, then A on top.
|
|
1394
|
+
// A has higher MRO priority → A#foo (String) should be resolved.
|
|
1395
|
+
let info = genv
|
|
1396
|
+
.resolve_method(&Type::instance("User"), "foo")
|
|
1397
|
+
.expect("User#foo should be resolved via include");
|
|
1398
|
+
let ret_vtx = info.return_vertex.unwrap();
|
|
1399
|
+
assert_eq!(get_type_show(&genv, ret_vtx), "String");
|
|
1400
|
+
}
|
|
983
1401
|
}
|