method-ray 0.1.6 → 0.1.8
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 +30 -0
- data/ext/Cargo.toml +1 -1
- data/lib/methodray/version.rb +1 -1
- data/rust/Cargo.toml +1 -1
- data/rust/src/analyzer/assignments.rs +152 -0
- data/rust/src/analyzer/attributes.rs +2 -1
- data/rust/src/analyzer/blocks.rs +4 -3
- data/rust/src/analyzer/calls.rs +7 -3
- data/rust/src/analyzer/definitions.rs +10 -5
- data/rust/src/analyzer/dispatch.rs +265 -24
- data/rust/src/analyzer/exceptions.rs +521 -0
- data/rust/src/analyzer/install.rs +22 -1
- data/rust/src/analyzer/loops.rs +176 -0
- data/rust/src/analyzer/mod.rs +32 -0
- data/rust/src/analyzer/operators.rs +119 -27
- data/rust/src/analyzer/parameters.rs +60 -9
- data/rust/src/cache/rbs_cache.rs +0 -1
- data/rust/src/cli/commands.rs +3 -3
- data/rust/src/diagnostics/diagnostic.rs +0 -3
- data/rust/src/diagnostics/formatter.rs +0 -1
- data/rust/src/env/box_manager.rs +2 -4
- data/rust/src/env/global_env.rs +43 -5
- data/rust/src/env/local_env.rs +35 -1
- data/rust/src/env/method_registry.rs +140 -17
- data/rust/src/env/scope.rs +143 -164
- data/rust/src/env/vertex_manager.rs +0 -1
- data/rust/src/graph/box.rs +217 -84
- data/rust/src/graph/change_set.rs +14 -0
- data/rust/src/lsp/server.rs +1 -1
- data/rust/src/rbs/loader.rs +1 -2
- data/rust/src/source_map.rs +0 -1
- data/rust/src/types.rs +0 -1
- metadata +4 -1
|
@@ -3,12 +3,15 @@
|
|
|
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;
|
|
9
11
|
use crate::types::Type;
|
|
10
12
|
use ruby_prism::Node;
|
|
11
13
|
|
|
14
|
+
use super::bytes_to_name;
|
|
12
15
|
use super::calls::install_method_call;
|
|
13
16
|
use super::variables::{
|
|
14
17
|
install_ivar_read, install_ivar_write, install_local_var_read, install_local_var_write,
|
|
@@ -68,7 +71,7 @@ pub enum NeedsChildKind<'a> {
|
|
|
68
71
|
pub fn dispatch_simple(genv: &mut GlobalEnv, lenv: &mut LocalEnv, node: &Node) -> DispatchResult {
|
|
69
72
|
// Instance variable read: @name
|
|
70
73
|
if let Some(ivar_read) = node.as_instance_variable_read_node() {
|
|
71
|
-
let ivar_name =
|
|
74
|
+
let ivar_name = bytes_to_name(ivar_read.name().as_slice());
|
|
72
75
|
return match install_ivar_read(genv, &ivar_name) {
|
|
73
76
|
Some(vtx) => DispatchResult::Vertex(vtx),
|
|
74
77
|
None => DispatchResult::NotHandled,
|
|
@@ -82,17 +85,19 @@ pub fn dispatch_simple(genv: &mut GlobalEnv, lenv: &mut LocalEnv, node: &Node) -
|
|
|
82
85
|
|
|
83
86
|
// Local variable read: x
|
|
84
87
|
if let Some(read_node) = node.as_local_variable_read_node() {
|
|
85
|
-
let var_name =
|
|
88
|
+
let var_name = bytes_to_name(read_node.name().as_slice());
|
|
86
89
|
return match install_local_var_read(lenv, &var_name) {
|
|
87
90
|
Some(vtx) => DispatchResult::Vertex(vtx),
|
|
88
91
|
None => DispatchResult::NotHandled,
|
|
89
92
|
};
|
|
90
93
|
}
|
|
91
94
|
|
|
92
|
-
// ConstantReadNode: User → Type::Singleton("User")
|
|
95
|
+
// ConstantReadNode: User → Type::Singleton("User") or Type::Singleton("Api::User")
|
|
93
96
|
if let Some(const_read) = node.as_constant_read_node() {
|
|
94
|
-
let name =
|
|
95
|
-
let
|
|
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));
|
|
96
101
|
return DispatchResult::Vertex(vtx);
|
|
97
102
|
}
|
|
98
103
|
|
|
@@ -116,7 +121,7 @@ fn extract_symbol_names(call_node: &ruby_prism::CallNode) -> Vec<String> {
|
|
|
116
121
|
.iter()
|
|
117
122
|
.filter_map(|arg| {
|
|
118
123
|
arg.as_symbol_node().map(|sym| {
|
|
119
|
-
|
|
124
|
+
bytes_to_name(sym.unescaped())
|
|
120
125
|
})
|
|
121
126
|
})
|
|
122
127
|
.collect()
|
|
@@ -128,7 +133,7 @@ fn extract_symbol_names(call_node: &ruby_prism::CallNode) -> Vec<String> {
|
|
|
128
133
|
pub fn dispatch_needs_child<'a>(node: &Node<'a>, source: &str) -> Option<NeedsChildKind<'a>> {
|
|
129
134
|
// Instance variable write: @name = value
|
|
130
135
|
if let Some(ivar_write) = node.as_instance_variable_write_node() {
|
|
131
|
-
let ivar_name =
|
|
136
|
+
let ivar_name = bytes_to_name(ivar_write.name().as_slice());
|
|
132
137
|
return Some(NeedsChildKind::IvarWrite {
|
|
133
138
|
ivar_name,
|
|
134
139
|
value: ivar_write.value(),
|
|
@@ -137,7 +142,7 @@ pub fn dispatch_needs_child<'a>(node: &Node<'a>, source: &str) -> Option<NeedsCh
|
|
|
137
142
|
|
|
138
143
|
// Local variable write: x = value
|
|
139
144
|
if let Some(write_node) = node.as_local_variable_write_node() {
|
|
140
|
-
let var_name =
|
|
145
|
+
let var_name = bytes_to_name(write_node.name().as_slice());
|
|
141
146
|
return Some(NeedsChildKind::LocalVarWrite {
|
|
142
147
|
var_name,
|
|
143
148
|
value: write_node.value(),
|
|
@@ -146,7 +151,7 @@ pub fn dispatch_needs_child<'a>(node: &Node<'a>, source: &str) -> Option<NeedsCh
|
|
|
146
151
|
|
|
147
152
|
// Method call: x.upcase, x.each { |i| ... }, or name (implicit self)
|
|
148
153
|
if let Some(call_node) = node.as_call_node() {
|
|
149
|
-
let method_name =
|
|
154
|
+
let method_name = bytes_to_name(call_node.name().as_slice());
|
|
150
155
|
let block = call_node.block();
|
|
151
156
|
let arguments: Vec<Node<'a>> = call_node
|
|
152
157
|
.arguments()
|
|
@@ -232,7 +237,8 @@ pub(crate) fn process_needs_child(
|
|
|
232
237
|
} => {
|
|
233
238
|
let recv_vtx = super::install::install_node(genv, lenv, changes, source, &receiver)?;
|
|
234
239
|
process_method_call_common(
|
|
235
|
-
genv, lenv, changes, source,
|
|
240
|
+
genv, lenv, changes, source,
|
|
241
|
+
MethodCallContext { recv_vtx, method_name, location, block, arguments },
|
|
236
242
|
)
|
|
237
243
|
}
|
|
238
244
|
NeedsChildKind::ImplicitSelfCall {
|
|
@@ -248,7 +254,8 @@ pub(crate) fn process_needs_child(
|
|
|
248
254
|
genv.new_source(Type::instance("Object"))
|
|
249
255
|
};
|
|
250
256
|
process_method_call_common(
|
|
251
|
-
genv, lenv, changes, source,
|
|
257
|
+
genv, lenv, changes, source,
|
|
258
|
+
MethodCallContext { recv_vtx, method_name, location, block, arguments },
|
|
252
259
|
)
|
|
253
260
|
}
|
|
254
261
|
NeedsChildKind::AttrDeclaration { kind, attr_names } => {
|
|
@@ -274,6 +281,15 @@ fn finish_local_var_write(
|
|
|
274
281
|
install_local_var_write(genv, lenv, changes, var_name, value_vtx)
|
|
275
282
|
}
|
|
276
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
|
+
|
|
277
293
|
/// MethodCall / ImplicitSelfCall common processing:
|
|
278
294
|
/// Handles argument processing, block processing, and MethodCallBox creation after recv_vtx is obtained
|
|
279
295
|
fn process_method_call_common<'a>(
|
|
@@ -281,16 +297,44 @@ fn process_method_call_common<'a>(
|
|
|
281
297
|
lenv: &mut LocalEnv,
|
|
282
298
|
changes: &mut ChangeSet,
|
|
283
299
|
source: &str,
|
|
284
|
-
|
|
285
|
-
method_name: String,
|
|
286
|
-
location: SourceLocation,
|
|
287
|
-
block: Option<Node<'a>>,
|
|
288
|
-
arguments: Vec<Node<'a>>,
|
|
300
|
+
ctx: MethodCallContext<'a>,
|
|
289
301
|
) -> Option<VertexId> {
|
|
290
|
-
let
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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
|
+
}
|
|
294
338
|
|
|
295
339
|
if let Some(block_node) = block {
|
|
296
340
|
if let Some(block) = block_node.as_block_node() {
|
|
@@ -311,8 +355,19 @@ fn process_method_call_common<'a>(
|
|
|
311
355
|
}
|
|
312
356
|
}
|
|
313
357
|
|
|
358
|
+
let kwarg_vtxs = if keyword_arg_vtxs.is_empty() {
|
|
359
|
+
None
|
|
360
|
+
} else {
|
|
361
|
+
Some(keyword_arg_vtxs)
|
|
362
|
+
};
|
|
363
|
+
|
|
314
364
|
Some(finish_method_call(
|
|
315
|
-
genv,
|
|
365
|
+
genv,
|
|
366
|
+
recv_vtx,
|
|
367
|
+
method_name,
|
|
368
|
+
positional_arg_vtxs,
|
|
369
|
+
kwarg_vtxs,
|
|
370
|
+
location,
|
|
316
371
|
))
|
|
317
372
|
}
|
|
318
373
|
|
|
@@ -322,9 +377,10 @@ fn finish_method_call(
|
|
|
322
377
|
recv_vtx: VertexId,
|
|
323
378
|
method_name: String,
|
|
324
379
|
arg_vtxs: Vec<VertexId>,
|
|
380
|
+
kwarg_vtxs: Option<HashMap<String, VertexId>>,
|
|
325
381
|
location: SourceLocation,
|
|
326
382
|
) -> VertexId {
|
|
327
|
-
install_method_call(genv, recv_vtx, method_name, arg_vtxs, Some(location))
|
|
383
|
+
install_method_call(genv, recv_vtx, method_name, arg_vtxs, kwarg_vtxs, Some(location))
|
|
328
384
|
}
|
|
329
385
|
|
|
330
386
|
#[cfg(test)]
|
|
@@ -463,8 +519,8 @@ end
|
|
|
463
519
|
|
|
464
520
|
// Utils.run should be registered
|
|
465
521
|
let info = genv
|
|
466
|
-
.resolve_method(&Type::
|
|
467
|
-
.expect("Utils
|
|
522
|
+
.resolve_method(&Type::singleton("Utils"), "run")
|
|
523
|
+
.expect("Utils.run should be registered");
|
|
468
524
|
assert!(info.return_vertex.is_some());
|
|
469
525
|
}
|
|
470
526
|
|
|
@@ -893,4 +949,189 @@ Api::User.new.name
|
|
|
893
949
|
genv.type_errors
|
|
894
950
|
);
|
|
895
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
|
+
}
|
|
896
1137
|
}
|