method-ray 0.1.7 → 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 +15 -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/calls.rs +7 -3
- data/rust/src/analyzer/definitions.rs +6 -2
- data/rust/src/analyzer/dispatch.rs +167 -13
- 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 +3 -0
- data/rust/src/analyzer/operators.rs +119 -27
- data/rust/src/analyzer/parameters.rs +54 -5
- 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 +10 -3
- data/rust/src/env/local_env.rs +35 -1
- data/rust/src/env/method_registry.rs +8 -2
- data/rust/src/env/scope.rs +13 -4
- data/rust/src/env/vertex_manager.rs +0 -1
- data/rust/src/graph/box.rs +134 -8
- 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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f44709f8354439522722c8ea65da30463fae9de68d9be1aa07c8dac9627a03ce
|
|
4
|
+
data.tar.gz: 2a09885854020892cf93a1c85e83735a23be60240fcbee09f77fa8c9fbf58801
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4be6a23881adf54e88039c4e62255f09aa52005cf4085e6cbdc1910f642dae8e763ec7993445051bd3cff20693b5576e417f47b0864359c60db6becf1c21e204
|
|
7
|
+
data.tar.gz: cc9b502af18acc8463e9cddf95cc16b8ed6095ffc34d826642fe8193b2f4cc16aa7490a96f930493fe30fc4af21412cf2ff515f8f45e4d04bf9ec704f256ea26
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.1.8] - 2026-03-09
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- while/until loop support to type inference ([#46](https://github.com/dak2/method-ray/pull/46))
|
|
13
|
+
- Not operator (!) support to type inference ([#47](https://github.com/dak2/method-ray/pull/47))
|
|
14
|
+
- begin/rescue/ensure exception handling support to type inference ([#48](https://github.com/dak2/method-ray/pull/48))
|
|
15
|
+
- Keyword argument support to type inference ([#49](https://github.com/dak2/method-ray/pull/49))
|
|
16
|
+
- Multiple assignment support to type inference ([#50](https://github.com/dak2/method-ray/pull/50))
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- Resolve all Clippy warnings for cleaner, more idiomatic Rust ([#51](https://github.com/dak2/method-ray/pull/51))
|
|
21
|
+
|
|
8
22
|
## [0.1.7] - 2026-03-07
|
|
9
23
|
|
|
10
24
|
### Added
|
|
@@ -109,6 +123,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
109
123
|
- Initial release
|
|
110
124
|
- `methodray check` - Static type checking for Ruby files
|
|
111
125
|
|
|
126
|
+
[0.1.8]: https://github.com/dak2/method-ray/releases/tag/v0.1.8
|
|
112
127
|
[0.1.7]: https://github.com/dak2/method-ray/releases/tag/v0.1.7
|
|
113
128
|
[0.1.6]: https://github.com/dak2/method-ray/releases/tag/v0.1.6
|
|
114
129
|
[0.1.5]: https://github.com/dak2/method-ray/releases/tag/v0.1.5
|
data/ext/Cargo.toml
CHANGED
data/lib/methodray/version.rb
CHANGED
data/rust/Cargo.toml
CHANGED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
//! Multiple Assignment Handlers - Processing Ruby multiple assignment
|
|
2
|
+
//!
|
|
3
|
+
//! v0.1.8 scope: Only RHS as ArrayNode (multiple literal values) is supported.
|
|
4
|
+
//! TODO: Support RHS as single expression (array decomposition)
|
|
5
|
+
//! TODO: Support splat target (*rest) as Array type
|
|
6
|
+
//! TODO: Support RHS as method return value decomposition
|
|
7
|
+
//! TODO: When LHS is longer than RHS, register trailing targets as NilClass
|
|
8
|
+
|
|
9
|
+
use crate::env::{GlobalEnv, LocalEnv};
|
|
10
|
+
use crate::graph::{ChangeSet, VertexId};
|
|
11
|
+
|
|
12
|
+
use super::bytes_to_name;
|
|
13
|
+
use super::variables::install_local_var_write;
|
|
14
|
+
|
|
15
|
+
/// Process multiple assignment node (e.g., `a, b = 1, "hello"`)
|
|
16
|
+
pub(crate) fn process_multi_write_node(
|
|
17
|
+
genv: &mut GlobalEnv,
|
|
18
|
+
lenv: &mut LocalEnv,
|
|
19
|
+
changes: &mut ChangeSet,
|
|
20
|
+
source: &str,
|
|
21
|
+
node: &ruby_prism::MultiWriteNode,
|
|
22
|
+
) -> Option<VertexId> {
|
|
23
|
+
let value = node.value();
|
|
24
|
+
let mut last_vtx = None;
|
|
25
|
+
|
|
26
|
+
if let Some(array_node) = value.as_array_node() {
|
|
27
|
+
for (target, rhs_elem) in node.lefts().iter().zip(array_node.elements().iter()) {
|
|
28
|
+
if let Some(target_node) = target.as_local_variable_target_node() {
|
|
29
|
+
let var_name = bytes_to_name(target_node.name().as_slice());
|
|
30
|
+
let rhs_vtx =
|
|
31
|
+
super::install::install_node(genv, lenv, changes, source, &rhs_elem);
|
|
32
|
+
if let Some(rv) = rhs_vtx {
|
|
33
|
+
last_vtx = Some(install_local_var_write(genv, lenv, changes, var_name, rv));
|
|
34
|
+
} else {
|
|
35
|
+
let var_vtx = genv.new_vertex();
|
|
36
|
+
lenv.new_var(var_name, var_vtx);
|
|
37
|
+
last_vtx = Some(var_vtx);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
} else {
|
|
42
|
+
for target in node.lefts().iter() {
|
|
43
|
+
if let Some(target_node) = target.as_local_variable_target_node() {
|
|
44
|
+
let var_name = bytes_to_name(target_node.name().as_slice());
|
|
45
|
+
let var_vtx = genv.new_vertex();
|
|
46
|
+
lenv.new_var(var_name, var_vtx);
|
|
47
|
+
last_vtx = Some(var_vtx);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
last_vtx
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
#[cfg(test)]
|
|
56
|
+
mod tests {
|
|
57
|
+
use crate::analyzer::install::AstInstaller;
|
|
58
|
+
use crate::env::{GlobalEnv, LocalEnv};
|
|
59
|
+
use crate::graph::VertexId;
|
|
60
|
+
use crate::parser::ParseSession;
|
|
61
|
+
|
|
62
|
+
fn analyze(source: &str) -> (GlobalEnv, LocalEnv) {
|
|
63
|
+
let session = ParseSession::new();
|
|
64
|
+
let parse_result = session.parse_source(source, "test.rb").unwrap();
|
|
65
|
+
let root = parse_result.node();
|
|
66
|
+
let program = root.as_program_node().unwrap();
|
|
67
|
+
|
|
68
|
+
let mut genv = GlobalEnv::new();
|
|
69
|
+
let mut lenv = LocalEnv::new();
|
|
70
|
+
|
|
71
|
+
let mut installer = AstInstaller::new(&mut genv, &mut lenv, source);
|
|
72
|
+
for stmt in &program.statements().body() {
|
|
73
|
+
installer.install_node(&stmt);
|
|
74
|
+
}
|
|
75
|
+
installer.finish();
|
|
76
|
+
|
|
77
|
+
(genv, lenv)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
fn get_type_show(genv: &GlobalEnv, vtx: VertexId) -> String {
|
|
81
|
+
if let Some(vertex) = genv.get_vertex(vtx) {
|
|
82
|
+
vertex.show()
|
|
83
|
+
} else if let Some(source) = genv.get_source(vtx) {
|
|
84
|
+
source.ty.show()
|
|
85
|
+
} else {
|
|
86
|
+
panic!("vertex {:?} not found as either Vertex or Source", vtx);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
#[test]
|
|
91
|
+
fn test_multi_write_integer_and_string() {
|
|
92
|
+
let source = r#"a, b = 1, "hello""#;
|
|
93
|
+
let (genv, lenv) = analyze(source);
|
|
94
|
+
|
|
95
|
+
let a_vtx = lenv.get_var("a").expect("a should be registered");
|
|
96
|
+
assert_eq!(get_type_show(&genv, a_vtx), "Integer");
|
|
97
|
+
|
|
98
|
+
let b_vtx = lenv.get_var("b").expect("b should be registered");
|
|
99
|
+
assert_eq!(get_type_show(&genv, b_vtx), "String");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
#[test]
|
|
103
|
+
fn test_multi_write_all_integer() {
|
|
104
|
+
let source = "a, b, c = 1, 2, 3";
|
|
105
|
+
let (genv, lenv) = analyze(source);
|
|
106
|
+
|
|
107
|
+
let a_vtx = lenv.get_var("a").expect("a should be registered");
|
|
108
|
+
assert_eq!(get_type_show(&genv, a_vtx), "Integer");
|
|
109
|
+
|
|
110
|
+
let b_vtx = lenv.get_var("b").expect("b should be registered");
|
|
111
|
+
assert_eq!(get_type_show(&genv, b_vtx), "Integer");
|
|
112
|
+
|
|
113
|
+
let c_vtx = lenv.get_var("c").expect("c should be registered");
|
|
114
|
+
assert_eq!(get_type_show(&genv, c_vtx), "Integer");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
#[test]
|
|
118
|
+
fn test_multi_write_variable_reference_after_assignment() {
|
|
119
|
+
let source = r#"
|
|
120
|
+
a, b = 1, "hello"
|
|
121
|
+
x = a
|
|
122
|
+
"#;
|
|
123
|
+
let (genv, lenv) = analyze(source);
|
|
124
|
+
|
|
125
|
+
let x_vtx = lenv.get_var("x").expect("x should be registered");
|
|
126
|
+
assert_eq!(get_type_show(&genv, x_vtx), "Integer");
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
#[test]
|
|
130
|
+
fn test_multi_write_lhs_longer_than_rhs() {
|
|
131
|
+
let source = "a, b, c = 1, 2";
|
|
132
|
+
let (_, lenv) = analyze(source);
|
|
133
|
+
|
|
134
|
+
assert!(lenv.get_var("a").is_some(), "a should be registered");
|
|
135
|
+
assert!(lenv.get_var("b").is_some(), "b should be registered");
|
|
136
|
+
// KNOWN LIMITATION (v0.1.8): In Ruby, c receives nil, but zip skips it here
|
|
137
|
+
assert!(
|
|
138
|
+
lenv.get_var("c").is_none(),
|
|
139
|
+
"c should not be registered (zip skips)"
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
#[test]
|
|
144
|
+
fn test_multi_write_does_not_panic_on_non_array_rhs() {
|
|
145
|
+
let source = "a, b = some_expr";
|
|
146
|
+
let (_, lenv) = analyze(source);
|
|
147
|
+
|
|
148
|
+
// Variables should be registered (untyped) without panic
|
|
149
|
+
assert!(lenv.get_var("a").is_some(), "a should be registered");
|
|
150
|
+
assert!(lenv.get_var("b").is_some(), "b should be registered");
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -39,7 +39,7 @@ pub(crate) fn process_attr_declaration(
|
|
|
39
39
|
|
|
40
40
|
// Register getter (attr_reader / attr_accessor)
|
|
41
41
|
if matches!(kind, AttrKind::Reader | AttrKind::Accessor) {
|
|
42
|
-
genv.register_user_method(recv_ty.clone(), &attr_name, ivar_vtx, vec![]);
|
|
42
|
+
genv.register_user_method(recv_ty.clone(), &attr_name, ivar_vtx, vec![], None);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
// Register setter (attr_writer / attr_accessor)
|
|
@@ -51,6 +51,7 @@ pub(crate) fn process_attr_declaration(
|
|
|
51
51
|
&format!("{}=", attr_name),
|
|
52
52
|
ivar_vtx,
|
|
53
53
|
vec![param_vtx],
|
|
54
|
+
None,
|
|
54
55
|
);
|
|
55
56
|
}
|
|
56
57
|
}
|
data/rust/src/analyzer/calls.rs
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
//! - Managing return value vertices
|
|
6
6
|
//! - Attaching source location for error reporting
|
|
7
7
|
|
|
8
|
+
use std::collections::HashMap;
|
|
9
|
+
|
|
8
10
|
use crate::env::GlobalEnv;
|
|
9
11
|
use crate::graph::{MethodCallBox, VertexId};
|
|
10
12
|
use crate::source_map::SourceLocation;
|
|
@@ -15,6 +17,7 @@ pub fn install_method_call(
|
|
|
15
17
|
recv_vtx: VertexId,
|
|
16
18
|
method_name: String,
|
|
17
19
|
arg_vtxs: Vec<VertexId>,
|
|
20
|
+
kwarg_vtxs: Option<HashMap<String, VertexId>>,
|
|
18
21
|
location: Option<SourceLocation>,
|
|
19
22
|
) -> VertexId {
|
|
20
23
|
// Create Vertex for return value
|
|
@@ -22,7 +25,8 @@ pub fn install_method_call(
|
|
|
22
25
|
|
|
23
26
|
// Create MethodCallBox with location and argument vertices
|
|
24
27
|
let box_id = genv.alloc_box_id();
|
|
25
|
-
let call_box =
|
|
28
|
+
let call_box =
|
|
29
|
+
MethodCallBox::new(box_id, recv_vtx, method_name, ret_vtx, arg_vtxs, kwarg_vtxs, location);
|
|
26
30
|
genv.register_box(box_id, Box::new(call_box));
|
|
27
31
|
|
|
28
32
|
ret_vtx
|
|
@@ -39,7 +43,7 @@ mod tests {
|
|
|
39
43
|
|
|
40
44
|
let recv_vtx = genv.new_source(Type::string());
|
|
41
45
|
let ret_vtx =
|
|
42
|
-
install_method_call(&mut genv, recv_vtx, "upcase".to_string(), vec![], None);
|
|
46
|
+
install_method_call(&mut genv, recv_vtx, "upcase".to_string(), vec![], None, None);
|
|
43
47
|
|
|
44
48
|
// Return vertex should exist
|
|
45
49
|
assert!(genv.get_vertex(ret_vtx).is_some());
|
|
@@ -51,7 +55,7 @@ mod tests {
|
|
|
51
55
|
|
|
52
56
|
let recv_vtx = genv.new_source(Type::string());
|
|
53
57
|
let _ret_vtx =
|
|
54
|
-
install_method_call(&mut genv, recv_vtx, "upcase".to_string(), vec![], None);
|
|
58
|
+
install_method_call(&mut genv, recv_vtx, "upcase".to_string(), vec![], None, None);
|
|
55
59
|
|
|
56
60
|
// Box should be added
|
|
57
61
|
assert_eq!(genv.box_count(), 1);
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
//! - Method definition scope management (def baz ... end)
|
|
7
7
|
//! - Extracting class/module names from AST nodes (including qualified names like Api::User)
|
|
8
8
|
|
|
9
|
+
use std::collections::HashMap;
|
|
10
|
+
|
|
9
11
|
use crate::env::{GlobalEnv, LocalEnv};
|
|
10
12
|
use crate::graph::{ChangeSet, VertexId};
|
|
11
13
|
use crate::types::Type;
|
|
@@ -78,10 +80,10 @@ pub(crate) fn process_def_node(
|
|
|
78
80
|
let merge_vtx = genv.scope_manager.current_method_return_vertex();
|
|
79
81
|
|
|
80
82
|
// Process parameters BEFORE processing body
|
|
81
|
-
let param_vtxs = if let Some(params_node) = def_node.parameters() {
|
|
83
|
+
let (param_vtxs, keyword_param_vtxs) = if let Some(params_node) = def_node.parameters() {
|
|
82
84
|
install_parameters(genv, lenv, changes, source, ¶ms_node)
|
|
83
85
|
} else {
|
|
84
|
-
vec![]
|
|
86
|
+
(vec![], HashMap::new())
|
|
85
87
|
};
|
|
86
88
|
|
|
87
89
|
let mut last_vtx = None;
|
|
@@ -108,11 +110,13 @@ pub(crate) fn process_def_node(
|
|
|
108
110
|
} else {
|
|
109
111
|
Type::instance(&name)
|
|
110
112
|
};
|
|
113
|
+
let kw_params = (!keyword_param_vtxs.is_empty()).then_some(keyword_param_vtxs);
|
|
111
114
|
genv.register_user_method(
|
|
112
115
|
recv_type,
|
|
113
116
|
&method_name,
|
|
114
117
|
ret_vtx,
|
|
115
118
|
param_vtxs,
|
|
119
|
+
kw_params,
|
|
116
120
|
);
|
|
117
121
|
}
|
|
118
122
|
}
|
|
@@ -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;
|
|
@@ -235,7 +237,8 @@ pub(crate) fn process_needs_child(
|
|
|
235
237
|
} => {
|
|
236
238
|
let recv_vtx = super::install::install_node(genv, lenv, changes, source, &receiver)?;
|
|
237
239
|
process_method_call_common(
|
|
238
|
-
genv, lenv, changes, source,
|
|
240
|
+
genv, lenv, changes, source,
|
|
241
|
+
MethodCallContext { recv_vtx, method_name, location, block, arguments },
|
|
239
242
|
)
|
|
240
243
|
}
|
|
241
244
|
NeedsChildKind::ImplicitSelfCall {
|
|
@@ -251,7 +254,8 @@ pub(crate) fn process_needs_child(
|
|
|
251
254
|
genv.new_source(Type::instance("Object"))
|
|
252
255
|
};
|
|
253
256
|
process_method_call_common(
|
|
254
|
-
genv, lenv, changes, source,
|
|
257
|
+
genv, lenv, changes, source,
|
|
258
|
+
MethodCallContext { recv_vtx, method_name, location, block, arguments },
|
|
255
259
|
)
|
|
256
260
|
}
|
|
257
261
|
NeedsChildKind::AttrDeclaration { kind, attr_names } => {
|
|
@@ -277,6 +281,15 @@ fn finish_local_var_write(
|
|
|
277
281
|
install_local_var_write(genv, lenv, changes, var_name, value_vtx)
|
|
278
282
|
}
|
|
279
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
|
+
|
|
280
293
|
/// MethodCall / ImplicitSelfCall common processing:
|
|
281
294
|
/// Handles argument processing, block processing, and MethodCallBox creation after recv_vtx is obtained
|
|
282
295
|
fn process_method_call_common<'a>(
|
|
@@ -284,16 +297,44 @@ fn process_method_call_common<'a>(
|
|
|
284
297
|
lenv: &mut LocalEnv,
|
|
285
298
|
changes: &mut ChangeSet,
|
|
286
299
|
source: &str,
|
|
287
|
-
|
|
288
|
-
method_name: String,
|
|
289
|
-
location: SourceLocation,
|
|
290
|
-
block: Option<Node<'a>>,
|
|
291
|
-
arguments: Vec<Node<'a>>,
|
|
300
|
+
ctx: MethodCallContext<'a>,
|
|
292
301
|
) -> Option<VertexId> {
|
|
293
|
-
let
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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
|
+
}
|
|
297
338
|
|
|
298
339
|
if let Some(block_node) = block {
|
|
299
340
|
if let Some(block) = block_node.as_block_node() {
|
|
@@ -314,8 +355,19 @@ fn process_method_call_common<'a>(
|
|
|
314
355
|
}
|
|
315
356
|
}
|
|
316
357
|
|
|
358
|
+
let kwarg_vtxs = if keyword_arg_vtxs.is_empty() {
|
|
359
|
+
None
|
|
360
|
+
} else {
|
|
361
|
+
Some(keyword_arg_vtxs)
|
|
362
|
+
};
|
|
363
|
+
|
|
317
364
|
Some(finish_method_call(
|
|
318
|
-
genv,
|
|
365
|
+
genv,
|
|
366
|
+
recv_vtx,
|
|
367
|
+
method_name,
|
|
368
|
+
positional_arg_vtxs,
|
|
369
|
+
kwarg_vtxs,
|
|
370
|
+
location,
|
|
319
371
|
))
|
|
320
372
|
}
|
|
321
373
|
|
|
@@ -325,9 +377,10 @@ fn finish_method_call(
|
|
|
325
377
|
recv_vtx: VertexId,
|
|
326
378
|
method_name: String,
|
|
327
379
|
arg_vtxs: Vec<VertexId>,
|
|
380
|
+
kwarg_vtxs: Option<HashMap<String, VertexId>>,
|
|
328
381
|
location: SourceLocation,
|
|
329
382
|
) -> VertexId {
|
|
330
|
-
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))
|
|
331
384
|
}
|
|
332
385
|
|
|
333
386
|
#[cfg(test)]
|
|
@@ -980,4 +1033,105 @@ end
|
|
|
980
1033
|
genv.type_errors
|
|
981
1034
|
);
|
|
982
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
|
+
}
|
|
983
1137
|
}
|